From ac09b26253275a4c5174fa21c9a0387b3e262e8d Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 20 May 2026 15:05:35 +0800 Subject: [PATCH] first commit --- .gitignore | 92 + Back_Up.sh | 104 + Check_Graph_Card.sh | 56 + .../1_rename_pics.sh | 308 ++ .../2_1_Trans_to_png.py | 73 + .../2_2_Resize.py | 103 + .../2_reformate_pics.sh | 118 + .../3_pair_ori_label.sh | 186 + .../4_deal_labels.py | 445 +++ .../4_deal_labels_old(老版程序).py | 463 +++ .../4_rebuild_labels.sh | 163 + .../5_TOOL_stack_pics.sh | 122 + .../5_stack_picture.py | 41 + .../6_TOOL_stitch_pics.sh | 167 + .../6_stitch_picture.py | 75 + .../Seg_data_run.sh | 246 ++ .../※1_环境安装.txt | 14 + .../※2_使用手册.txt | 28 + README.md | 264 ++ Seg_All_In_One_Analysis/1_Analysis_All.py | 298 ++ ...olecSeg8k-13Type-1920x1080_mIoU_vs_FPS.svg | 2992 +++++++++++++++++ ...k-13Type-1920x1080_performance_summary.csv | 21 + ...Seg8k-13Type-1920x1080_all_iou_summary.csv | 21 + ...utoLaparo-10Type-1920x1080_mIoU_vs_FPS.svg | 2906 ++++++++++++++++ ...o-10Type-1920x1080_performance_summary.csv | 21 + ...aparo-10Type-1920x1080_all_iou_summary.csv | 21 + ...Endovis_2017-8Type-512x512_mIoU_vs_FPS.svg | 2815 ++++++++++++++++ ...2017-8Type-512x512_performance_summary.csv | 21 + ...vis_2017-8Type-512x512_all_iou_summary.csv | 21 + ...Endovis_2018-8Type-512x512_mIoU_vs_FPS.svg | 2912 ++++++++++++++++ ...2018-8Type-512x512_performance_summary.csv | 21 + ...vis_2018-8Type-512x512_all_iou_summary.csv | 21 + ...1_4_Dresden-11Type-512x512_mIoU_vs_FPS.svg | 2906 ++++++++++++++++ ...den-11Type-512x512_performance_summary.csv | 21 + ...Dresden-11Type-512x512_all_iou_summary.csv | 21 + Seg_All_In_One_MMSeg/CITATION.cff | 8 + Seg_All_In_One_MMSeg/LICENSE | 203 ++ Seg_All_In_One_MMSeg/MANIFEST.in | 5 + .../0_Initial_Save_All_Model_locally.py | 297 ++ .../1_Data_Parameter/All_Data_Record.json | 628 ++++ .../1_Data_Parameter/my_dataset_model.json | 50 + .../public_dataset_autoLaparo.json | 42 + .../public_dataset_cholecseg8k.json | 42 + .../public_dataset_dresden.json | 42 + .../public_dataset_endovis_2017.json | 42 + .../public_dataset_endovis_2018.json | 42 + .../My_All_In_One/1_Initial_Data_All-ori.py | 61 + ..._Data_All_data_from_1_Data_Parameter-V1.py | 209 ++ ..._Data_All_data_from_1_Data_Parameter-V2.py | 212 ++ .../My_All_In_One/2_Alg_Program/my_ann_r50.py | 101 + .../2_Alg_Program/my_apcnet_r50.py | 102 + .../2_Alg_Program/my_beit_upernet.py | 176 + .../2_Alg_Program/my_bisenetv1_r18.py | 133 + .../2_Alg_Program/my_bisenetv2.py | 111 + .../2_Alg_Program/my_ccnet_r50.py | 102 + .../2_Alg_Program/my_cgnet_fcn.py | 95 + .../2_Alg_Program/my_danet_r50.py | 102 + .../My_All_In_One/2_Alg_Program/my_ddrnet.py | 95 + .../2_Alg_Program/my_deeplabv3_r50.py | 135 + .../2_Alg_Program/my_deeplabv3plus_r50.py | 135 + .../2_Alg_Program/my_dnlnet_r50.py | 102 + .../My_All_In_One/2_Alg_Program/my_dpt_vit.py | 100 + .../2_Alg_Program/my_emanet_r50.py | 106 + .../2_Alg_Program/my_encnet_r50.py | 106 + .../2_Alg_Program/my_erfnet_fcn.py | 83 + .../2_Alg_Program/my_fast_scnn.py | 89 + .../2_Alg_Program/my_fastfcn_r50_neck.py | 189 ++ .../My_All_In_One/2_Alg_Program/my_fcn_r18.py | 132 + .../2_Alg_Program/my_gcnet_r50.py | 106 + .../2_Alg_Program/my_hrnet_fcn.py | 144 + .../2_Alg_Program/my_icnet_r18.py | 124 + .../2_Alg_Program/my_isanet_r50.py | 106 + .../My_All_In_One/2_Alg_Program/my_knet.py | 276 ++ .../2_Alg_Program/my_mae_upernet.py | 136 + .../2_Alg_Program/my_mask2former.py | 284 ++ .../2_Alg_Program/my_maskformer.py | 246 ++ .../My_All_In_One/2_Alg_Program/my_pidnet.py | 152 + .../My_All_In_One/2_Alg_Program/my_pspnet.py | 111 + .../My_All_In_One/2_Alg_Program/my_stdc.py | 139 + .../2_Alg_Program/my_unet_deeplabv3.py | 105 + .../2_Initial_Alg_All-ori-old.py | 70 + ...tial_Alg_All_data_from_2_Alg_Program-V1.py | 301 ++ ...tial_Alg_All_data_from_2_Alg_Program-V2.py | 357 ++ .../3_Find_And_Delete_Special_Epoch.py | 78 + .../3_Tool_Copy_Result_To_Hardisk.sh | 99 + .../4_1_predict_params_FLOPs_FPS_V2.py | 427 +++ .../4_1_predict_params_and_FLOPs_V1.py | 332 ++ .../4_2_predict_matrics_from_log_V1.py | 322 ++ .../4_2_predict_matrics_from_log_V2.py | 366 ++ .../4_3_predict_draw_pictures_and_tabels.py | 249 ++ .../4_4_extract_loss_and_best_miou.py | 413 +++ .../Initial_Alg_Gen_0_base_.py | 28 + .../Initial_Alg_Gen_1_norm_cfg.py | 19 + .../Initial_Alg_Gen_2_data_preprocessor.py | 23 + .../Initial_Alg_Gen_3_model.py | 119 + .../Initial_Alg_Gen_4_optimizer.py | 173 + .../Initial_Alg_Gen_5_train_dataloader.py | 59 + .../Initial_Alg_Gen_Tool.py | 264 ++ .../Initial_Alg_Select_Tool.py | 236 ++ .../Initial_Data_Calculate_std_and_mean.py | 46 + ...ta_Gen_configs_base_datasets_my_dataset.py | 121 + .../Initial_Data_Gen_mmseg_datasets_init_.py | 114 + ...tial_Data_Gen_mmseg_datasets_my_dataset.py | 83 + ...nitial_Data_Gen_mmseg_utils_class_names.py | 617 ++++ ...Gen_configs_base_schedules_schedule_XXe.py | 82 + ...Gen_configs_base_schedules_schedule_XXk.py | 57 + .../My_All_In_One/x4_Predict_V1-.py | 540 +++ .../My_All_In_One/x4_Predict_V2-.py | 613 ++++ Seg_All_In_One_MMSeg/README.md | 420 +++ Seg_All_In_One_MMSeg/README_zh-CN.md | 426 +++ .../configs/_base_/datasets/ade20k.py | 68 + .../configs/_base_/datasets/ade20k_640x640.py | 68 + .../configs/_base_/datasets/bdd100k.py | 70 + .../configs/_base_/datasets/chase_db1.py | 75 + .../configs/_base_/datasets/cityscapes.py | 67 + .../_base_/datasets/cityscapes_1024x1024.py | 29 + .../_base_/datasets/cityscapes_768x768.py | 29 + .../_base_/datasets/cityscapes_769x769.py | 29 + .../_base_/datasets/cityscapes_832x832.py | 29 + .../configs/_base_/datasets/coco-stuff10k.py | 69 + .../configs/_base_/datasets/coco-stuff164k.py | 67 + .../configs/_base_/datasets/drive.py | 73 + .../configs/_base_/datasets/hrf.py | 73 + .../configs/_base_/datasets/hsi_drive.py | 53 + .../configs/_base_/datasets/isaid.py | 73 + .../configs/_base_/datasets/levir_256x256.py | 68 + .../configs/_base_/datasets/loveda.py | 66 + .../configs/_base_/datasets/mapillary_v1.py | 68 + .../_base_/datasets/mapillary_v1_65.py | 37 + .../configs/_base_/datasets/mapillary_v2.py | 68 + .../configs/_base_/datasets/my_dataset.py | 64 + .../_base_/datasets/my_dataset_model.py | 64 + .../configs/_base_/datasets/nyu.py | 67 + .../configs/_base_/datasets/nyu_512x512.py | 72 + .../configs/_base_/datasets/pascal_context.py | 56 + .../_base_/datasets/pascal_context_59.py | 72 + .../configs/_base_/datasets/pascal_voc12.py | 69 + .../_base_/datasets/pascal_voc12_aug.py | 81 + .../configs/_base_/datasets/potsdam.py | 66 + .../datasets/publicdataset_autolaparo.py | 64 + .../datasets/publicdataset_cholecseg8k.py | 64 + .../_base_/datasets/publicdataset_dresden.py | 64 + .../datasets/publicdataset_endovis_2017.py | 64 + .../datasets/publicdataset_endovis_2018.py | 64 + .../configs/_base_/datasets/refuge.py | 90 + .../configs/_base_/datasets/stare.py | 73 + .../configs/_base_/datasets/synapse.py | 41 + .../configs/_base_/datasets/vaihingen.py | 66 + .../configs/_base_/default_runtime.py | 15 + .../configs/_base_/models/ann_r50-d8.py | 54 + .../configs/_base_/models/apcnet_r50-d8.py | 52 + .../_base_/models/bisenetv1_r18-d32.py | 78 + .../configs/_base_/models/bisenetv2.py | 88 + .../configs/_base_/models/bisenetv2_large.py | 88 + .../configs/_base_/models/ccnet_r50-d8.py | 52 + .../configs/_base_/models/cgnet.py | 44 + .../configs/_base_/models/danet_r50-d8.py | 52 + .../configs/_base_/models/ddrnet.py | 55 + .../configs/_base_/models/deeplabv3_r50-d8.py | 52 + .../_base_/models/deeplabv3_unet_s5-d16.py | 58 + .../_base_/models/deeplabv3plus_r50-d8.py | 54 + .../configs/_base_/models/dmnet_r50-d8.py | 52 + .../configs/_base_/models/dnl_r50-d8.py | 54 + .../configs/_base_/models/dpt_vit-b16.py | 39 + .../configs/_base_/models/emanet_r50-d8.py | 55 + .../configs/_base_/models/en_bisenetv2.py | 92 + .../configs/_base_/models/encnet_r50-d8.py | 56 + .../configs/_base_/models/erfnet_fcn.py | 40 + .../configs/_base_/models/fast_scnn.py | 65 + .../_base_/models/fastfcn_r50-d32_jpu_psp.py | 61 + .../configs/_base_/models/fcn_hr18.py | 60 + .../configs/_base_/models/fcn_r50-d8.py | 53 + .../configs/_base_/models/fcn_unet_s5-d16.py | 59 + .../_base_/models/fpn_poolformer_s12.py | 54 + .../configs/_base_/models/fpn_r50.py | 44 + .../configs/_base_/models/gcnet_r50-d8.py | 54 + .../configs/_base_/models/icnet_r50-d8.py | 82 + .../configs/_base_/models/isanet_r50-d8.py | 53 + .../configs/_base_/models/knet_r50-d8_my.py | 83 + .../configs/_base_/models/lraspp_m-v3-d8.py | 33 + .../configs/_base_/models/mask2former_my.py | 136 + .../configs/_base_/models/maskformer_my.py | 108 + .../configs/_base_/models/my_bisenetv2_A1.py | 88 + .../configs/_base_/models/my_bisenetv2_A2.py | 88 + .../configs/_base_/models/nonlocal_r50-d8.py | 54 + .../configs/_base_/models/ocrnet_hr18.py | 76 + .../configs/_base_/models/ocrnet_r50-d8.py | 55 + .../configs/_base_/models/pidnet.py | 68 + .../configs/_base_/models/pointrend_r50.py | 64 + .../configs/_base_/models/psanet_r50-d8.py | 57 + .../configs/_base_/models/pspnet_r50-d8.py | 52 + .../_base_/models/pspnet_unet_s5-d16.py | 58 + .../configs/_base_/models/san_vit-b16.py | 137 + .../configs/_base_/models/segformer_mit-b0.py | 42 + .../_base_/models/segmenter_vit-b16_mask.py | 44 + .../configs/_base_/models/setr_mla.py | 103 + .../configs/_base_/models/setr_naive.py | 88 + .../configs/_base_/models/setr_pup.py | 88 + .../configs/_base_/models/stdc.py | 91 + .../_base_/models/twins_pcpvt-s_fpn.py | 53 + .../_base_/models/twins_pcpvt-s_upernet.py | 61 + .../configs/_base_/models/upernet_beit.py | 58 + .../configs/_base_/models/upernet_convnext.py | 52 + .../configs/_base_/models/upernet_mae.py | 57 + .../configs/_base_/models/upernet_r50.py | 52 + .../configs/_base_/models/upernet_swin.py | 62 + .../_base_/models/upernet_vit-b16_ln_mln.py | 65 + .../configs/_base_/models/vpd_sd.py | 86 + .../configs/_base_/schedules/schedule_160k.py | 25 + .../schedules/schedule_160k_check_10000.py | 25 + .../schedules/schedule_160k_check_16000.py | 25 + .../configs/_base_/schedules/schedule_20k.py | 24 + .../configs/_base_/schedules/schedule_240k.py | 25 + .../configs/_base_/schedules/schedule_25k.py | 28 + .../schedules/schedule_300e_val1_check10.py | 30 + .../schedule_300e_val1_check10_SGD.py | 30 + .../configs/_base_/schedules/schedule_320k.py | 25 + .../configs/_base_/schedules/schedule_40k.py | 24 + .../schedules/schedule_40k_check_4000.py | 25 + .../configs/_base_/schedules/schedule_4k.py | 25 + .../_base_/schedules/schedule_4k_check_400.py | 25 + .../configs/_base_/schedules/schedule_80k.py | 24 + .../_base_/schedules/schedule_XXXk_model.py | 25 + .../configs/_base_/schedules/schedules_4k.py | 25 + Seg_All_In_One_MMSeg/configs/ann/README.md | 68 + ...nn_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...ann_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...nn_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...ann_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../ann_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + .../ann_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + .../ann_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../ann_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...ann_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + .../ann_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...ann_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + .../ann_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../ann_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + .../ann_r50-d8_4xb4-20k_voc12aug-512x512.py | 10 + .../ann_r50-d8_4xb4-40k_voc12aug-512x512.py | 10 + .../ann/ann_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../configs/ann/metafile.yaml | 391 +++ Seg_All_In_One_MMSeg/configs/ann/my_ann.py | 41 + ...000_my_dataset_model-512x1024-testslide.py | 101 + ...0_my_dataset_model-512x512-no_testslide.py | 99 + ...0_my_dataset_model-512x512-no_testslide.py | 99 + ...n_r50-d8_b4-4k_my_dataset_model-512x512.py | 25 + ...0_my_dataset_model-769x769-no_testslide.py | 99 + ...0_my_dataset_model-512x512-no_testslide.py | 99 + Seg_All_In_One_MMSeg/configs/apcnet/README.md | 59 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...apcnet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + .../apcnet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...cnet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...cnet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../apcnet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + .../apcnet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../configs/apcnet/metafile.yaml | 296 ++ ...0_my_dataset_model-512x512-no_testslide.py | 99 + ...0_my_dataset_model-512x512-no_testslide.py | 99 + ...0_my_dataset_model-512x512-no_testslide.py | 103 + Seg_All_In_One_MMSeg/configs/beit/README.md | 85 + ...t-base_upernet_8xb2-160k_ade20k-640x640.py | 36 + ...ase_upernet_8xb2-160k_ade20k-640x640_ms.py | 16 + ...ge_upernet_8xb1-amp-160k_ade20k-640x640.py | 50 + ...upernet_8xb1-amp-160k_ade20k-640x640_ms.py | 16 + .../configs/beit/metafile.yaml | 49 + ...40k_check_4000_my_dataset_model-640x640.py | 134 + ...40k_check_4000_my_dataset_model-640x640.py | 132 + ...40k_check_4000_my_dataset_model-512x512.py | 140 + ...40k_check_4000_my_dataset_model-640x640.py | 140 + ...4000_my_dataset_model-512x512-testslide.py | 115 + .../configs/bisenetv1/README.md | 64 + ...1k-pre_4xb4-160k_coco-stuff164k-512x512.py | 6 + ...01-d32_4xb4-160k_coco-stuff164k-512x512.py | 58 + ...in1k-pre_4xb4-160k_cityscapes-1024x1024.py | 29 + ...1k-pre_4xb4-160k_coco-stuff164k-512x512.py | 10 + ...in1k-pre_4xb8-160k_cityscapes-1024x1024.py | 4 + ..._r18-d32_4xb4-160k_cityscapes-1024x1024.py | 24 + ...18-d32_4xb4-160k_coco-stuff164k-512x512.py | 53 + ...in1k-pre_4xb4-160k_cityscapes-1024x1024.py | 7 + ...1k-pre_4xb4-160k_coco-stuff164k-512x512.py | 7 + ..._r50-d32_4xb4-160k_cityscapes-1024x1024.py | 55 + ...50-d32_4xb4-160k_coco-stuff164k-512x512.py | 58 + .../configs/bisenetv1/metafile.yaml | 275 ++ ...heck10_publicdataset_autolaparo-512x512.py | 140 + ...eck10_publicdataset_cholecseg8k-512x512.py | 140 + ...1_check10_publicdataset_dresden-512x512.py | 140 + ...ck10_publicdataset_endovis_2017-512x512.py | 140 + ...ck10_publicdataset_endovis_2018-512x512.py | 140 + ...heck10_publicdataset_autolaparo-512x512.py | 140 + ...eck10_publicdataset_cholecseg8k-512x512.py | 140 + ...1_check10_publicdataset_dresden-512x512.py | 140 + ...ck10_publicdataset_endovis_2017-512x512.py | 140 + ...ck10_publicdataset_endovis_2018-512x512.py | 140 + .../configs/bisenetv2/README.md | 53 + ...etv2_fcn_4xb4-160k_cityscapes-1024x1024.py | 24 + ..._fcn_4xb4-amp-160k_cityscapes-1024x1024.py | 6 + ...fcn_4xb4-ohem-160k_cityscapes-1024x1024.py | 83 + ...etv2_fcn_4xb8-160k_cityscapes-1024x1024.py | 24 + .../configs/bisenetv2/metafile.yaml | 114 + ...heck10_publicdataset_autolaparo-512x512.py | 155 + ...eck10_publicdataset_cholecseg8k-512x512.py | 155 + ...1_check10_publicdataset_dresden-512x512.py | 154 + ...ck10_publicdataset_endovis_2017-512x512.py | 154 + ...ck10_publicdataset_endovis_2018-512x512.py | 154 + ...heck10_publicdataset_autolaparo-512x512.py | 155 + ...eck10_publicdataset_cholecseg8k-512x512.py | 155 + ...1_check10_publicdataset_dresden-512x512.py | 154 + ...ck10_publicdataset_endovis_2017-512x512.py | 154 + ...ck10_publicdataset_endovis_2018-512x512.py | 154 + ...heck10_publicdataset_autolaparo-512x512.py | 164 + ...eck10_publicdataset_cholecseg8k-512x512.py | 164 + ...1_check10_publicdataset_dresden-512x512.py | 164 + ...ck10_publicdataset_endovis_2017-512x512.py | 164 + ...ck10_publicdataset_endovis_2018-512x512.py | 164 + Seg_All_In_One_MMSeg/configs/ccnet/README.md | 67 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../ccnet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...ccnet_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...ccnet_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../ccnet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...cnet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...cnet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../ccnet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + .../ccnet_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + .../ccnet_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + .../ccnet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../configs/ccnet/metafile.yaml | 391 +++ ...4000_my_dataset_model-512x512-testslide.py | 105 + ...40k_check_4000_my_dataset_model-680x680.py | 99 + Seg_All_In_One_MMSeg/configs/cgnet/README.md | 46 + .../cgnet_fcn_4xb4-60k_cityscapes-680x680.py | 59 + .../cgnet_fcn_4xb8-60k_cityscapes-512x1024.py | 38 + .../configs/cgnet/metafile.yaml | 61 + ...40k_check_4000_my_dataset_model-680x680.py | 99 + ...40k_check_4000_my_dataset_model-680x680.py | 84 + ...40k_check_4000_my_dataset_model-680x680.py | 100 + .../configs/convnext/README.md | 74 + ...se_upernet_8xb2-amp-160k_ade20k-512x512.py | 43 + ...se_upernet_8xb2-amp-160k_ade20k-640x640.py | 58 + ...ge_upernet_8xb2-amp-160k_ade20k-640x640.py | 58 + ...ll_upernet_8xb2-amp-160k_ade20k-512x512.py | 57 + ...ny_upernet_8xb2-amp-160k_ade20k-512x512.py | 57 + ...ge_upernet_8xb2-amp-160k_ade20k-640x640.py | 58 + .../configs/convnext/metafile.yaml | 145 + Seg_All_In_One_MMSeg/configs/danet/README.md | 67 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../danet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...danet_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...danet_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../danet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...anet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...anet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../danet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + .../danet_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + .../danet_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + .../danet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../configs/danet/metafile.yaml | 387 +++ ...0_my_dataset_model-512x512-no_testslide.py | 103 + Seg_All_In_One_MMSeg/configs/ddrnet/README.md | 46 + ...in1k-pre_2xb6-120k_cityscapes-1024x1024.py | 93 + ...in1k-pre_2xb6-120k_cityscapes-1024x1024.py | 93 + .../configs/ddrnet/metafile.yaml | 64 + ...heck10_publicdataset_autolaparo-512x512.py | 89 + ...eck10_publicdataset_cholecseg8k-512x512.py | 89 + ...1_check10_publicdataset_dresden-512x512.py | 89 + ...ck10_publicdataset_endovis_2017-512x512.py | 89 + ...ck10_publicdataset_endovis_2018-512x512.py | 89 + ...heck10_publicdataset_autolaparo-512x512.py | 89 + ...eck10_publicdataset_cholecseg8k-512x512.py | 89 + ...1_check10_publicdataset_dresden-512x512.py | 89 + ...ck10_publicdataset_endovis_2017-512x512.py | 89 + ...ck10_publicdataset_endovis_2018-512x512.py | 89 + .../configs/deeplabv3/README.md | 118 + ...-d16-mg124_4xb2-40k_cityscapes-512x1024.py | 11 + ...-d16-mg124_4xb2-80k_cityscapes-512x1024.py | 11 + ...v3_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...bv3_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...v3_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...bv3_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...101-d8_4xb2-amp-80k_cityscapes-512x1024.py | 7 + ...plabv3_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...101-d8_4xb4-160k_coco-stuff164k-512x512.py | 2 + ..._r101-d8_4xb4-20k_coco-stuff10k-512x512.py | 2 + ...labv3_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...101-d8_4xb4-320k_coco-stuff164k-512x512.py | 2 + ..._r101-d8_4xb4-40k_coco-stuff10k-512x512.py | 2 + ...r101-d8_4xb4-40k_pascal-context-480x480.py | 2 + ...1-d8_4xb4-40k_pascal-context-59-480x480.py | 2 + ...labv3_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + ...eplabv3_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...r101-d8_4xb4-80k_coco-stuff164k-512x512.py | 2 + ...r101-d8_4xb4-80k_pascal-context-480x480.py | 2 + ...1-d8_4xb4-80k_pascal-context-59-480x480.py | 2 + ...3_r101b-d8_4xb2-80k_cityscapes-512x1024.py | 4 + ...v3_r101b-d8_4xb2-80k_cityscapes-769x769.py | 4 + ...bv3_r18-d8_4xb2-80k_cityscapes-512x1024.py | 9 + ...abv3_r18-d8_4xb2-80k_cityscapes-769x769.py | 9 + ...v3_r18b-d8_4xb2-80k_cityscapes-512x1024.py | 9 + ...bv3_r18b-d8_4xb2-80k_cityscapes-769x769.py | 9 + ...bv3_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...abv3_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...bv3_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...abv3_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + ...eplabv3_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + ...r50-d8_4xb4-160k_coco-stuff164k-512x512.py | 11 + ...3_r50-d8_4xb4-20k_coco-stuff10k-512x512.py | 11 + ...plabv3_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + ...r50-d8_4xb4-320k_coco-stuff164k-512x512.py | 11 + ...3_r50-d8_4xb4-40k_coco-stuff10k-512x512.py | 11 + ..._r50-d8_4xb4-40k_pascal-context-480x480.py | 14 + ...0-d8_4xb4-40k_pascal-context-59-480x480.py | 14 + ...plabv3_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + ...eeplabv3_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + ..._r50-d8_4xb4-80k_coco-stuff164k-512x512.py | 11 + ..._r50-d8_4xb4-80k_pascal-context-480x480.py | 14 + ...0-d8_4xb4-80k_pascal-context-59-480x480.py | 14 + ...v3_r50b-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...bv3_r50b-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../configs/deeplabv3/metafile.yaml | 985 ++++++ ...4000_my_dataset_model-769x769-testslide.py | 125 + ...ataset_cholecseg8k-512x512-no_testslide.py | 20 + .../configs/deeplabv3plus/README.md | 138 + ...-d16-mg124_4xb2-40k_cityscapes-512x1024.py | 11 + ...-d16-mg124_4xb2-80k_cityscapes-512x1024.py | 11 + ...us_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...lus_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...us_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...lus_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...101-d8_4xb2-amp-80k_cityscapes-512x1024.py | 7 + ...v3plus_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...3plus_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...r101-d8_4xb4-40k_pascal-context-480x480.py | 2 + ...1-d8_4xb4-40k_pascal-context-59-480x480.py | 2 + ...3plus_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + ...bv3plus_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...bv3plus_r101-d8_4xb4-80k_loveda-512x512.py | 2 + ...r101-d8_4xb4-80k_pascal-context-480x480.py | 2 + ...1-d8_4xb4-80k_pascal-context-59-480x480.py | 2 + ...v3plus_r101-d8_4xb4-80k_potsdam-512x512.py | 2 + ...plus_r101-d8_4xb4-80k_vaihingen-512x512.py | 2 + ...s_r101b-d8_4xb2-80k_cityscapes-512x1024.py | 4 + ...us_r101b-d8_4xb2-80k_cityscapes-769x769.py | 4 + ...lus_r18-d8_4xb2-80k_cityscapes-512x1024.py | 11 + ...plus_r18-d8_4xb2-80k_cityscapes-769x769.py | 11 + ...labv3plus_r18-d8_4xb4-80k_isaid-896x896.py | 11 + ...abv3plus_r18-d8_4xb4-80k_loveda-512x512.py | 11 + ...bv3plus_r18-d8_4xb4-80k_potsdam-512x512.py | 11 + ...3plus_r18-d8_4xb4-80k_vaihingen-512x512.py | 11 + ...us_r18b-d8_4xb2-80k_cityscapes-512x1024.py | 11 + ...lus_r18b-d8_4xb2-80k_cityscapes-769x769.py | 11 + ...0-d8_4xb2-300k_mapillay_v1_65-1280x1280.py | 58 + ...lus_r50-d8_4xb2-40k_cityscapes-512x1024.py | 8 + ...plus_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...lus_r50-d8_4xb2-80k_cityscapes-512x1024.py | 8 + ...plus_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + ...bv3plus_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + ...v3plus_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + ..._r50-d8_4xb4-40k_pascal-context-480x480.py | 14 + ...0-d8_4xb4-40k_pascal-context-59-480x480.py | 14 + ...v3plus_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + ...abv3plus_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + ...labv3plus_r50-d8_4xb4-80k_isaid-896x896.py | 10 + ...abv3plus_r50-d8_4xb4-80k_loveda-512x512.py | 10 + ..._r50-d8_4xb4-80k_pascal-context-480x480.py | 14 + ...0-d8_4xb4-80k_pascal-context-59-480x480.py | 14 + ...bv3plus_r50-d8_4xb4-80k_potsdam-512x512.py | 11 + ...3plus_r50-d8_4xb4-80k_vaihingen-512x512.py | 11 + ...us_r50b-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...lus_r50b-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../configs/deeplabv3plus/metafile.yaml | 1041 ++++++ ..._my_dataset_model-512x1024-no_testslide.py | 116 + ...000_my_dataset_model-512x1024-testslide.py | 118 + ...0_my_dataset_model-512x512-no_testslide.py | 125 + ...4000_my_dataset_model-512x512-testslide.py | 127 + ...dataset_autolaparo-512x512-no_testslide.py | 20 + ...ataset_cholecseg8k-512x512-no_testslide.py | 20 + ...licdataset_dresden-512x512-no_testslide.py | 20 + ...taset_endovis_2017-512x512-no_testslide.py | 20 + ...taset_endovis_2018-512x512-no_testslide.py | 20 + ...dataset_autolaparo-512x512-no_testslide.py | 23 + ...ataset_cholecseg8k-512x512-no_testslide.py | 22 + ...licdataset_dresden-512x512-no_testslide.py | 23 + ...taset_endovis_2017-512x512-no_testslide.py | 23 + ...taset_endovis_2018-512x512-no_testslide.py | 23 + Seg_All_In_One_MMSeg/configs/dmnet/README.md | 59 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../dmnet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + .../dmnet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...mnet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...mnet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../dmnet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + .../dmnet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../configs/dmnet/metafile.yaml | 296 ++ ...b4-no_slide_test-160k_MyDataset-512x512.py | 46 + Seg_All_In_One_MMSeg/configs/dnlnet/README.md | 62 + ...nl_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...dnl_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...nl_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...dnl_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../dnl_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + .../dnl_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...dnl_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + .../dnl_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...dnl_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + .../dnl_r50-d8_4xb2-80k_cityscapes-769x769.py | 16 + .../dnl_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + .../dnl_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../configs/dnlnet/metafile.yaml | 292 ++ ...4000_my_dataset_model-512x512-testslide.py | 105 + Seg_All_In_One_MMSeg/configs/dpt/README.md | 67 + .../dpt_vit-b16_8xb2-160k_ade20k-512x512.py | 40 + .../configs/dpt/metafile.yaml | 37 + ...k_check_4000_my_dataset_model-1024x1024.py | 95 + ...k_check_4000_my_dataset_model-1024x1024.py | 101 + ...40k_check_4000_my_dataset_model-512x512.py | 101 + ...k_check_4000_my_dataset_model-1024x1024.py | 101 + .../configs/dsdl【数据集描述语言】/README.md | 103 + .../dsdl【数据集描述语言】/cityscapes.py | 70 + .../configs/dsdl【数据集描述语言】/voc.py | 65 + Seg_All_In_One_MMSeg/configs/emanet/README.md | 46 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...anet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../configs/emanet/metafile.yaml | 109 + ...000_my_dataset_model-512x1024-testslide.py | 105 + ...0_my_dataset_model-512x512-no_testslide.py | 103 + Seg_All_In_One_MMSeg/configs/encnet/README.md | 59 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...encnet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...ncnet_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...ncnet_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../encnet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...cnet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...cnet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../encnet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + ...encnet_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + ...encnet_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + .../encnet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../encnet_r50s-d8_4xb4-80k_ade20k-512x512.py | 11 + .../configs/encnet/metafile.yaml | 296 ++ ..._my_dataset_model-512x1024-no_testslide.py | 103 + Seg_All_In_One_MMSeg/configs/erfnet/README.md | 54 + ...rfnet_fcn_4xb4-160k_cityscapes-512x1024.py | 10 + .../configs/erfnet/metafile.yaml | 37 + ...0k_check_4000_my_dataset_model-512x1024.py | 84 + .../configs/fastfcn/README.md | 63 + ...2_jpu_aspp_4xb2-80k_cityscapes-512x1024.py | 20 + ...0-d32_jpu_aspp_4xb4-160k_ade20k-512x512.py | 20 + ...50-d32_jpu_aspp_4xb4-80k_ade20k-512x512.py | 20 + ...2_jpu_aspp_4xb4-80k_cityscapes-512x1024.py | 5 + ...32_jpu_enc_4xb2-80k_cityscapes-512x1024.py | 24 + ...50-d32_jpu_enc_4xb4-160k_ade20k-512x512.py | 24 + ...r50-d32_jpu_enc_4xb4-80k_ade20k-512x512.py | 24 + ...32_jpu_enc_4xb4-80k_cityscapes-512x1024.py | 5 + ...32_jpu_psp_4xb2-80k_cityscapes-512x1024.py | 8 + ...50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py | 11 + ...r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py | 11 + ...32_jpu_psp_4xb4-80k_cityscapes-512x1024.py | 11 + .../configs/fastfcn/metafile.yaml | 311 ++ ...40k_check_4000_my_dataset_model-512x512.py | 112 + ...heck10_publicdataset_autolaparo-512x512.py | 112 + ...eck10_publicdataset_cholecseg8k-512x512.py | 112 + ...1_check10_publicdataset_dresden-512x512.py | 112 + ...ck10_publicdataset_endovis_2017-512x512.py | 112 + ...ck10_publicdataset_endovis_2018-512x512.py | 112 + ...40k_check_4000_my_dataset_model-512x512.py | 120 + .../configs/fastscnn/README.md | 42 + ...fast_scnn_8xb4-160k_cityscapes-512x1024.py | 15 + .../configs/fastscnn/metafile.yaml | 37 + ...heck10_publicdataset_autolaparo-512x512.py | 122 + ...eck10_publicdataset_cholecseg8k-512x512.py | 122 + ...1_check10_publicdataset_dresden-512x512.py | 122 + ...ck10_publicdataset_endovis_2017-512x512.py | 122 + ...ck10_publicdataset_endovis_2018-512x512.py | 122 + ...0k_check_4000_my_dataset_model-512x1024.py | 134 + Seg_All_In_One_MMSeg/configs/fcn/README.md | 111 + ...6_r101-d16_4xb2-40k_cityscapes-512x1024.py | 2 + ...d6_r101-d16_4xb2-40k_cityscapes-769x769.py | 2 + ...6_r101-d16_4xb2-80k_cityscapes-512x1024.py | 2 + ...d6_r101-d16_4xb2-80k_cityscapes-769x769.py | 2 + ..._r101b-d16_4xb2-80k_cityscapes-512x1024.py | 4 + ...6_r101b-d16_4xb2-80k_cityscapes-769x769.py | 4 + ...d6_r50-d16_4xb2-40k_cityscapes-512x1024.py | 11 + ...-d6_r50-d16_4xb2-40k_cityscapes-769x769.py | 13 + ...d6_r50-d16_4xb2-80k_cityscapes-512x1024.py | 11 + ...-d6_r50-d16_4xb2-80k_cityscapes-769x769.py | 13 + ...6_r50b-d16_4xb2-80k_cityscapes-512x1024.py | 2 + ...d6_r50b-d16_4xb2-80k_cityscapes-769x769.py | 2 + ...cn_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...fcn_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...cn_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...fcn_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...101-d8_4xb2-amp-80k_cityscapes-512x1024.py | 6 + .../fcn_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + .../fcn_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...r101-d8_4xb4-40k_pascal-context-480x480.py | 2 + ...1-d8_4xb4-40k_pascal-context-59-480x480.py | 2 + .../fcn_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../fcn_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...r101-d8_4xb4-80k_pascal-context-480x480.py | 2 + ...1-d8_4xb4-80k_pascal-context-59-480x480.py | 2 + ...n_r101b-d8_4xb2-80k_cityscapes-512x1024.py | 4 + ...cn_r101b-d8_4xb2-80k_cityscapes-769x769.py | 4 + ...fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py | 9 + .../fcn_r18-d8_4xb2-80k_cityscapes-769x769.py | 9 + ...cn_r18b-d8_4xb2-80k_cityscapes-512x1024.py | 9 + ...fcn_r18b-d8_4xb2-80k_cityscapes-769x769.py | 9 + ...fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + .../fcn_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + .../fcn_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../fcn_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + .../fcn_r50-d8_4xb4-20k_voc12aug-512x512.py | 10 + ..._r50-d8_4xb4-40k_pascal-context-480x480.py | 13 + ...0-d8_4xb4-40k_pascal-context-59-480x480.py | 14 + .../fcn_r50-d8_4xb4-40k_voc12aug-512x512.py | 10 + .../fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + ..._r50-d8_4xb4-80k_pascal-context-480x480.py | 13 + ...0-d8_4xb4-80k_pascal-context-59-480x480.py | 14 + ...cn_r50b-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...fcn_r50b-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../configs/fcn/metafile.yaml | 997 ++++++ ..._my_dataset_model-512x1024-no_testslide.py | 112 + ...0_my_dataset_model-512x512-no_testslide.py | 112 + ...4000_my_dataset_model-769x769-testslide.py | 114 + ..._my_dataset_model-512x1024-no_testslide.py | 112 + Seg_All_In_One_MMSeg/configs/gcnet/README.md | 68 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../gcnet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...gcnet_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...gcnet_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../gcnet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...cnet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...cnet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../gcnet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + .../gcnet_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + .../gcnet_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + .../gcnet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../configs/gcnet/metafile.yaml | 391 +++ ...000_my_dataset_model-512x1024-testslide.py | 105 + Seg_All_In_One_MMSeg/configs/hrnet/README.md | 122 + .../fcn_hr18_4xb2-160k_cityscapes-512x1024.py | 7 + .../fcn_hr18_4xb2-40k_cityscapes-512x1024.py | 7 + .../fcn_hr18_4xb2-80k_cityscapes-512x1024.py | 7 + .../fcn_hr18_4xb4-160k_ade20k-512x512.py | 8 + .../fcn_hr18_4xb4-20k_voc12aug-512x512.py | 8 + ...cn_hr18_4xb4-40k_pascal-context-480x480.py | 12 + ...hr18_4xb4-40k_pascal-context-59-480x480.py | 12 + .../fcn_hr18_4xb4-40k_voc12aug-512x512.py | 8 + .../hrnet/fcn_hr18_4xb4-80k_ade20k-512x512.py | 8 + .../hrnet/fcn_hr18_4xb4-80k_isaid-896x896.py | 8 + .../hrnet/fcn_hr18_4xb4-80k_loveda-512x512.py | 8 + ...cn_hr18_4xb4-80k_pascal-context-480x480.py | 12 + ...hr18_4xb4-80k_pascal-context-59-480x480.py | 12 + .../fcn_hr18_4xb4-80k_potsdam-512x512.py | 8 + .../fcn_hr18_4xb4-80k_vaihingen-512x512.py | 8 + ...fcn_hr18s_4xb2-160k_cityscapes-512x1024.py | 9 + .../fcn_hr18s_4xb2-40k_cityscapes-512x1024.py | 9 + .../fcn_hr18s_4xb2-80k_cityscapes-512x1024.py | 9 + .../fcn_hr18s_4xb4-160k_ade20k-512x512.py | 9 + .../fcn_hr18s_4xb4-20k_voc12aug-512x512.py | 9 + ...n_hr18s_4xb4-40k_pascal-context-480x480.py | 9 + ...r18s_4xb4-40k_pascal-context-59-480x480.py | 9 + .../fcn_hr18s_4xb4-40k_voc12aug-512x512.py | 9 + .../fcn_hr18s_4xb4-80k_ade20k-512x512.py | 9 + .../hrnet/fcn_hr18s_4xb4-80k_isaid-896x896.py | 9 + .../fcn_hr18s_4xb4-80k_loveda-512x512.py | 9 + ...n_hr18s_4xb4-80k_pascal-context-480x480.py | 9 + ...r18s_4xb4-80k_pascal-context-59-480x480.py | 9 + .../fcn_hr18s_4xb4-80k_potsdam-512x512.py | 9 + .../fcn_hr18s_4xb4-80k_vaihingen-512x512.py | 9 + .../fcn_hr48_4xb2-160k_cityscapes-512x1024.py | 10 + .../fcn_hr48_4xb2-40k_cityscapes-512x1024.py | 10 + .../fcn_hr48_4xb2-80k_cityscapes-512x1024.py | 10 + .../fcn_hr48_4xb4-160k_ade20k-512x512.py | 10 + .../fcn_hr48_4xb4-20k_voc12aug-512x512.py | 10 + ...cn_hr48_4xb4-40k_pascal-context-480x480.py | 10 + ...hr48_4xb4-40k_pascal-context-59-480x480.py | 10 + .../fcn_hr48_4xb4-40k_voc12aug-512x512.py | 10 + .../hrnet/fcn_hr48_4xb4-80k_ade20k-512x512.py | 10 + .../hrnet/fcn_hr48_4xb4-80k_isaid-896x896.py | 10 + .../hrnet/fcn_hr48_4xb4-80k_loveda-512x512.py | 10 + ...cn_hr48_4xb4-80k_pascal-context-480x480.py | 10 + ...hr48_4xb4-80k_pascal-context-59-480x480.py | 10 + .../fcn_hr48_4xb4-80k_potsdam-512x512.py | 10 + .../fcn_hr48_4xb4-80k_vaihingen-512x512.py | 10 + .../configs/hrnet/metafile.yaml | 874 +++++ ...000_my_dataset_model-512x1024-testslide.py | 118 + ...000_my_dataset_model-512x1024-testslide.py | 122 + ...000_my_dataset_model-512x1024-testslide.py | 113 + ...4000_my_dataset_model-769x769-testslide.py | 113 + Seg_All_In_One_MMSeg/configs/icnet/README.md | 56 + ...8-in1k-pre_4xb2-160k_cityscapes-832x832.py | 7 + ...d8-in1k-pre_4xb2-80k_cityscapes-832x832.py | 7 + ...et_r101-d8_4xb2-160k_cityscapes-832x832.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-832x832.py | 2 + ...8-in1k-pre_4xb2-160k_cityscapes-832x832.py | 8 + ...d8-in1k-pre_4xb2-80k_cityscapes-832x832.py | 8 + ...net_r18-d8_4xb2-160k_cityscapes-832x832.py | 3 + ...cnet_r18-d8_4xb2-80k_cityscapes-832x832.py | 3 + ...8-in1k-pre_4xb2-160k_cityscapes-832x832.py | 6 + ...d8-in1k-pre_4xb2-80k_cityscapes-832x832.py | 6 + ...net_r50-d8_4xb2-160k_cityscapes-832x832.py | 8 + ...cnet_r50-d8_4xb2-80k_cityscapes-832x832.py | 8 + .../configs/icnet/metafile.yaml | 298 ++ ...dataset_autolaparo-512x512-no_testslide.py | 144 + ...ataset_cholecseg8k-512x512-no_testslide.py | 144 + ...licdataset_dresden-512x512-no_testslide.py | 144 + ...taset_endovis_2017-512x512-no_testslide.py | 144 + ...taset_endovis_2018-512x512-no_testslide.py | 144 + ...0_my_dataset_model-832x832-no_testslide.py | 138 + ...4000_my_dataset_model-832x832-testslide.py | 146 + ...dataset_autolaparo-512x512-no_testslide.py | 144 + ...ataset_cholecseg8k-512x512-no_testslide.py | 144 + ...licdataset_dresden-512x512-no_testslide.py | 144 + ...taset_endovis_2017-512x512-no_testslide.py | 144 + ...taset_endovis_2018-512x512-no_testslide.py | 144 + Seg_All_In_One_MMSeg/configs/isanet/README.md | 80 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...isanet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...sanet_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...sanet_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../isanet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...anet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...anet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../isanet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + ...isanet_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + ...isanet_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + .../isanet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + .../configs/isanet/metafile.yaml | 399 +++ ...4000_my_dataset_model-512x512-testslide.py | 105 + Seg_All_In_One_MMSeg/configs/knet/README.md | 52 + ...deeplabv3_8xb2-adamw-80k_ade20k-512x512.py | 111 + ...50-d8_fcn_8xb2-adamw-80k_ade20k-512x512.py | 112 + ...d8_pspnet_8xb2-adamw-80k_ade20k-512x512.py | 110 + ...8_upernet_8xb2-adamw-80k_ade20k-512x512.py | 111 + ...l_upernet_8xb2-adamw-80k_ade20k-512x512.py | 21 + ...l_upernet_8xb2-adamw-80k_ade20k-640x640.py | 57 + ...t_upernet_8xb2-adamw-80k_ade20k-512x512.py | 63 + .../configs/knet/metafile.yaml | 188 ++ Seg_All_In_One_MMSeg/configs/mae/README.md | 82 + ...upernet_8xb2-amp-160k_ade20k-512x512-ms.py | 16 + ...se_upernet_8xb2-amp-160k_ade20k-512x512.py | 53 + .../configs/mae/metafile.yaml | 25 + ...4000_my_dataset_model-512x512-testslide.py | 130 + ...4000_my_dataset_model-512x512-testslide.py | 137 + .../configs/mask2former/README.md | 74 + ...sk2former_r101_8xb2-160k_ade20k-512x512.py | 7 + ...ormer_r101_8xb2-90k_cityscapes-512x1024.py | 7 + ...ask2former_r50_8xb2-160k_ade20k-512x512.py | 200 ++ ...former_r50_8xb2-90k_cityscapes-512x1024.py | 197 ++ ...1k-384x384-pre_8xb2-160k_ade20k-640x640.py | 229 ++ ...2k-384x384-pre_8xb2-160k_ade20k-640x640.py | 5 + ...84x384-pre_8xb2-90k_cityscapes-512x1024.py | 42 + ...2k-384x384-pre_8xb2-160k_ade20k-640x640.py | 9 + ...84x384-pre_8xb2-90k_cityscapes-512x1024.py | 42 + ...2former_swin-s_8xb2-160k_ade20k-512x512.py | 37 + ...mer_swin-s_8xb2-90k_cityscapes-512x1024.py | 37 + ...2former_swin-t_8xb2-160k_ade20k-512x512.py | 52 + ...mer_swin-t_8xb2-90k_cityscapes-512x1024.py | 52 + .../configs/mask2former/metafile.yaml | 314 ++ .../configs/maskformer/README.md | 62 + ...ormer_r101-d32_8xb2-160k_ade20k-512x512.py | 7 + ...former_r50-d32_8xb2-160k_ade20k-512x512.py | 141 + ...swin-s_upernet_8xb2-160k_ade20k-512x512.py | 79 + ...swin-t_upernet_8xb2-160k_ade20k-512x512.py | 81 + .../configs/maskformer/metafile.yaml | 111 + .../configs/mobilenet_v2/README.md | 56 + .../configs/mobilenet_v2/metafile.yaml | 186 + ..._deeplabv3_4xb2-80k_cityscapes-512x1024.py | 13 + ...2-d8_deeplabv3_4xb4-160k_ade20k-512x512.py | 13 + ...plabv3plus_4xb2-80k_cityscapes-512x1024.py | 15 + ..._deeplabv3plus_4xb4-160k_ade20k-512x512.py | 13 + ...-v2-d8_fcn_4xb2-80k_cityscapes-512x1024.py | 13 + ...enet-v2-d8_fcn_4xb4-160k_ade20k-512x512.py | 13 + ...-d8_pspnet_4xb2-80k_cityscapes-512x1024.py | 13 + ...t-v2-d8_pspnet_4xb4-160k_ade20k-512x512.py | 13 + .../configs/mobilenet_v3/README.md | 50 + .../configs/mobilenet_v3/metafile.yaml | 109 + ...-s_lraspp_4xb4-320k_cityscapes-512x1024.py | 23 + ...-s_lraspp_4xb4-320k_cityscapes-512x1024.py | 22 + ...ch_lraspp_4xb4-320k_cityscapes-512x1024.py | 13 + ...d8_lraspp_4xb4-320k_cityscapes-512x1024.py | 16 + ...heck10_publicdataset_autolaparo-512x512.py | 160 + ...eck10_publicdataset_cholecseg8k-512x512.py | 158 + ...1_check10_publicdataset_dresden-512x512.py | 157 + ...ck10_publicdataset_endovis_2017-512x512.py | 157 + ...ck10_publicdataset_endovis_2018-512x512.py | 157 + ...heck10_publicdataset_autolaparo-512x512.py | 160 + ...eck10_publicdataset_cholecseg8k-512x512.py | 158 + ...1_check10_publicdataset_dresden-512x512.py | 157 + ...ck10_publicdataset_endovis_2017-512x512.py | 157 + ...ck10_publicdataset_endovis_2018-512x512.py | 157 + ...heck10_publicdataset_autolaparo-512x512.py | 160 + ...eck10_publicdataset_cholecseg8k-512x512.py | 158 + ...1_check10_publicdataset_dresden-512x512.py | 157 + ...ck10_publicdataset_endovis_2017-512x512.py | 157 + ...ck10_publicdataset_endovis_2018-512x512.py | 157 + .../configs/nonlocal_net/README.md | 68 + .../configs/nonlocal_net/metafile.yaml | 387 +++ ...al_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...cal_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...al_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...cal_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...nlocal_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...local_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...local_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + ...onlocal_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...cal_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...ocal_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...cal_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...ocal_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + ...onlocal_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + ...nlocal_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + ...nlocal_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + ...nonlocal_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + Seg_All_In_One_MMSeg/configs/ocrnet/README.md | 89 + .../configs/ocrnet/metafile.yaml | 577 ++++ ...rnet_hr18_4xb2-160k_cityscapes-512x1024.py | 7 + ...crnet_hr18_4xb2-40k_cityscapes-512x1024.py | 7 + ...crnet_hr18_4xb2-80k_cityscapes-512x1024.py | 7 + .../ocrnet_hr18_4xb4-160k_ade20k-512x512.py | 39 + .../ocrnet_hr18_4xb4-20k_voc12aug-512x512.py | 40 + .../ocrnet_hr18_4xb4-40k_voc12aug-512x512.py | 40 + .../ocrnet_hr18_4xb4-80k_ade20k-512x512.py | 39 + ...net_hr18s_4xb2-160k_cityscapes-512x1024.py | 9 + ...rnet_hr18s_4xb2-40k_cityscapes-512x1024.py | 9 + ...rnet_hr18s_4xb2-80k_cityscapes-512x1024.py | 9 + .../ocrnet_hr18s_4xb4-160k_ade20k-512x512.py | 9 + .../ocrnet_hr18s_4xb4-20k_voc12aug-512x512.py | 9 + .../ocrnet_hr18s_4xb4-40k_voc12aug-512x512.py | 9 + .../ocrnet_hr18s_4xb4-80k_ade20k-512x512.py | 9 + ...rnet_hr48_4xb2-160k_cityscapes-512x1024.py | 39 + ...crnet_hr48_4xb2-40k_cityscapes-512x1024.py | 39 + ...crnet_hr48_4xb2-80k_cityscapes-512x1024.py | 39 + .../ocrnet_hr48_4xb4-160k_ade20k-512x512.py | 39 + .../ocrnet_hr48_4xb4-20k_voc12aug-512x512.py | 39 + .../ocrnet_hr48_4xb4-40k_voc12aug-512x512.py | 39 + .../ocrnet_hr48_4xb4-80k_ade20k-512x512.py | 39 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 10 + ...et_r101-d8_8xb2-40k_cityscapes-512x1024.py | 21 + ...et_r101-d8_8xb2-80k_cityscapes-512x1024.py | 21 + Seg_All_In_One_MMSeg/configs/pidnet/README.md | 50 + .../configs/pidnet/metafile.yaml | 85 + ...heck10_publicdataset_autolaparo-512x512.py | 162 + ...eck10_publicdataset_cholecseg8k-512x512.py | 162 + ...1_check10_publicdataset_dresden-512x512.py | 162 + ...ck10_publicdataset_endovis_2017-512x512.py | 162 + ...ck10_publicdataset_endovis_2018-512x512.py | 162 + ...heck10_publicdataset_autolaparo-512x512.py | 162 + ...eck10_publicdataset_cholecseg8k-512x512.py | 162 + ...1_check10_publicdataset_dresden-512x512.py | 162 + ...ck10_publicdataset_endovis_2017-512x512.py | 162 + ...ck10_publicdataset_endovis_2018-512x512.py | 162 + ...heck10_publicdataset_autolaparo-512x512.py | 162 + ...eck10_publicdataset_cholecseg8k-512x512.py | 162 + ...1_check10_publicdataset_dresden-512x512.py | 162 + ...ck10_publicdataset_endovis_2017-512x512.py | 162 + ...ck10_publicdataset_endovis_2018-512x512.py | 162 + ...pidnet-l_2xb6-120k_1024x1024-cityscapes.py | 10 + ...pidnet-m_2xb6-120k_1024x1024-cityscapes.py | 5 + ...pidnet-s_2xb6-120k_1024x1024-cityscapes.py | 113 + .../configs/point_rend/README.md | 51 + .../configs/point_rend/metafile.yaml | 110 + ...trend_r101_4xb2-80k_cityscapes-512x1024.py | 2 + ...pointrend_r101_4xb4-160k_ade20k-512x512.py | 2 + ...ntrend_r50_4xb2-80k_cityscapes-512x1024.py | 18 + .../pointrend_r50_4xb4-160k_ade20k-512x512.py | 46 + .../configs/poolformer/README.md | 65 + ..._poolformer_m36_8xb4-40k_ade20k-512x512.py | 11 + ..._poolformer_m48_8xb4-40k_ade20k-512x512.py | 11 + ..._poolformer_s12_8xb4-40k_ade20k-512x512.py | 91 + ..._poolformer_s24_8xb4-40k_ade20k-512x512.py | 9 + ...n_poolformer_s36_8x4_512x512_40k_ade20k.py | 10 + .../configs/poolformer/metafile.yaml | 116 + Seg_All_In_One_MMSeg/configs/psanet/README.md | 68 + .../configs/psanet/metafile.yaml | 391 +++ ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...psanet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...sanet_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...sanet_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../psanet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...anet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...anet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../psanet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + ...psanet_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + ...psanet_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + .../psanet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + Seg_All_In_One_MMSeg/configs/pspnet/README.md | 182 + .../configs/pspnet/metafile.yaml | 1303 +++++++ ...heck10_publicdataset_autolaparo-512x512.py | 20 + ...eck10_publicdataset_cholecseg8k-512x512.py | 20 + ...1_check10_publicdataset_dresden-512x512.py | 20 + ...ck10_publicdataset_endovis_2017-512x512.py | 20 + ...ck10_publicdataset_endovis_2018-512x512.py | 20 + ...heck10_publicdataset_autolaparo-512x512.py | 22 + ...eck10_publicdataset_cholecseg8k-512x512.py | 22 + ...1_check10_publicdataset_dresden-512x512.py | 22 + ...ck10_publicdataset_endovis_2017-512x512.py | 22 + ...ck10_publicdataset_endovis_2018-512x512.py | 22 + ...et_r101-d8_4xb2-40k_cityscapes-512x1024.py | 2 + ...tyscapes-512x1024_dark-zurich-1920x1080.py | 2 + ...scapes-512x1024_night-driving-1920x1080.py | 2 + ...net_r101-d8_4xb2-40k_cityscapes-769x769.py | 2 + ...et_r101-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r101-d8_4xb2-80k_cityscapes-769x769.py | 2 + ...101-d8_4xb2-amp-80k_cityscapes-512x1024.py | 6 + ...pspnet_r101-d8_4xb4-160k_ade20k-512x512.py | 2 + ...101-d8_4xb4-160k_coco-stuff164k-512x512.py | 2 + ..._r101-d8_4xb4-20k_coco-stuff10k-512x512.py | 2 + ...spnet_r101-d8_4xb4-20k_voc12aug-512x512.py | 2 + ...101-d8_4xb4-320k_coco-stuff164k-512x512.py | 2 + ..._r101-d8_4xb4-40k_coco-stuff10k-512x512.py | 2 + ...r101-d8_4xb4-40k_pascal-context-480x480.py | 2 + ...1-d8_4xb4-40k_pascal-context-59-480x480.py | 2 + ...spnet_r101-d8_4xb4-40k_voc12aug-512x512.py | 2 + .../pspnet_r101-d8_4xb4-80k_ade20k-512x512.py | 2 + ...r101-d8_4xb4-80k_coco-stuff164k-512x512.py | 2 + .../pspnet_r101-d8_4xb4-80k_loveda-512x512.py | 2 + ...r101-d8_4xb4-80k_pascal-context-480x480.py | 2 + ...1-d8_4xb4-80k_pascal-context-59-480x480.py | 2 + ...pspnet_r101-d8_4xb4-80k_potsdam-512x512.py | 2 + ...pnet_r101-d8_4xb4-80k_vaihingen-512x512.py | 2 + ...t_r101b-d8_4xb2-80k_cityscapes-512x1024.py | 4 + ...tyscapes-512x1024_dark-zurich-1920x1080.py | 4 + ...scapes-512x1024_night-driving-1920x1080.py | 4 + ...et_r101b-d8_4xb2-80k_cityscapes-769x769.py | 4 + ...net_r18-d8_4xb2-80k_cityscapes-512x1024.py | 9 + ...pnet_r18-d8_4xb2-80k_cityscapes-769x769.py | 9 + .../pspnet_r18-d8_4xb4-80k_isaid-896x896.py | 9 + .../pspnet_r18-d8_4xb4-80k_loveda-512x512.py | 9 + .../pspnet_r18-d8_4xb4-80k_potsdam-512x512.py | 9 + ...spnet_r18-d8_4xb4-80k_vaihingen-512x512.py | 9 + ...et_r18b-d8_4xb2-80k_cityscapes-512x1024.py | 9 + ...net_r18b-d8_4xb2-80k_cityscapes-769x769.py | 9 + ...et_r50-d32_4xb2-80k_cityscapes-512x1024.py | 9 + ..._rsb_4xb2-adamw-80k_cityscapes-512x1024.py | 35 + ...-rsb_4xb2-adamw-80k_cityscapes-512x1024.py | 33 + ...net_r50-d8_4xb2-40k_cityscapes-512x1024.py | 7 + ...tyscapes-512x1024_dark-zurich-1920x1080.py | 24 + ...scapes-512x1024_night-driving-1920x1080.py | 25 + ...pnet_r50-d8_4xb2-40k_cityscapes-769x769.py | 12 + ...net_r50-d8_4xb2-80k_cityscapes-512x1024.py | 7 + ...tyscapes-512x1024_dark-zurich-1920x1080.py | 25 + ...scapes-512x1024_night-driving-1920x1080.py | 25 + ...pnet_r50-d8_4xb2-80k_cityscapes-769x769.py | 12 + .../pspnet_r50-d8_4xb4-160k_ade20k-512x512.py | 10 + ...r50-d8_4xb4-160k_coco-stuff164k-512x512.py | 11 + ...t_r50-d8_4xb4-20k_coco-stuff10k-512x512.py | 10 + ...pspnet_r50-d8_4xb4-20k_voc12aug-512x512.py | 11 + ...r50-d8_4xb4-320k_coco-stuff164k-512x512.py | 11 + ...t_r50-d8_4xb4-40k_coco-stuff10k-512x512.py | 10 + ..._r50-d8_4xb4-40k_pascal-context-480x480.py | 14 + ...0-d8_4xb4-40k_pascal-context-59-480x480.py | 14 + ...pspnet_r50-d8_4xb4-40k_voc12aug-512x512.py | 11 + .../pspnet_r50-d8_4xb4-80k_ade20k-512x512.py | 10 + ..._r50-d8_4xb4-80k_coco-stuff164k-512x512.py | 11 + .../pspnet_r50-d8_4xb4-80k_isaid-896x896.py | 10 + .../pspnet_r50-d8_4xb4-80k_loveda-512x512.py | 10 + ..._r50-d8_4xb4-80k_pascal-context-480x480.py | 14 + ...0-d8_4xb4-80k_pascal-context-59-480x480.py | 14 + .../pspnet_r50-d8_4xb4-80k_potsdam-512x512.py | 10 + ...spnet_r50-d8_4xb4-80k_vaihingen-512x512.py | 10 + ...t_r50b-d32_4xb2-80k_cityscapes-512x1024.py | 10 + ...et_r50b-d8_4xb2-80k_cityscapes-512x1024.py | 2 + ...net_r50b-d8_4xb2-80k_cityscapes-769x769.py | 2 + .../configs/resnest/README.md | 54 + .../configs/resnest/metafile.yaml | 193 ++ ..._deeplabv3_4xb2-80k_cityscapes-512x1024.py | 9 + ...1-d8_deeplabv3_4xb4-160k_ade20k-512x512.py | 9 + ...plabv3plus_4xb2-80k_cityscapes-512x1024.py | 9 + ..._deeplabv3plus_4xb4-160k_ade20k-512x512.py | 9 + ...101-d8_fcn_4xb2-80k_cityscapes-512x1024.py | 9 + ...st_s101-d8_fcn_4xb4-160k_ade20k-512x512.py | 9 + ...1-d8_pspnet_4xb2-80k_cityscapes512x1024.py | 9 + ...s101-d8_pspnet_4xb4-160k_ade20k-512x512.py | 9 + Seg_All_In_One_MMSeg/configs/san/README.md | 47 + .../configs/san/metafile.yaml | 61 + .../san/san-vit-b16_coco-stuff164k-640x640.py | 82 + .../san/san-vit-b16_pascal_context-640x640.py | 56 + .../san/san-vit-b16_voc12aug-640x640.py | 65 + .../san/san-vit-l14_coco-stuff164k-640x640.py | 36 + .../san/san-vit-l14_pascal_context-640x640.py | 32 + .../san/san-vit-l14_voc12aug-640x640.py | 32 + .../configs/segformer/README.md | 101 + .../configs/segformer/metafile.yaml | 340 ++ ...r_mit-b0_8xb1-160k_cityscapes-1024x1024.py | 41 + ...gformer_mit-b0_8xb2-160k_ade20k-512x512.py | 39 + ...r_mit-b1_8xb1-160k_cityscapes-1024x1024.py | 9 + ...gformer_mit-b1_8xb2-160k_ade20k-512x512.py | 12 + ...r_mit-b2_8xb1-160k_cityscapes-1024x1024.py | 10 + ...gformer_mit-b2_8xb2-160k_ade20k-512x512.py | 12 + ...r_mit-b3_8xb1-160k_cityscapes-1024x1024.py | 10 + ...gformer_mit-b3_8xb2-160k_ade20k-512x512.py | 12 + ...r_mit-b4_8xb1-160k_cityscapes-1024x1024.py | 10 + ...gformer_mit-b4_8xb2-160k_ade20k-512x512.py | 12 + ...r_mit-b5_8xb1-160k_cityscapes-1024x1024.py | 10 + ...gformer_mit-b5_8xb2-160k_ade20k-512x512.py | 12 + ...gformer_mit-b5_8xb2-160k_ade20k-640x640.py | 41 + .../configs/segmenter/README.md | 76 + .../configs/segmenter/metafile.yaml | 138 + ...ter_vit-b_mask_8xb1-160k_ade20k-512x512.py | 14 + ...ter_vit-l_mask_8xb1-160k_ade20k-512x512.py | 32 + ...nter_vit-s_fcn_8xb1-160k_ade20k-512x512.py | 14 + ...ter_vit-s_mask_8xb1-160k_ade20k-512x512.py | 36 + ...ter_vit-t_mask_8xb1-160k_ade20k-512x512.py | 26 + .../configs/segnext/README.md | 63 + .../configs/segnext/metafile.yaml | 109 + ...mscan-b_1xb16-adamw-160k_ade20k-512x512.py | 28 + ...mscan-l_1xb16-adamw-160k_ade20k-512x512.py | 27 + ...mscan-s_1xb16-adamw-160k_ade20k-512x512.py | 27 + ...mscan-t_1xb16-adamw-160k_ade20k-512x512.py | 84 + .../configs/sem_fpn/README.md | 51 + .../fpn_r101_4xb2-80k_cityscapes-512x1024.py | 2 + .../fpn_r101_4xb4-160k_ade20k-512x512.py | 5 + .../fpn_r50_4xb2-80k_cityscapes-512x1024.py | 7 + .../fpn_r50_4xb4-160k_ade20k-512x512.py | 8 + .../configs/sem_fpn/metafile.yaml | 110 + Seg_All_In_One_MMSeg/configs/setr/README.md | 74 + .../configs/setr/metafile.yaml | 197 ++ ...setr_vit-l-mla_8xb1-160k_ade20k-512x512.py | 90 + ...r_vit-l_mla_8xb1-80k_cityscapes-768x768.py | 23 + ...setr_vit-l_mla_8xb2-160k_ade20k-512x512.py | 6 + ...vit-l_naive_8xb1-80k_cityscapes-768x768.py | 24 + ...tr_vit-l_naive_8xb2-160k_ade20k-512x512.py | 72 + ...r_vit-l_pup_8xb1-80k_cityscapes-768x768.py | 70 + ...setr_vit-l_pup_8xb2-160k_ade20k-512x512.py | 72 + Seg_All_In_One_MMSeg/configs/stdc/README.md | 73 + .../configs/stdc/metafile.yaml | 107 + ...heck10_publicdataset_autolaparo-512x512.py | 94 + ...eck10_publicdataset_cholecseg8k-512x512.py | 94 + ...1_check10_publicdataset_dresden-512x512.py | 94 + ...ck10_publicdataset_endovis_2017-512x512.py | 94 + ...ck10_publicdataset_endovis_2018-512x512.py | 94 + ...heck10_publicdataset_autolaparo-512x512.py | 94 + ...eck10_publicdataset_cholecseg8k-512x512.py | 94 + ...1_check10_publicdataset_dresden-512x512.py | 94 + ...ck10_publicdataset_endovis_2017-512x512.py | 94 + ...ck10_publicdataset_endovis_2018-512x512.py | 94 + .../stdc1_4xb12-80k_cityscapes-512x1024.py | 21 + ..._in1k-pre_4xb12-80k_cityscapes-512x1024.py | 6 + .../stdc2_4xb12-80k_cityscapes-512x1024.py | 2 + ..._in1k-pre_4xb12-80k_cityscapes-512x1024.py | 6 + Seg_All_In_One_MMSeg/configs/swin/README.md | 76 + .../configs/swin/metafile.yaml | 143 + ...84-pre_upernet_8xb2-160k_ade20k-512x512.py | 14 + ...84-pre_upernet_8xb2-160k_ade20k-512x512.py | 7 + ...1k-pre_upernet_8xb2-160k_ade20k-512x512.py | 12 + ...2k-pre_upernet_8xb2-160k_ade20k-512x512.py | 7 + ...84-pre_upernet_8xb2-160k_ade20k-512x512.py | 10 + ...2k-pre_upernet_8xb2-160k_ade20k-512x512.py | 15 + ...1k-pre_upernet_8xb2-160k_ade20k-512x512.py | 10 + ...1k-pre_upernet_8xb2-160k_ade20k-512x512.py | 52 + ...-window7_upernet_1xb8-20k_levir-256x256.py | 57 + Seg_All_In_One_MMSeg/configs/twins/README.md | 76 + .../configs/twins/metafile.yaml | 289 ++ ...t-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py | 8 + ...pvt-b_uperhead_8xb2-160k_ade20k-512x512.py | 13 + ...t-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py | 8 + ...pvt-l_uperhead_8xb2-160k_ade20k-512x512.py | 13 + ...t-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py | 12 + ...pvt-s_uperhead_8xb4-160k_ade20k-512x512.py | 31 + ...t-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py | 12 + ...svt-b_uperhead_8xb2-160k_ade20k-512x512.py | 12 + ...t-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py | 13 + ...svt-l_uperhead_8xb2-160k_ade20k-512x512.py | 13 + ...t-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py | 28 + ...svt-s_uperhead_8xb2-160k_ade20k-512x512.py | 49 + Seg_All_In_One_MMSeg/configs/unet/README.md | 92 + .../configs/unet/metafile.yaml | 642 ++++ ...4000_my_dataset_model-128x128-testslide.py | 107 + ...t-s5-d16_deeplabv3_4xb4-40k_drive-64x64.py | 9 + ...t-s5-d16_deeplabv3_4xb4-40k_hrf-256x256.py | 9 + ...s5-d16_deeplabv3_4xb4-40k_stare-128x128.py | 9 + ...4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py | 6 + ...v3_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py | 6 + ...v3_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py | 6 + ..._4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py | 6 + ...5-d16_fcn_4xb4-160k_cityscapes-512x1024.py | 16 + ...t-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py | 36 + ...t-s5-d16_fcn_4xb4-40k_chase-db1-128x128.py | 9 + .../unet-s5-d16_fcn_4xb4-40k_drive-64x64.py | 9 + .../unet-s5-d16_fcn_4xb4-40k_hrf-256x256.py | 9 + .../unet-s5-d16_fcn_4xb4-40k_stare-128x128.py | 9 + ...4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py | 6 + ...cn_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py | 6 + ...cn_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py | 6 + ..._4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py | 6 + ...5-d16_pspnet_4xb4-40k_chase-db1-128x128.py | 10 + ...unet-s5-d16_pspnet_4xb4-40k_drive-64x64.py | 9 + ...unet-s5-d16_pspnet_4xb4-40k_hrf-256x256.py | 9 + ...et-s5-d16_pspnet_4xb4-40k_stare-128x128.py | 9 + ...4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py | 6 + ...et_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py | 6 + ...et_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py | 6 + ..._4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py | 6 + ...16_deeplabv3_4xb4-40k_chase-db1-128x128.py | 10 + .../configs/upernet/README.md | 68 + .../configs/upernet/metafile.yaml | 391 +++ ...ernet_r101_4xb2-40k_cityscapes-512x1024.py | 2 + ...pernet_r101_4xb2-40k_cityscapes-769x769.py | 2 + ...ernet_r101_4xb2-80k_cityscapes-512x1024.py | 2 + ...pernet_r101_4xb2-80k_cityscapes-769x769.py | 2 + .../upernet_r101_4xb4-160k_ade20k-512x512.py | 2 + .../upernet_r101_4xb4-20k_voc12aug-512x512.py | 2 + .../upernet_r101_4xb4-40k_voc12aug-512x512.py | 2 + .../upernet_r101_4xb4-80k_ade20k-512x512.py | 2 + ...pernet_r18_4xb2-40k_cityscapes-512x1024.py | 6 + ...pernet_r18_4xb2-80k_cityscapes-512x1024.py | 6 + .../upernet_r18_4xb4-160k_ade20k-512x512.py | 9 + .../upernet_r18_4xb4-20k_voc12aug-512x512.py | 10 + .../upernet_r18_4xb4-40k_voc12aug-512x512.py | 10 + .../upernet_r18_4xb4-80k_ade20k-512x512.py | 9 + ...pernet_r50_4xb2-40k_cityscapes-512x1024.py | 7 + ...upernet_r50_4xb2-40k_cityscapes-769x769.py | 12 + ...pernet_r50_4xb2-80k_cityscapes-512x1024.py | 7 + ...upernet_r50_4xb2-80k_cityscapes-769x769.py | 12 + .../upernet_r50_4xb4-160k_ade20k-512x512.py | 10 + .../upernet_r50_4xb4-20k_voc12aug-512x512.py | 11 + .../upernet_r50_4xb4-40k_voc12aug-512x512.py | 11 + .../upernet_r50_4xb4-80k_ade20k-512x512.py | 10 + Seg_All_In_One_MMSeg/configs/vit/README.md | 70 + .../configs/vit/metafile.yaml | 265 ++ ...ln_mln_upernet_8xb2-160k_ade20k-512x512.py | 5 + ...16_mln_upernet_8xb2-160k_ade20k-512x512.py | 6 + ...it-b16_upernet_8xb2-160k_ade20k-512x512.py | 6 + ...eit-b16_upernet_8xb2-80k_ade20k-512x512.py | 6 + ...ln_mln_upernet_8xb2-160k_ade20k-512x512.py | 9 + ...16_mln_upernet_8xb2-160k_ade20k-512x512.py | 8 + ...it-s16_upernet_8xb2-160k_ade20k-512x512.py | 8 + ...eit-s16_upernet_8xb2-80k_ade20k-512x512.py | 8 + ...ln_mln_upernet_8xb2-160k_ade20k-512x512.py | 45 + ...16_mln_upernet_8xb2-160k_ade20k-512x512.py | 44 + ...b16_mln_upernet_8xb2-80k_ade20k-512x512.py | 44 + Seg_All_In_One_MMSeg/configs/vpd/README.md | 50 + .../configs/vpd/metafile.yaml | 56 + .../vpd/vpd_sd_4xb8-25k_nyu-480x480.py | 38 + .../vpd/vpd_sd_4xb8-25k_nyu-512x512.py | 37 + Seg_All_In_One_MMSeg/dataset-index.yml | 80 + .../demo/MMSegmentation_Tutorial.ipynb | 555 +++ .../demo/classroom__rgb_00283.jpg | Bin 0 -> 50269 bytes Seg_All_In_One_MMSeg/demo/demo.png | Bin 0 -> 307861 bytes Seg_All_In_One_MMSeg/demo/image_demo.py | 51 + .../demo/image_demo_with_inferencer.py | 54 + .../demo/inference_demo.ipynb | 120 + .../demo/rs_image_inference.py | 50 + Seg_All_In_One_MMSeg/demo/video_demo.py | 112 + Seg_All_In_One_MMSeg/docker/Dockerfile | 35 + Seg_All_In_One_MMSeg/docker/serve/Dockerfile | 51 + .../docker/serve/config.properties | 5 + .../docker/serve/entrypoint.sh | 12 + .../docs/en/.readthedocs.yaml | 17 + Seg_All_In_One_MMSeg/docs/en/Makefile | 20 + .../docs/en/_static/css/readthedocs.css | 6 + .../docs/en/_static/images/mmsegmentation.png | Bin 0 -> 44728 bytes .../docs/en/advanced_guides/add_datasets.md | 199 ++ .../docs/en/advanced_guides/add_metrics.md | 81 + .../docs/en/advanced_guides/add_models.md | 260 ++ .../docs/en/advanced_guides/add_transforms.md | 52 + .../en/advanced_guides/customize_runtime.md | 168 + .../docs/en/advanced_guides/data_flow.md | 87 + .../docs/en/advanced_guides/datasets.md | 386 +++ .../docs/en/advanced_guides/engine.md | 279 ++ .../docs/en/advanced_guides/evaluation.md | 155 + .../docs/en/advanced_guides/index.rst | 26 + .../docs/en/advanced_guides/models.md | 164 + .../docs/en/advanced_guides/structures.md | 104 + .../en/advanced_guides/training_tricks.md | 75 + .../docs/en/advanced_guides/transforms.md | 119 + Seg_All_In_One_MMSeg/docs/en/api.rst | 95 + Seg_All_In_One_MMSeg/docs/en/conf.py | 133 + Seg_All_In_One_MMSeg/docs/en/device/npu.md | 39 + Seg_All_In_One_MMSeg/docs/en/get_started.md | 211 ++ Seg_All_In_One_MMSeg/docs/en/index.rst | 63 + Seg_All_In_One_MMSeg/docs/en/make.bat | 35 + .../docs/en/migration/index.rst | 8 + .../docs/en/migration/interface.md | 525 +++ .../docs/en/migration/package.md | 113 + Seg_All_In_One_MMSeg/docs/en/model_zoo.md | 186 + .../docs/en/modelzoo_statistics.md | 102 + .../docs/en/notes/changelog.md | 506 +++ .../docs/en/notes/changelog_v0.x.md | 720 ++++ Seg_All_In_One_MMSeg/docs/en/notes/faq.md | 132 + Seg_All_In_One_MMSeg/docs/en/overview.md | 85 + Seg_All_In_One_MMSeg/docs/en/stat.py | 67 + .../docs/en/switch_language.md | 3 + .../docs/en/user_guides/1_config.md | 588 ++++ .../docs/en/user_guides/2_dataset_prepare.md | 806 +++++ .../docs/en/user_guides/3_inference.md | 244 ++ .../docs/en/user_guides/4_train_test.md | 315 ++ .../docs/en/user_guides/5_deployment.md | 255 ++ .../docs/en/user_guides/index.rst | 21 + .../docs/en/user_guides/useful_tools.md | 245 ++ .../docs/en/user_guides/visualization.md | 174 + .../user_guides/visualization_feature_map.md | 201 ++ .../docs/zh_cn/.readthedocs.yaml | 17 + Seg_All_In_One_MMSeg/docs/zh_cn/Makefile | 20 + .../docs/zh_cn/_static/css/readthedocs.css | 6 + .../zh_cn/_static/images/mmsegmentation.png | Bin 0 -> 44728 bytes .../zh_cn/advanced_guides/add_datasets.md | 199 ++ .../docs/zh_cn/advanced_guides/add_metrics.md | 81 + .../docs/zh_cn/advanced_guides/add_models.md | 260 ++ .../zh_cn/advanced_guides/add_transforms.md | 51 + .../advanced_guides/contribute_dataset.md | 461 +++ .../advanced_guides/customize_runtime.md | 162 + .../docs/zh_cn/advanced_guides/data_flow.md | 90 + .../docs/zh_cn/advanced_guides/datasets.md | 363 ++ .../docs/zh_cn/advanced_guides/engine.md | 281 ++ .../docs/zh_cn/advanced_guides/evaluation.md | 158 + .../docs/zh_cn/advanced_guides/index.rst | 26 + .../docs/zh_cn/advanced_guides/models.md | 177 + .../docs/zh_cn/advanced_guides/structures.md | 102 + .../zh_cn/advanced_guides/training_tricks.md | 74 + .../docs/zh_cn/advanced_guides/transforms.md | 119 + Seg_All_In_One_MMSeg/docs/zh_cn/api.rst | 95 + Seg_All_In_One_MMSeg/docs/zh_cn/conf.py | 133 + Seg_All_In_One_MMSeg/docs/zh_cn/device/npu.md | 39 + .../docs/zh_cn/get_started.md | 209 ++ .../docs/zh_cn/imgs/zhihu_qrcode.jpg | Bin 0 -> 397245 bytes Seg_All_In_One_MMSeg/docs/zh_cn/index.rst | 57 + Seg_All_In_One_MMSeg/docs/zh_cn/make.bat | 35 + .../docs/zh_cn/migration/index.rst | 8 + .../docs/zh_cn/migration/interface.md | 523 +++ .../docs/zh_cn/migration/package.md | 113 + Seg_All_In_One_MMSeg/docs/zh_cn/model_zoo.md | 152 + .../docs/zh_cn/modelzoo_statistics.md | 102 + Seg_All_In_One_MMSeg/docs/zh_cn/notes/faq.md | 125 + Seg_All_In_One_MMSeg/docs/zh_cn/overview.md | 75 + Seg_All_In_One_MMSeg/docs/zh_cn/stat.py | 67 + .../docs/zh_cn/switch_language.md | 3 + .../docs/zh_cn/user_guides/1_config.md | 577 ++++ .../zh_cn/user_guides/2_dataset_prepare.md | 802 +++++ .../docs/zh_cn/user_guides/3_inference.md | 244 ++ .../docs/zh_cn/user_guides/4_train_test.md | 317 ++ .../docs/zh_cn/user_guides/5_deployment.md | 243 ++ .../docs/zh_cn/user_guides/deploy_jetson.md | 372 ++ .../docs/zh_cn/user_guides/index.rst | 21 + .../docs/zh_cn/user_guides/useful_tools.md | 368 ++ .../docs/zh_cn/user_guides/visualization.md | 173 + .../user_guides/visualization_feature_map.md | 201 ++ Seg_All_In_One_MMSeg/mmseg/.mim/configs | 1 + .../mmseg/.mim/dataset-index.yml | 1 + .../mmseg/.mim/model-index.yml | 1 + Seg_All_In_One_MMSeg/mmseg/.mim/tools | 1 + Seg_All_In_One_MMSeg/mmseg/__init__.py | 73 + Seg_All_In_One_MMSeg/mmseg/apis/__init__.py | 9 + Seg_All_In_One_MMSeg/mmseg/apis/inference.py | 189 ++ .../mmseg/apis/mmseg_inferencer.py | 382 +++ .../mmseg/apis/remote_sense_inferencer.py | 279 ++ Seg_All_In_One_MMSeg/mmseg/apis/utils.py | 41 + .../mmseg/configs/_base_/datasets/loveda.py | 79 + .../mmseg/configs/_base_/datasets/potsdam.py | 81 + .../mmseg/configs/_base_/default_runtime.py | 22 + .../configs/_base_/schedules/schedule_160k.py | 43 + .../configs/_base_/schedules/schedule_20k.py | 36 + .../configs/_base_/schedules/schedule_240k.py | 34 + .../configs/_base_/schedules/schedule_25k.py | 43 + .../configs/_base_/schedules/schedule_300e.py | 56 + .../configs/_base_/schedules/schedule_320k.py | 36 + .../configs/_base_/schedules/schedule_40k.py | 34 + .../configs/_base_/schedules/schedule_80k.py | 42 + .../mmseg/datasets/__init__.py | 77 + Seg_All_In_One_MMSeg/mmseg/datasets/ade.py | 92 + .../mmseg/datasets/basesegdataset.py | 552 +++ .../mmseg/datasets/bdd100k.py | 30 + .../mmseg/datasets/chase_db1.py | 32 + .../mmseg/datasets/cityscapes.py | 30 + .../mmseg/datasets/coco_stuff.py | 99 + .../mmseg/datasets/dark_zurich.py | 15 + .../mmseg/datasets/dataset_wrappers.py | 136 + .../mmseg/datasets/decathlon.py | 96 + Seg_All_In_One_MMSeg/mmseg/datasets/drive.py | 32 + Seg_All_In_One_MMSeg/mmseg/datasets/dsdl.py | 116 + Seg_All_In_One_MMSeg/mmseg/datasets/hrf.py | 32 + .../mmseg/datasets/hsi_drive.py | 42 + Seg_All_In_One_MMSeg/mmseg/datasets/isaid.py | 39 + Seg_All_In_One_MMSeg/mmseg/datasets/isprs.py | 29 + Seg_All_In_One_MMSeg/mmseg/datasets/levir.py | 31 + Seg_All_In_One_MMSeg/mmseg/datasets/lip.py | 47 + Seg_All_In_One_MMSeg/mmseg/datasets/loveda.py | 29 + .../mmseg/datasets/mapillary.py | 176 + .../mmseg/datasets/my_dataset.py | 28 + .../mmseg/datasets/my_dataset_model.py | 28 + .../mmseg/datasets/night_driving.py | 15 + Seg_All_In_One_MMSeg/mmseg/datasets/nyu.py | 123 + .../mmseg/datasets/pascal_context.py | 116 + .../mmseg/datasets/potsdam.py | 29 + .../datasets/publicdataset_autolaparo.py | 28 + .../datasets/publicdataset_cholecseg8k.py | 28 + .../mmseg/datasets/publicdataset_dresden.py | 28 + .../datasets/publicdataset_endovis_2017.py | 28 + .../datasets/publicdataset_endovis_2018.py | 28 + Seg_All_In_One_MMSeg/mmseg/datasets/refuge.py | 28 + Seg_All_In_One_MMSeg/mmseg/datasets/stare.py | 32 + .../mmseg/datasets/synapse.py | 28 + .../mmseg/datasets/transforms/__init__.py | 30 + .../mmseg/datasets/transforms/formatting.py | 112 + .../mmseg/datasets/transforms/loading.py | 771 +++++ .../mmseg/datasets/transforms/transforms.py | 2537 ++++++++++++++ Seg_All_In_One_MMSeg/mmseg/datasets/voc.py | 40 + Seg_All_In_One_MMSeg/mmseg/engine/__init__.py | 12 + .../mmseg/engine/hooks/__init__.py | 4 + .../mmseg/engine/hooks/visualization_hook.py | 129 + .../mmseg/engine/optimizers/__init__.py | 9 + .../optimizers/force_default_constructor.py | 255 ++ .../layer_decay_optimizer_constructor.py | 207 ++ .../mmseg/engine/schedulers/__init__.py | 4 + .../engine/schedulers/poly_ratio_scheduler.py | 62 + .../mmseg/evaluation/__init__.py | 4 + .../mmseg/evaluation/metrics/__init__.py | 6 + .../mmseg/evaluation/metrics/citys_metric.py | 158 + .../mmseg/evaluation/metrics/depth_metric.py | 212 ++ .../mmseg/evaluation/metrics/iou_metric.py | 286 ++ Seg_All_In_One_MMSeg/mmseg/models/__init__.py | 16 + .../mmseg/models/assigners/__init__.py | 12 + .../mmseg/models/assigners/base_assigner.py | 18 + .../models/assigners/hungarian_assigner.py | 86 + .../mmseg/models/assigners/match_cost.py | 231 ++ .../mmseg/models/backbones/__init__.py | 40 + .../mmseg/models/backbones/beit.py | 554 +++ .../mmseg/models/backbones/bisenetv1.py | 332 ++ .../mmseg/models/backbones/bisenetv2.py | 622 ++++ .../mmseg/models/backbones/cgnet.py | 372 ++ .../mmseg/models/backbones/ddrnet.py | 222 ++ .../mmseg/models/backbones/en_bisenetv2.py | 418 +++ .../mmseg/models/backbones/erfnet.py | 329 ++ .../mmseg/models/backbones/fast_scnn.py | 408 +++ .../mmseg/models/backbones/hrnet.py | 642 ++++ .../mmseg/models/backbones/icnet.py | 166 + .../mmseg/models/backbones/mae.py | 260 ++ .../mmseg/models/backbones/mit.py | 450 +++ .../mmseg/models/backbones/mobilenet_v2.py | 197 ++ .../mmseg/models/backbones/mobilenet_v3.py | 267 ++ .../mmseg/models/backbones/mscan.py | 467 +++ .../mmseg/models/backbones/my_bisnetv2_A1.py | 629 ++++ .../models/backbones/my_bisnetv2_A1_add_A2.py | 591 ++++ .../mmseg/models/backbones/my_bisnetv2_A2.py | 597 ++++ .../mmseg/models/backbones/pidnet.py | 522 +++ .../mmseg/models/backbones/resnest.py | 318 ++ .../mmseg/models/backbones/resnet.py | 712 ++++ .../mmseg/models/backbones/resnext.py | 150 + .../mmseg/models/backbones/stdc.py | 422 +++ .../mmseg/models/backbones/swin.py | 757 +++++ .../mmseg/models/backbones/timm_backbone.py | 63 + .../mmseg/models/backbones/twins.py | 588 ++++ .../mmseg/models/backbones/unet.py | 436 +++ .../mmseg/models/backbones/vit.py | 501 +++ .../mmseg/models/backbones/vpd.py | 395 +++ Seg_All_In_One_MMSeg/mmseg/models/builder.py | 52 + .../mmseg/models/data_preprocessor.py | 151 + .../mmseg/models/decode_heads/__init__.py | 48 + .../mmseg/models/decode_heads/ann_head.py | 245 ++ .../mmseg/models/decode_heads/apc_head.py | 159 + .../mmseg/models/decode_heads/aspp_head.py | 122 + .../decode_heads/cascade_decode_head.py | 62 + .../mmseg/models/decode_heads/cc_head.py | 43 + .../mmseg/models/decode_heads/da_head.py | 184 + .../mmseg/models/decode_heads/ddr_head.py | 116 + .../mmseg/models/decode_heads/decode_head.py | 366 ++ .../mmseg/models/decode_heads/dm_head.py | 141 + .../mmseg/models/decode_heads/dnl_head.py | 137 + .../mmseg/models/decode_heads/dpt_head.py | 294 ++ .../mmseg/models/decode_heads/ema_head.py | 169 + .../mmseg/models/decode_heads/enc_head.py | 196 ++ .../mmseg/models/decode_heads/fcn_head.py | 96 + .../mmseg/models/decode_heads/fpn_head.py | 68 + .../mmseg/models/decode_heads/gc_head.py | 48 + .../mmseg/models/decode_heads/ham_head.py | 255 ++ .../mmseg/models/decode_heads/isa_head.py | 143 + .../mmseg/models/decode_heads/knet_head.py | 461 +++ .../mmseg/models/decode_heads/lraspp_head.py | 91 + .../models/decode_heads/mask2former_head.py | 163 + .../models/decode_heads/maskformer_head.py | 174 + .../mmseg/models/decode_heads/nl_head.py | 50 + .../mmseg/models/decode_heads/ocr_head.py | 127 + .../mmseg/models/decode_heads/pid_head.py | 183 + .../mmseg/models/decode_heads/point_head.py | 367 ++ .../mmseg/models/decode_heads/psa_head.py | 197 ++ .../mmseg/models/decode_heads/psp_head.py | 117 + .../mmseg/models/decode_heads/san_head.py | 736 ++++ .../models/decode_heads/segformer_head.py | 66 + .../decode_heads/segmenter_mask_head.py | 132 + .../models/decode_heads/sep_aspp_head.py | 102 + .../mmseg/models/decode_heads/sep_fcn_head.py | 60 + .../models/decode_heads/setr_mla_head.py | 62 + .../mmseg/models/decode_heads/setr_up_head.py | 81 + .../mmseg/models/decode_heads/stdc_head.py | 97 + .../mmseg/models/decode_heads/uper_head.py | 139 + .../models/decode_heads/vpd_depth_head.py | 253 ++ .../mmseg/models/losses/__init__.py | 21 + .../mmseg/models/losses/accuracy.py | 92 + .../mmseg/models/losses/boundary_loss.py | 62 + .../mmseg/models/losses/cross_entropy_loss.py | 311 ++ .../mmseg/models/losses/dice_loss.py | 242 ++ .../mmseg/models/losses/focal_loss.py | 337 ++ .../models/losses/huasdorff_distance_loss.py | 160 + .../mmseg/models/losses/kldiv_loss.py | 99 + .../mmseg/models/losses/lovasz_loss.py | 323 ++ .../models/losses/ohem_cross_entropy_loss.py | 94 + .../mmseg/models/losses/silog_loss.py | 122 + .../mmseg/models/losses/tversky_loss.py | 137 + .../mmseg/models/losses/utils.py | 129 + .../mmseg/models/necks/__init__.py | 11 + .../mmseg/models/necks/featurepyramid.py | 67 + .../mmseg/models/necks/fpn.py | 212 ++ .../mmseg/models/necks/ic_neck.py | 148 + .../mmseg/models/necks/jpu.py | 131 + .../mmseg/models/necks/mla_neck.py | 118 + .../mmseg/models/necks/multilevel_neck.py | 79 + .../mmseg/models/segmentors/__init__.py | 12 + .../mmseg/models/segmentors/base.py | 200 ++ .../segmentors/cascade_encoder_decoder.py | 138 + .../models/segmentors/depth_estimator.py | 392 +++ .../models/segmentors/encoder_decoder.py | 364 ++ .../segmentors/multimodal_encoder_decoder.py | 350 ++ .../mmseg/models/segmentors/seg_tta.py | 47 + .../mmseg/models/text_encoder/__init__.py | 4 + .../models/text_encoder/clip_text_encoder.py | 229 ++ .../mmseg/models/utils/__init__.py | 27 + .../mmseg/models/utils/basic_block.py | 143 + .../mmseg/models/utils/embed.py | 330 ++ .../mmseg/models/utils/encoding.py | 75 + .../mmseg/models/utils/inverted_residual.py | 213 ++ .../mmseg/models/utils/make_divisible.py | 28 + .../mmseg/models/utils/point_sample.py | 88 + .../mmseg/models/utils/ppm.py | 193 ++ .../mmseg/models/utils/res_layer.py | 96 + .../mmseg/models/utils/san_layers.py | 418 +++ .../mmseg/models/utils/se_layer.py | 58 + .../models/utils/self_attention_block.py | 161 + .../mmseg/models/utils/shape_convert.py | 107 + .../mmseg/models/utils/up_conv_block.py | 102 + .../mmseg/models/utils/wrappers.py | 51 + .../mmseg/registry/__init__.py | 15 + .../mmseg/registry/registry.py | 118 + .../mmseg/structures/__init__.py | 8 + .../mmseg/structures/sampler/__init__.py | 6 + .../structures/sampler/base_pixel_sampler.py | 13 + .../mmseg/structures/sampler/builder.py | 14 + .../structures/sampler/ohem_pixel_sampler.py | 85 + .../mmseg/structures/seg_data_sample.py | 92 + Seg_All_In_One_MMSeg/mmseg/utils/__init__.py | 70 + .../mmseg/utils/bpe_simple_vocab_16e6.txt.gz | Bin 0 -> 1356917 bytes .../mmseg/utils/class_names-ori.py | 548 +++ .../mmseg/utils/class_names.py | 614 ++++ .../mmseg/utils/collect_env.py | 18 + .../mmseg/utils/get_templates.py | 109 + Seg_All_In_One_MMSeg/mmseg/utils/io.py | 42 + .../mmseg/utils/mask_classification.py | 205 ++ Seg_All_In_One_MMSeg/mmseg/utils/misc.py | 128 + Seg_All_In_One_MMSeg/mmseg/utils/set_env.py | 40 + Seg_All_In_One_MMSeg/mmseg/utils/tokenizer.py | 240 ++ .../mmseg/utils/typing_utils.py | 25 + Seg_All_In_One_MMSeg/mmseg/version.py | 18 + .../mmseg/visualization/__init__.py | 4 + .../mmseg/visualization/local_visualizer.py | 349 ++ Seg_All_In_One_MMSeg/model-index.yml | 53 + .../projects/Adabins/README.md | 46 + .../projects/Adabins/backbones/__init__.py | 4 + .../Adabins/backbones/adabins_backbone.py | 141 + .../Adabins/configs/_base_/datasets/nyu.py | 32 + .../Adabins/configs/_base_/default_runtime.py | 15 + .../Adabins/configs/_base_/models/Adabins.py | 35 + ...abins_efficient_b5_4x16_25e_NYU_416x544.py | 15 + ...ins_efficient_b5_4x16_25e_kitti_352x704.py | 12 + .../projects/Adabins/decode_head/__init__.py | 4 + .../Adabins/decode_head/adabins_head.py | 179 + .../projects/CAT-Seg/README.md | 92 + .../projects/CAT-Seg/cat_seg/__init__.py | 2 + .../CAT-Seg/cat_seg/models/__init__.py | 10 + .../CAT-Seg/cat_seg/models/cat_aggregator.py | 763 +++++ .../CAT-Seg/cat_seg/models/cat_head.py | 116 + .../CAT-Seg/cat_seg/models/clip_ovseg.py | 293 ++ .../CAT-Seg/cat_seg/utils/__init__.py | 10 + .../bpe_vocab/bpe_simple_vocab_16e6.txt.gz | Bin 0 -> 1356917 bytes .../CAT-Seg/cat_seg/utils/clip_model.py | 651 ++++ .../CAT-Seg/cat_seg/utils/clip_templates.py | 204 ++ .../CAT-Seg/cat_seg/utils/clip_wrapper.py | 275 ++ .../cat_seg/utils/self_attention_block.py | 79 + .../CAT-Seg/cat_seg/utils/tokenizer.py | 160 + .../configs/_base_/datasets/ade20k_384x384.py | 68 + .../_base_/datasets/coco-stuff164k_384x384.py | 62 + .../datasets/pascal_context_59_384x384.py | 72 + .../CAT-Seg/configs/_base_/default_runtime.py | 15 + .../configs/_base_/schedules/schedule_80k.py | 24 + ...-warmcoslr2e-4-adamw-80k_ade20k-384x384.py | 103 + ...e-4-adamw-80k_pascal-context-59-384x384.py | 103 + ...lr2e-4-adamw-80k_coco-stuff164k-384x384.py | 102 + ...lr2e-4_adamw-80k_coco-stuff164k_384x384.py | 11 + ...lr2e-4_adamw-80k_coco-stuff164k_384x384.py | 11 + ...lr2e-4_adamw-80k_coco-stuff164k_384x384.py | 72 + .../projects/CAT-Seg/utils/__init__.py | 7 + Seg_All_In_One_MMSeg/projects/README.md | 19 + .../projects/XDecoder/README.md | 17 + .../projects/bdd100k_dataset/README.md | 50 + .../configs/_base_/datasets/bdd100k.py | 70 + ...pspnet_r50-d8_4xb2-80k_bdd100k-512x1024.py | 11 + .../docs/en/user_guides/2_dataset_prepare.md | 40 + .../zh_cn/user_guides/2_dataset_prepare.md | 42 + .../bdd100k_dataset/mmseg/datasets/bdd100k.py | 31 + .../projects/example_project/README.md | 134 + ...mmy-r50-d8_4xb2-40k_cityscapes-512x1024.py | 8 + .../example_project/dummy/__init__.py | 3 + .../example_project/dummy/dummy_resnet.py | 14 + Seg_All_In_One_MMSeg/projects/faq.md | 19 + .../configs/_base_/datasets/gid.py | 67 + ...labv3plus_r101-d8_4xb2-240k_gid-256x256.py | 15 + .../gid_dataset/mmseg/datasets/gid.py | 55 + .../tools/dataset_converters/gid.py | 181 + .../gid_select15imgFromAll.py | 75 + .../user_guides/2_dataset_prepare.md | 53 + .../projects/hsidrive20_dataset/README.md | 34 + .../configs/_base_/datasets/hsi_drive.py | 50 + ...t-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py | 58 + .../docs/en/user_guides/2_dataset_prepare.md | 42 + .../zh_cn/user_guides/2_dataset_prepare.md | 42 + .../mmseg/datasets/hsi_drive.py | 23 + Seg_All_In_One_MMSeg/projects/hssn/README.md | 91 + .../configs/_base_/datasets/cityscapes.py | 67 + .../hssn/configs/_base_/default_runtime.py | 15 + .../deeplabv3plus_r50-d8_vd_contrast.py | 55 + .../configs/_base_/schedules/schedule_80k.py | 24 + ...us_r101-d8_4xb2-80l_cityscapes-512x1024.py | 21 + .../projects/hssn/decode_head/__init__.py | 4 + .../decode_head/sep_aspp_contrast_head.py | 193 ++ .../projects/hssn/losses/__init__.py | 4 + .../losses/hiera_triplet_loss_cityscape.py | 218 ++ .../projects/hssn/losses/tree_triplet_loss.py | 86 + Seg_All_In_One_MMSeg/projects/isnet/README.md | 117 + ...et_r50-d8_8xb2-160k_cityscapes-512x1024.py | 80 + .../projects/isnet/decode_heads/__init__.py | 3 + .../projects/isnet/decode_heads/isnet_head.py | 337 ++ .../projects/mapillary_dataset/README.md | 86 + .../configs/_base_/datasets/mapillary_v1.py | 68 + .../_base_/datasets/mapillary_v1_65.py | 37 + .../configs/_base_/datasets/mapillary_v2.py | 68 + ..._r101-d8_4xb2-240k_mapillay_v1-512x1024.py | 17 + ..._r101-d8_4xb2-240k_mapillay_v2-512x1024.py | 16 + ..._r101-d8_4xb2-240k_mapillay_v1-512x1024.py | 16 + ..._r101-d8_4xb2-240k_mapillay_v2-512x1024.py | 16 + .../docs/en/user_guides/2_dataset_prepare.md | 255 ++ .../mmseg/datasets/mapillary.py | 177 + .../medical/2d_image/ct/cranium/README.md | 142 + .../ct/cranium/configs/cranium_512x512.py | 42 + ...sigmoid}_1xb16-0.01-20k_cranium-512x512.py | 18 + ...6_unet_1xb16-0.0001-20k_cranium-512x512.py | 17 + ...16_unet_1xb16-0.001-20k_cranium-512x512.py | 17 + ...d16_unet_1xb16-0.01-20k_cranium-512x512.py | 17 + .../ct/cranium/datasets/cranium_dataset.py | 31 + .../ct/cranium/tools/prepare_dataset.py | 66 + .../dermoscopy/isic2016_task1/README.md | 149 + ...1xb16-0.0001-20k_isic2016-task1-512x512.py | 17 + ..._1xb16-0.001-20k_isic2016-task1-512x512.py | 17 + ...t_1xb16-0.01-20k_isic2016-task1-512x512.py | 17 + .../configs/isic2016-task1_512x512.py | 42 + .../datasets/isic2016-task1_dataset.py | 30 + .../isic2016_task1/tools/prepare_dataset.py | 120 + .../dermoscopy/isic2017_task1/README.md | 158 + ...1xb16-0.0001-20k_isic2017-task1-512x512.py | 17 + ..._1xb16-0.001-20k_isic2017-task1-512x512.py | 17 + ...t_1xb16-0.01-20k_isic2017-task1-512x512.py | 17 + .../configs/isic2017-task1_512x512.py | 41 + .../datasets/isic2017-task1_dataset.py | 30 + .../isic2017_task1/tools/prepare_dataset.py | 127 + .../2d_image/endoscopy/kvasir_seg/README.md | 145 + ...moid}_1xb16-0.01-20k_kvasir-seg-512x512.py | 18 + ...net_1xb16-0.0001-20k_kvasir-seg-512x512.py | 17 + ...unet_1xb16-0.001-20k_kvasir-seg-512x512.py | 17 + ..._unet_1xb16-0.01-20k_kvasir-seg-512x512.py | 17 + .../kvasir_seg/configs/kvasir-seg_512x512.py | 42 + .../kvasir_seg/datasets/kvasir-seg_dataset.py | 30 + .../kvasir_seg/tools/prepare_dataset.py | 87 + .../endoscopy/kvasir_seg_aliyun/README.md | 145 + ...16-0.0001-20k_kvasir-seg-aliyun-512x512.py | 17 + ...b16-0.001-20k_kvasir-seg-aliyun-512x512.py | 17 + ...xb16-0.01-20k_kvasir-seg-aliyun-512x512.py | 17 + ...r-sigmoid-20k_kvasir-seg-aliyun-512x512.py | 18 + .../configs/kvasir-seg-aliyun_512x512.py | 42 + .../datasets/kvasir-seg-aliyun_dataset.py | 30 + .../tools/prepare_dataset.py | 86 + .../fluorescein_angriogram/vampire/README.md | 158 + ...6_unet_1xb16-0.0001-20k_vampire-512x512.py | 19 + ...16_unet_1xb16-0.001-20k_vampire-512x512.py | 19 + ...d16_unet_1xb16-0.01-20k_vampire-512x512.py | 22 + .../vampire/configs/vampire_512x512.py | 42 + .../vampire/datasets/__init__.py | 3 + .../vampire/datasets/vampire_dataset.py | 28 + .../vampire/tools/prepare_dataset.py | 44 + .../fundus_photography/dr_hagis/README.md | 155 + .../dr_hagis/configs/dr-hagis_512x512.py | 42 + ..._unet_1xb16-0.0001-20k_dr-hagis-512x512.py | 17 + ...6_unet_1xb16-0.001-20k_dr-hagis-512x512.py | 17 + ...16_unet_1xb16-0.01-20k_dr-hagis-512x512.py | 17 + .../dr_hagis/datasets/dr-hagis_dataset.py | 27 + .../dr_hagis/tools/prepare_dataset.py | 41 + .../fundus_photography/gamma3/README.md | 167 + ...16_unet_1xb16-0.0001-20k_gamma3-512x512.py | 17 + ...d16_unet_1xb16-0.001-20k_gamma3-512x512.py | 17 + ...-d16_unet_1xb16-0.01-20k_gamma3-512x512.py | 17 + .../gamma3/configs/gamma3_512x512.py | 42 + .../gamma3/datasets/gamma3_dataset.py | 30 + .../gamma3/tools/prepare_dataset.py | 107 + .../fundus_photography/orvs/README.md | 140 + ...-d16_unet_1xb16-0.0001-20k_orvs-512x512.py | 17 + ...5-d16_unet_1xb16-0.001-20k_orvs-512x512.py | 17 + ...s5-d16_unet_1xb16-0.01-20k_orvs-512x512.py | 17 + .../orvs/configs/orvs_512x512.py | 42 + .../orvs/datasets/orvs_dataset.py | 27 + .../orvs/tools/prepare_dataset.py | 55 + .../fundus_photography/rite/README.md | 135 + ...-d16_unet_1xb16-0.0001-20k_rite-512x512.py | 17 + ...5-d16_unet_1xb16-0.001-20k_rite-512x512.py | 17 + ...s5-d16_unet_1xb16-0.01-20k_rite-512x512.py | 17 + ...t_1xb16-0.01lr-sigmoid-20k_rite-512x512.py | 18 + .../rite/configs/rite_512x512.py | 42 + .../rite/datasets/rite_dataset.py | 31 + .../rite/tools/prepare_dataset.py | 98 + .../breastCancerCellSegmentation/README.md | 123 + .../breastCancerCellSegmentation_512x512.py | 42 + ...0k_breastCancerCellSegmentation-512x512.py | 18 + ...0k_breastCancerCellSegmentation-512x512.py | 18 + ...0k_breastCancerCellSegmentation-512x512.py | 18 + .../breastCancerCellSegmentation_dataset.py | 30 + .../tools/prepare_dataset.py | 36 + .../breast_cancer_cell_seg/README.md | 147 + .../configs/breast-cancer-cell-seg_512x512.py | 42 + ...0001-20k_breast-cancer-cell-seg-512x512.py | 18 + ....001-20k_breast-cancer-cell-seg-512x512.py | 18 + ...0.01-20k_breast-cancer-cell-seg-512x512.py | 18 + .../breast-cancer-cell-seg_dataset.py | 29 + .../tools/prepare_dataset.py | 47 + .../histopathology/conic2022_seg/README.md | 207 ++ .../configs/conic2022-seg_512x512.py | 42 + ...unet_1xb16-0.0001-20k_conic2022-512x512.py | 17 + ..._unet_1xb16-0.001-20k_conic2022-512x512.py | 17 + ...6_unet_1xb16-0.01-20k_conic2022-512x512.py | 17 + .../conic2022_seg/conic2022_seg_dataset.png | Bin 0 -> 668126 bytes .../datasets/conic2022-seg_dataset.py | 29 + .../conic2022_seg/tools/prepare_dataset.py | 65 + .../2d_image/histopathology/consep/README.md | 147 + .../consep/configs/consep_512x512.py | 42 + ...16_unet_1xb16-0.0001-20k_consep-512x512.py | 17 + ...d16_unet_1xb16-0.001-20k_consep-512x512.py | 17 + ...-d16_unet_1xb16-0.01-20k_consep-512x512.py | 17 + .../consep/datasets/consep_dataset.py | 30 + .../consep/tools/prepare_dataset.py | 54 + .../histopathology/fusc2021/README.md | 136 + ..._unet_1xb16-0.0001-20k_fusc2021-512x512.py | 17 + ...6_unet_1xb16-0.001-20k_fusc2021-512x512.py | 17 + ...16_unet_1xb16-0.01-20k_fusc2021-512x512.py | 17 + ...b16-0.01lr-sigmoid-20k_fusc2021-512x512.py | 18 + .../fusc2021/configs/fusc2021_512x512.py | 42 + .../fusc2021/datasets/fusc2021_dataset.py | 30 + .../fusc2021/tools/prepare_dataset.py | 114 + .../2d_image/histopathology/pannuke/README.md | 146 + ...16-0.01-20k_bactteria-detection-512x512.py | 18 + ...6_unet_1xb16-0.0001-20k_pannuke-512x512.py | 17 + ...16_unet_1xb16-0.001-20k_pannuke-512x512.py | 17 + ...d16_unet_1xb16-0.01-20k_pannuke-512x512.py | 17 + .../pannuke/configs/pannuke_512x512.py | 42 + .../pannuke/datasets/pannuke_dataset.py | 33 + .../pannuke/tools/prepare_dataset.py | 49 + .../2d_image/histopathology/pcam/README.md | 153 + ...-d16_unet_1xb16-0.0001-20k_pcam-512x512.py | 17 + ...5-d16_unet_1xb16-0.001-20k_pcam-512x512.py | 17 + ...s5-d16_unet_1xb16-0.01-20k_pcam-512x512.py | 17 + ...t_1xb16-0.01lr-sigmoid-20k_pcam-512x512.py | 18 + .../pcam/configs/pcam_512x512.py | 42 + .../pcam/datasets/pcam_dataset.py | 31 + .../pcam/tools/prepare_dataset.py | 49 + .../ravir/README.md | 167 + ...d16_unet_1xb16-0.0001-20k_ravir-512x512.py | 19 + ...-d16_unet_1xb16-0.001-20k_ravir-512x512.py | 19 + ...5-d16_unet_1xb16-0.01-20k_ravir-512x512.py | 19 + .../ravir/configs/ravir_512x512.py | 42 + .../ravir/datasets/__init__.py | 3 + .../ravir/datasets/ravir_dataset.py | 28 + .../ravir/tools/prepare_dataset.py | 33 + .../microscopy_images/2pm_vessel/README.md | 153 + .../2pm_vessel/configs/2pm-vessel_512x512.py | 42 + ...net_1xb16-0.0001-20k_2pm-vessel-512x512.py | 17 + ...unet_1xb16-0.001-20k_2pm-vessel-512x512.py | 17 + ..._unet_1xb16-0.01-20k_2pm-vessel-512x512.py | 17 + ...sigmoid-20k_bactteria-detection-512x512.py | 18 + .../2pm_vessel/datasets/2pm-vessel_dataset.py | 31 + .../2pm_vessel/tools/prepare_dataset.py | 46 + .../bactteria_detection/README.md | 160 + .../configs/bactteria-detection_512x512.py | 42 + ...-0.0001-20k_bactteria-detection-512x512.py | 18 + ...6-0.001-20k_bactteria-detection-512x512.py | 18 + ...16-0.01-20k_bactteria-detection-512x512.py | 18 + .../datasets/bactteria-detection_dataset.py | 27 + .../tools/prepare_dataset.py | 33 + .../2d_image/tools/split_seg_dataset.py | 42 + .../x_ray/chest_image_pneum/README.md | 147 + .../configs/chest-image-pneum_512x512.py | 42 + ...16-0.0001-20k_chest-image-pneum-512x512.py | 18 + ...b16-0.001-20k_chest-image-pneum-512x512.py | 18 + ...xb16-0.01-20k_chest-image-pneum-512x512.py | 18 + .../datasets/chest-image-pneum_dataset.py | 27 + .../tools/prepare_dataset.py | 73 + .../README.md | 119 + ...-images-with-pneumothorax-masks_512x512.py | 42 + ...-images-with-pneumothorax-masks-512x512.py | 20 + ...-images-with-pneumothorax-masks-512x512.py | 19 + ...-images-with-pneumothorax-masks-512x512.py | 19 + ...-images-with-pneumothorax-masks-512x512.py | 19 + ...-images-with-pneumothorax-masks_dataset.py | 31 + .../tools/prepare_dataset.py | 36 + .../2d_image/x_ray/covid_19_ct_cxr/README.md | 158 + .../configs/covid-19-ct-cxr_512x512.py | 42 + ..._1xb16-0.01-20k_covid-19-ct-cxr-512x512.py | 18 + ...xb16-0.0001-20k_covid-19-ct-cxr-512x512.py | 17 + ...1xb16-0.001-20k_covid-19-ct-cxr-512x512.py | 17 + ..._1xb16-0.01-20k_covid-19-ct-cxr-512x512.py | 17 + .../datasets/covid-19-ct-cxr_dataset.py | 31 + .../covid_19_ct_cxr/tools/prepare_dataset.py | 52 + .../medical/2d_image/x_ray/crass/README.md | 144 + .../x_ray/crass/configs/crass_512x512.py | 42 + ...d16_unet_1xb16-0.0001-20k_crass-512x512.py | 17 + ...-d16_unet_1xb16-0.001-20k_crass-512x512.py | 17 + ...5-d16_unet_1xb16-0.01-20k_crass-512x512.py | 17 + ..._1xb16-lr0.01-sigmoid-20k_crass-512x512.py | 18 + .../x_ray/crass/datasets/crass_dataset.py | 30 + .../x_ray/crass/tools/prepare_dataset.py | 84 + .../projects/nvidia_jetson/README.md | 372 ++ .../projects/pp_mobileseg/README.md | 123 + .../pp_mobileseg/backbones/__init__.py | 4 + .../pp_mobileseg/backbones/strideformer.py | 958 ++++++ .../configs/_base_/datasets/ade20k.py | 68 + .../configs/_base_/default_runtime.py | 15 + .../configs/_base_/models/pp_mobile.py | 47 + .../configs/_base_/schedules/schedule_80k.py | 24 + ...obilenetv3_2x16_80k_ade20k_512x512_base.py | 18 + ...obilenetv3_2x16_80k_ade20k_512x512_tiny.py | 45 + .../pp_mobileseg/decode_head/__init__.py | 6 + .../decode_head/pp_mobileseg_head.py | 94 + .../projects/pp_mobileseg/inference_onnx.py | 203 ++ .../projects/sam_inference_demo/README.md | 40 + .../sam_inference_demo/sam/__init__.py | 2 + .../sam/modeling/__init__.py | 12 + .../sam_inference_demo/sam/modeling/common.py | 45 + .../sam/modeling/mask_decoder.py | 196 ++ .../sam/modeling/prompt_encoder.py | 227 ++ .../sam_inference_demo/sam/modeling/sam.py | 188 ++ .../sam/modeling/transformer.py | 241 ++ .../sam_inference_demo/sam/sam_inferencer.py | 688 ++++ .../sam_inference_demo/sam/utils/__init__.py | 2 + .../sam_inference_demo/sam/utils/amg.py | 355 ++ .../sam/utils/transforms.py | 110 + .../sam_inference_demo/sam_image_demo.ipynb | 122 + Seg_All_In_One_MMSeg/projects/van/README.md | 101 + .../projects/van/backbones/__init__.py | 4 + .../projects/van/backbones/van.py | 124 + .../van/configs/_base_/datasets/ade20k.py | 14 + .../van/configs/_base_/models/van_fpn.py | 43 + .../van/configs/_base_/models/van_upernet.py | 51 + .../van/van-b0_fpn_8xb4-40k_ade20k-512x512.py | 8 + .../van/van-b1_fpn_8xb4-40k_ade20k-512x512.py | 6 + .../van/van-b2_fpn_8xb4-40k_ade20k-512x512.py | 53 + ...van-b2_upernet_4xb2-160k_ade20k-512x512.py | 46 + .../van/van-b3_fpn_8xb4-40k_ade20k-512x512.py | 11 + ...van-b3_upernet_4xb2-160k_ade20k-512x512.py | 8 + ...22kpre_upernet_4xb4-160k_ade20k-512x512.py | 10 + ...van-b4_upernet_4xb4-160k_ade20k-512x512.py | 10 + ...22kpre_upernet_4xb2-160k_ade20k-512x512.py | 10 + ...22kpre_upernet_4xb2-160k_ade20k-512x512.py | 10 + Seg_All_In_One_MMSeg/requirements.txt | 4 + Seg_All_In_One_MMSeg/requirements/albu.txt | 1 + Seg_All_In_One_MMSeg/requirements/docs.txt | 7 + .../requirements/mminstall.txt | 2 + .../requirements/multimodal.txt | 2 + .../requirements/optional.txt | 22 + .../requirements/readthedocs.txt | 6 + Seg_All_In_One_MMSeg/requirements/runtime.txt | 5 + Seg_All_In_One_MMSeg/requirements/tests.txt | 8 + Seg_All_In_One_MMSeg/setup.cfg | 19 + Seg_All_In_One_MMSeg/setup.py | 200 ++ Seg_All_In_One_MMSeg/tests/__init__.py | 1 + .../tests/test_apis/test_inferencer.py | 60 + .../tests/test_apis/test_rs_inferencer.py | 73 + Seg_All_In_One_MMSeg/tests/test_apis/utils.py | 38 + Seg_All_In_One_MMSeg/tests/test_config.py | 175 + .../tests/test_datasets/test_dataset.py | 475 +++ .../test_datasets/test_dataset_builder.py | 152 + .../tests/test_datasets/test_formatting.py | 61 + .../tests/test_datasets/test_loading.py | 295 ++ .../tests/test_datasets/test_transform.py | 1273 +++++++ .../tests/test_datasets/test_tta.py | 131 + .../tests/test_digit_version.py | 21 + .../test_layer_decay_optimizer_constructor.py | 300 ++ .../tests/test_engine/test_optimizer.py | 33 + .../test_engine/test_visualization_hook.py | 64 + .../test_metrics/test_citys_metric.py | 119 + .../test_metrics/test_depth_metric.py | 85 + .../test_metrics/test_iou_metric.py | 104 + .../tests/test_models/__init__.py | 1 + .../test_assigners/test_hungarian_assigner.py | 77 + .../test_models/test_backbones/__init__.py | 1 + .../test_models/test_backbones/test_beit.py | 185 + .../test_backbones/test_bisenetv1.py | 109 + .../test_backbones/test_bisenetv2.py | 57 + .../test_models/test_backbones/test_blocks.py | 187 ++ .../test_models/test_backbones/test_cgnet.py | 151 + .../test_backbones/test_clip_text_encoder.py | 43 + .../test_models/test_backbones/test_erfnet.py | 146 + .../test_backbones/test_fast_scnn.py | 42 + .../test_models/test_backbones/test_hrnet.py | 144 + .../test_models/test_backbones/test_icnet.py | 50 + .../test_models/test_backbones/test_mae.py | 186 + .../test_models/test_backbones/test_mit.py | 122 + .../test_backbones/test_mobilenet_v3.py | 67 + .../test_models/test_backbones/test_mscan.py | 69 + .../test_models/test_backbones/test_pidnet.py | 87 + .../test_backbones/test_resnest.py | 44 + .../test_models/test_backbones/test_resnet.py | 575 ++++ .../test_backbones/test_resnext.py | 62 + .../test_models/test_backbones/test_stdc.py | 131 + .../test_models/test_backbones/test_swin.py | 100 + .../test_backbones/test_timm_backbone.py | 133 + .../test_models/test_backbones/test_twins.py | 171 + .../test_models/test_backbones/test_unet.py | 825 +++++ .../test_models/test_backbones/test_vit.py | 185 + .../test_models/test_backbones/test_vpd.py | 51 + .../tests/test_models/test_backbones/utils.py | 43 + .../test_models/test_data_preprocessor.py | 64 + .../tests/test_models/test_forward.py | 229 ++ .../tests/test_models/test_heads/__init__.py | 1 + .../test_models/test_heads/test_ann_head.py | 20 + .../test_models/test_heads/test_apc_head.py | 59 + .../test_models/test_heads/test_aspp_head.py | 76 + .../test_models/test_heads/test_cc_head.py | 18 + .../test_heads/test_decode_head.py | 193 ++ .../test_models/test_heads/test_dm_head.py | 59 + .../test_models/test_heads/test_dnl_head.py | 44 + .../test_models/test_heads/test_dpt_head.py | 49 + .../test_models/test_heads/test_ema_head.py | 23 + .../test_models/test_heads/test_fcn_head.py | 131 + .../test_models/test_heads/test_gc_head.py | 16 + .../test_models/test_heads/test_ham_head.py | 44 + .../test_models/test_heads/test_isa_head.py | 20 + .../test_heads/test_lraspp_head.py | 68 + .../test_heads/test_mask2former_head.py | 153 + .../test_heads/test_maskformer_head.py | 54 + .../test_models/test_heads/test_nl_head.py | 16 + .../test_models/test_heads/test_ocr_head.py | 19 + .../test_heads/test_pidnet_head.py | 89 + .../test_models/test_heads/test_psa_head.py | 122 + .../test_models/test_heads/test_psp_head.py | 36 + .../test_models/test_heads/test_san_head.py | 126 + .../test_heads/test_segformer_head.py | 40 + .../test_heads/test_segmenter_mask_head.py | 24 + .../test_heads/test_setr_mla_head.py | 63 + .../test_heads/test_setr_up_head.py | 56 + .../test_models/test_heads/test_uper_head.py | 35 + .../test_heads/test_vpd_depth_head.py | 50 + .../tests/test_models/test_heads/utils.py | 31 + .../test_losses/test_cross_entropy_loss.py | 28 + .../test_models/test_losses/test_dice_loss.py | 96 + .../test_huasdorff_distance_loss.py | 29 + .../test_losses/test_kldiv_loss.py | 40 + .../test_losses/test_silog_loss.py | 20 + .../test_losses/test_tversky_loss.py | 77 + .../tests/test_models/test_necks/__init__.py | 1 + .../test_necks/test_feature2pyramid.py | 38 + .../tests/test_models/test_necks/test_fpn.py | 30 + .../test_models/test_necks/test_ic_neck.py | 53 + .../tests/test_models/test_necks/test_jpu.py | 46 + .../test_models/test_necks/test_mla_neck.py | 16 + .../test_necks/test_multilevel_neck.py | 32 + .../test_models/test_segmentors/__init__.py | 1 + .../test_cascade_encoder_decoder.py | 57 + .../test_segmentors/test_depth_estimator.py | 64 + .../test_segmentors/test_encoder_decoder.py | 100 + .../test_multimodal_encoder_decoder.py | 24 + .../test_segmentors/test_seg_tta_model.py | 63 + .../test_models/test_segmentors/utils.py | 182 + .../tests/test_models/test_utils/__init__.py | 1 + .../test_models/test_utils/test_embed.py | 461 +++ .../test_utils/test_shape_convert.py | 89 + Seg_All_In_One_MMSeg/tests/test_sampler.py | 78 + .../test_structures/test_seg_data_sample.py | 77 + .../tests/test_utils/test_io.py | 33 + .../tests/test_utils/test_set_env.py | 39 + .../test_local_visualizer.py | 213 ++ .../tools/analysis_tools/analyze_logs.py | 130 + .../tools/analysis_tools/benchmark.py | 121 + .../tools/analysis_tools/browse_dataset.py | 77 + .../tools/analysis_tools/confusion_matrix.py | 197 ++ .../tools/analysis_tools/get_flops.py | 124 + .../tools/analysis_tools/visualization_cam.py | 127 + .../tools/dataset_converters/chase_db1.py | 89 + .../tools/dataset_converters/cityscapes.py | 56 + .../tools/dataset_converters/coco_stuff10k.py | 308 ++ .../dataset_converters/coco_stuff164k.py | 265 ++ .../tools/dataset_converters/drive.py | 114 + .../tools/dataset_converters/hrf.py | 112 + .../tools/dataset_converters/isaid.py | 246 ++ .../tools/dataset_converters/levircd.py | 99 + .../tools/dataset_converters/loveda.py | 73 + .../tools/dataset_converters/nyu.py | 89 + .../dataset_converters/pascal_context.py | 87 + .../tools/dataset_converters/potsdam.py | 158 + .../tools/dataset_converters/refuge.py | 110 + .../tools/dataset_converters/stare.py | 167 + .../tools/dataset_converters/synapse.py | 155 + .../tools/dataset_converters/vaihingen.py | 156 + .../tools/dataset_converters/voc_aug.py | 92 + .../tools/deployment/pytorch2torchscript.py | 185 + Seg_All_In_One_MMSeg/tools/dist_test.sh | 20 + Seg_All_In_One_MMSeg/tools/dist_train.sh | 17 + .../tools/misc/browse_dataset.py | 73 + .../tools/misc/print_config.py | 69 + .../tools/misc/publish_model.py | 50 + .../tools/model_converters/beit2mmseg.py | 56 + .../tools/model_converters/clip2mmseg.py | 163 + .../tools/model_converters/mit2mmseg.py | 82 + .../tools/model_converters/san2mmseg.py | 220 ++ .../tools/model_converters/stdc2mmseg.py | 71 + .../tools/model_converters/swin2mmseg.py | 87 + .../tools/model_converters/twins2mmseg.py | 87 + .../tools/model_converters/vit2mmseg.py | 70 + .../tools/model_converters/vitjax2mmseg.py | 123 + Seg_All_In_One_MMSeg/tools/slurm_test.sh | 24 + Seg_All_In_One_MMSeg/tools/slurm_train.sh | 23 + Seg_All_In_One_MMSeg/tools/test.py | 123 + .../tools/torchserve/mmseg2torchserve.py | 112 + .../tools/torchserve/mmseg_handler.py | 56 + .../tools/torchserve/test_torchserve.py | 58 + Seg_All_In_One_MMSeg/tools/train.py | 105 + .../※使用手册/1_MedSAM环境配置.txt | 20 + Seg_All_In_One_MMSeg/※使用手册/1_环境配置 | 76 + Seg_All_In_One_MMSeg/※使用手册/2_报错与解决 | 14 + Seg_All_In_One_MMSeg/※使用手册/3_操作手册 | 2 + Seg_All_In_One_MMSeg/※使用手册/4_算法相关信息 | 8 + .../※使用手册/MMseg_操作手册.txt | 94 + .../※使用手册/mmseg数据集生成相关 | 65 + .../※使用手册/※2025_9_23_MMSeg使用手册 | 115 + Seg_All_In_One_SegModel/1_predict.py | 564 ++++ .../1_predict_raw_masks_check.py | 137 + .../2_predict_params_and_FLOPs_V1.py | 261 ++ .../2_predict_params_and_FLOPs_V2.py | 325 ++ .../3_predict_matrics_from_log.py | 271 ++ .../Tool_Copy_Best_Model.sh | 120 + Seg_All_In_One_SegModel/Tool_benchmark_smp.py | 189 ++ .../Tool_get_params_and_FLOPs.py | 89 + Seg_All_In_One_SegModel/config.py | 153 + Seg_All_In_One_SegModel/dataset.py | 99 + Seg_All_In_One_SegModel/loss.py | 86 + Seg_All_In_One_SegModel/predict.sh | 109 + Seg_All_In_One_SegModel/requirements.txt | 10 + Seg_All_In_One_SegModel/train.py | 312 ++ Seg_All_In_One_SegModel/train.sh | 105 + Seg_All_In_One_SegModel/utils.py | 361 ++ Seg_All_In_One_SegModel/※2025_9_21_使用手册 | 62 + .../Tool_Yolo_Copy_Best_Model.sh | 117 + .../Yolo可视化测试/Yolo可视化测试-使用手册 | 2 + .../Yolo可视化测试/yolo_layer_tester.py | 359 ++ .../Yolo数据集构建/0_1_check_picture_pair.py | 261 ++ .../Yolo数据集构建/0_2_TOOL_stack_pics.sh | 122 + .../Yolo数据集构建/0_2_stack_picture.py | 41 + .../Yolo数据集构建/1_deal_labels.py | 442 +++ .../2_Check_and_Gen_Txt_Label_ori_label.py | 169 + .../2_Check_and_Gen_Txt_Label_sort_label.py | 204 ++ .../Tool_Classes_And_Palette.py | 20 + .../Tool_convert_bmp_jpg_to_png.py | 167 + .../Yolo数据集构建/Tool_deal_labels.py | 180 + .../Yolo数据集构建/Tool_resize_pics.py | 190 ++ .../Yolo数据集构建/Yolo数据集构建_使用手册 | 69 + Seg_All_In_One_YoloModel/dataset.yaml | 318 ++ Seg_All_In_One_YoloModel/yolo12-seg.yaml | 48 + Seg_All_In_One_YoloModel/yolo_config.py | 123 + Seg_All_In_One_YoloModel/yolo_predict.sh | 167 + Seg_All_In_One_YoloModel/yolo_predict_V1.py | 238 ++ Seg_All_In_One_YoloModel/yolo_predict_V2.py | 538 +++ .../yolo_predict_V2_compare_all.py | 383 +++ .../yolo_predict_raw_masks_check.py | 233 ++ .../yolo_predict_visualize_nn.py | 538 +++ Seg_All_In_One_YoloModel/yolo_train.py | 158 + Seg_All_In_One_YoloModel/yolo_train.sh | 137 + Seg_All_In_One_YoloModel/※2025_9_21_使用手册 | 100 + Seg_Predict_Own_Video_V2/1_Save_Frame_V1.py | 158 + Seg_Predict_Own_Video_V2/1_Save_Frame_V2.py | 185 + Seg_Predict_Own_Video_V2/使用手册 | 2 + .../yolo_Seg_Video-V1-Visible.py | 181 + .../yolo_Seg_Video-V2-UnVisible.py | 193 ++ Seg_Predict_YoloModel/yolo_config.py | 130 + Seg_Predict_YoloModel/yolo_train.py | 113 + Tool-可视化/0_图片Labels生成/4_deal_labels.py | 445 +++ .../0_图片Labels生成/Tool_deal_labels.py | 185 + .../Tool_Check_and_Gen_Txt_Label_ori_label.py | 93 + .../Tool_Check_and_Gen_Txt_Label_sort_label.py | 109 + Tool-可视化/Tool_Gen_8_Bit_PNG[没用,不认].py | 25 + Tool-可视化/get_FPS.py | 92 + Tool-可视化/inference.py | 31 + Tool-可视化/my_dataset.yaml | 28 + Tool-可视化/train.py | 7 + Tool-可视化/yolov11_heatmap_V1.py | 139 + Tool-可视化/yolov11_heatmap_V2.py | 189 ++ Tool-可视化/使用流程.txt | 7 + Tool-图片堆叠/1_check_picture_pair.py | 129 + Tool-图片堆叠/2_TOOL_stack_pics.sh | 122 + Tool-图片堆叠/2_stack_picture.py | 41 + Tool-图片堆叠/※使用手册 | 5 + requirements-seg_smp.txt | 24 + 2048 files changed, 189478 insertions(+) create mode 100644 .gitignore create mode 100644 Back_Up.sh create mode 100644 Check_Graph_Card.sh create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/1_rename_pics.sh create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/2_1_Trans_to_png.py create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/2_2_Resize.py create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/2_reformate_pics.sh create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/3_pair_ori_label.sh create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels.py create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels_old(老版程序).py create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/4_rebuild_labels.sh create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/5_TOOL_stack_pics.sh create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/5_stack_picture.py create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/6_TOOL_stitch_pics.sh create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/6_stitch_picture.py create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/Seg_data_run.sh create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/※1_环境安装.txt create mode 100755 DataSet_Own/1. 图片预处理(内含使用手册)/※2_使用手册.txt create mode 100644 README.md create mode 100644 Seg_All_In_One_Analysis/1_Analysis_All.py create mode 100644 Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/F1_1_CholecSeg8k-13Type-1920x1080_mIoU_vs_FPS.svg create mode 100644 Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/T1_1_CholecSeg8k-13Type-1920x1080_performance_summary.csv create mode 100644 Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/T2_1_CholecSeg8k-13Type-1920x1080_all_iou_summary.csv create mode 100644 Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/F1_2_AutoLaparo-10Type-1920x1080_mIoU_vs_FPS.svg create mode 100644 Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/T1_2_AutoLaparo-10Type-1920x1080_performance_summary.csv create mode 100644 Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/T2_2_AutoLaparo-10Type-1920x1080_all_iou_summary.csv create mode 100644 Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/F1_3_1_Endovis_2017-8Type-512x512_mIoU_vs_FPS.svg create mode 100644 Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/T1_3_1_Endovis_2017-8Type-512x512_performance_summary.csv create mode 100644 Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/T2_3_1_Endovis_2017-8Type-512x512_all_iou_summary.csv create mode 100644 Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/F1_3_2_Endovis_2018-8Type-512x512_mIoU_vs_FPS.svg create mode 100644 Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/T1_3_2_Endovis_2018-8Type-512x512_performance_summary.csv create mode 100644 Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/T2_3_2_Endovis_2018-8Type-512x512_all_iou_summary.csv create mode 100644 Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/F1_4_Dresden-11Type-512x512_mIoU_vs_FPS.svg create mode 100644 Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/T1_4_Dresden-11Type-512x512_performance_summary.csv create mode 100644 Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/T2_4_Dresden-11Type-512x512_all_iou_summary.csv create mode 100644 Seg_All_In_One_MMSeg/CITATION.cff create mode 100644 Seg_All_In_One_MMSeg/LICENSE create mode 100644 Seg_All_In_One_MMSeg/MANIFEST.in create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/0_Initial_Save_All_Model_locally.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/All_Data_Record.json create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/my_dataset_model.json create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_autoLaparo.json create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_cholecseg8k.json create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_dresden.json create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_endovis_2017.json create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_endovis_2018.json create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All-ori.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V1.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V2.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ann_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_apcnet_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_beit_upernet.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_bisenetv1_r18.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_bisenetv2.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ccnet_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_cgnet_fcn.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_danet_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ddrnet.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_deeplabv3_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_deeplabv3plus_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_dnlnet_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_dpt_vit.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_emanet_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_encnet_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_erfnet_fcn.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fast_scnn.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fastfcn_r50_neck.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fcn_r18.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_gcnet_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_hrnet_fcn.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_icnet_r18.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_isanet_r50.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_knet.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_mae_upernet.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_mask2former.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_maskformer.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_pidnet.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_pspnet.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_stdc.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_unet_deeplabv3.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All-ori-old.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V1.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V2.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/3_Find_And_Delete_Special_Epoch.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/3_Tool_Copy_Result_To_Hardisk.sh create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_FLOPs_FPS_V2.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_and_FLOPs_V1.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V1.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V2.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/4_3_predict_draw_pictures_and_tabels.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/4_4_extract_loss_and_best_miou.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_0_base_.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_1_norm_cfg.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_2_data_preprocessor.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_3_model.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_4_optimizer.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_5_train_dataloader.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_Tool.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Select_Tool.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Calculate_std_and_mean.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_configs_base_datasets_my_dataset.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_datasets_init_.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_datasets_my_dataset.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_utils_class_names.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/Initial_Train_Gen_configs_base_schedules_schedule_XXe.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/Initial_Train_Gen_configs_base_schedules_schedule_XXk.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V1-.py create mode 100644 Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V2-.py create mode 100644 Seg_All_In_One_MMSeg/README.md create mode 100644 Seg_All_In_One_MMSeg/README_zh-CN.md create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/ade20k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/ade20k_640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/bdd100k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/chase_db1.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_768x768.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/coco-stuff10k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/coco-stuff164k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/drive.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/hrf.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/hsi_drive.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/isaid.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/levir_256x256.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/loveda.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v1.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v1_65.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v2.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/my_dataset.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/my_dataset_model.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/nyu.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/nyu_512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_context.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_context_59.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_voc12.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_voc12_aug.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/potsdam.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_autolaparo.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_cholecseg8k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_dresden.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_endovis_2017.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_endovis_2018.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/refuge.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/stare.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/synapse.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/datasets/vaihingen.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/default_runtime.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/ann_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/apcnet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv1_r18-d32.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv2.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv2_large.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/ccnet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/cgnet.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/danet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/ddrnet.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3_unet_s5-d16.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3plus_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/dmnet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/dnl_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/dpt_vit-b16.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/emanet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/en_bisenetv2.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/encnet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/erfnet_fcn.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/fast_scnn.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/fastfcn_r50-d32_jpu_psp.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/fcn_hr18.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/fcn_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/fcn_unet_s5-d16.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/fpn_poolformer_s12.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/fpn_r50.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/gcnet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/icnet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/isanet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/knet_r50-d8_my.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/lraspp_m-v3-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/mask2former_my.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/maskformer_my.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/my_bisenetv2_A1.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/my_bisenetv2_A2.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/nonlocal_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/ocrnet_hr18.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/ocrnet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/pidnet.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/pointrend_r50.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/psanet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/pspnet_r50-d8.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/pspnet_unet_s5-d16.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/san_vit-b16.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/segformer_mit-b0.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/segmenter_vit-b16_mask.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/setr_mla.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/setr_naive.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/setr_pup.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/stdc.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/twins_pcpvt-s_fpn.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/twins_pcpvt-s_upernet.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/upernet_beit.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/upernet_convnext.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/upernet_mae.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/upernet_r50.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/upernet_swin.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/upernet_vit-b16_ln_mln.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/models/vpd_sd.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k_check_10000.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k_check_16000.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_20k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_240k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_25k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_300e_val1_check10.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_300e_val1_check10_SGD.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_320k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_40k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_40k_check_4000.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_4k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_4k_check_400.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_80k.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_XXXk_model.py create mode 100644 Seg_All_In_One_MMSeg/configs/_base_/schedules/schedules_4k.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/ann/my_ann.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_10000_my_dataset_model-512x1024-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_10000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_16000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/my_ann_r50-d8_b4-4k_my_dataset_model-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/my_apcnet_r101_g1-160k_check_16000_my_dataset_model-769x769-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/ann/my_apcnet_r50_g1-160k_check_16000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r101_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640_ms.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640_ms.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-base_b2_g1-40k_check_4000_my_dataset_model-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-base_g1-40k_check_4000_my_dataset_model-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-large_b2_g1-40k_check_4000_my_dataset_model-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-large_b2_g1-40k_check_4000_my_dataset_model-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/beit/my_mae_upernet-base_b2_g1-40k_check_4000_my_dataset_model-512x512-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r101-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r101-d32_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb8-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-ohem-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb8-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/my_ccnet_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/ccnet/my_ccnet_r50_Pre_g1-40k_check_4000_my_dataset_model-680x680.py create mode 100644 Seg_All_In_One_MMSeg/configs/cgnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/cgnet/cgnet_fcn_4xb4-60k_cityscapes-680x680.py create mode 100644 Seg_All_In_One_MMSeg/configs/cgnet/cgnet_fcn_4xb8-60k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/cgnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_fcn_fcn_g1-40k_check_4000_my_dataset_model-680x680.py create mode 100644 Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_fcn_g1-40k_check_4000_my_dataset_model-680x680.py create mode 100644 Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_r50_Pre_g1-40k_check_4000_my_dataset_model-680x680.py create mode 100644 Seg_All_In_One_MMSeg/configs/convnext/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/convnext/convnext-large_upernet_8xb2-amp-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/convnext/convnext-small_upernet_8xb2-amp-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/convnext/convnext-tiny_upernet_8xb2-amp-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/convnext/convnext-xlarge_upernet_8xb2-amp-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/convnext/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/danet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/danet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/danet/my_danet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_coco-stuff10k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-320k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_coco-stuff10k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_coco-stuff10k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-320k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_coco-stuff10k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/my_deeplabv3_r50_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-769x769-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3/my_deeplabv3_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_isaid-896x896.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_isaid-896x896.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r18_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-512x512-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/dmnet/my_dmnet_r50xb4-no_slide_test-160k_MyDataset-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/dnlnet/my_dnlnet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/dpt/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/dpt/dpt_vit-b16_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dpt/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1-40k_check_4000_my_dataset_model-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b1-40k_check_4000_my_dataset_model-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b1-40k_check_4000_my_dataset_model-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b4-40k_check_4000_my_dataset_model-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/cityscapes.py create mode 100644 Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/voc.py create mode 100644 Seg_All_In_One_MMSeg/configs/emanet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/emanet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/emanet/my_emanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/emanet/my_emanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/encnet_r50s-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/encnet/my_encnet_r50_r50_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/erfnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/erfnet/erfnet_fcn_4xb4-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/erfnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/erfnet/my_erfnet_fcn_fcn_g1-40k_check_4000_my_dataset_model-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r101_Pre_jpu_aspp_g1-40k_check_4000_my_dataset_model-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-40k_check_4000_my_dataset_model-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/fast_scnn_8xb4-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-40k_check_4000_my_dataset_model-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r18_Pre_g1-40k_check_4000_my_dataset_model-769x769-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r50_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/gcnet/my_gcnet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_isaid-896x896.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_isaid-896x896.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_isaid-896x896.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w18-small_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w18_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w48_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w48_Pre_g1-40k_check_4000_my_dataset_model-769x769-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8_4xb2-160k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8_4xb2-80k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8_4xb2-160k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8_4xb2-80k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8_4xb2-160k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8_4xb2-80k_cityscapes-832x832.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-40k_check_4000_my_dataset_model-832x832-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-40k_check_4000_my_dataset_model-832x832-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/isanet/my_isanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/knet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_deeplabv3_8xb2-adamw-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_fcn_8xb2-adamw-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_pspnet_8xb2-adamw-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_upernet_8xb2-adamw-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/knet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/mae/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512-ms.py create mode 100644 Seg_All_In_One_MMSeg/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mae/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/mae/my_mae_upernet-base_b2_g1-40k_check_4000_my_dataset_model-512x512-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/mae/my_mae_upernet-base_b4_g1-40k_check_4000_my_dataset_model-512x512-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mask2former/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/maskformer/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/maskformer/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v3/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v3/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-s_lraspp_4xb4-320k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-scratch-s_lraspp_4xb4-320k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-scratch_lraspp_4xb4-320k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8_lraspp_4xb4-320k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_8xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_8xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes.py create mode 100644 Seg_All_In_One_MMSeg/configs/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes.py create mode 100644 Seg_All_In_One_MMSeg/configs/point_rend/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/point_rend/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r101_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r101_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r50_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/poolformer/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_m36_8xb4-40k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_m48_8xb4-40k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s24_8xb4-40k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s36_8x4_512x512_40k_ade20k.py create mode 100644 Seg_All_In_One_MMSeg/configs/poolformer/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-20k_coco-stuff10k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-320k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_coco-stuff10k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_isaid-896x896.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d32_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d32_rsb_4xb2-adamw-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8-rsb_4xb2-adamw-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-160k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-20k_coco-stuff10k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-320k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_coco-stuff10k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_coco-stuff164k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_isaid-896x896.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_loveda-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_pascal-context-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_pascal-context-59-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_potsdam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_vaihingen-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_fcn_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_fcn_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_pspnet_4xb2-80k_cityscapes512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_pspnet_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/san/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/san/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/san/san-vit-b16_coco-stuff164k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/san/san-vit-b16_pascal_context-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/san/san-vit-b16_voc12aug-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/san/san-vit-l14_coco-stuff164k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/san/san-vit-l14_pascal_context-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/san/san-vit-l14_voc12aug-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b0_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b1_8xb1-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b1_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b2_8xb1-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b2_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b3_8xb1-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b3_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b4_8xb1-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b4_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb1-160k_cityscapes-1024x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-640x640.py create mode 100644 Seg_All_In_One_MMSeg/configs/segmenter/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/segmenter/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-b_mask_8xb1-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-l_mask_8xb1-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-s_fcn_8xb1-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-s_mask_8xb1-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-t_mask_8xb1-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segnext/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/segnext/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-b_1xb16-adamw-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-l_1xb16-adamw-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-s_1xb16-adamw-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/sem_fpn/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r101_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r101_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r50_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r50_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/sem_fpn/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/setr/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/setr/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/setr/setr_vit-l-mla_8xb1-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_mla_8xb1-80k_cityscapes-768x768.py create mode 100644 Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_mla_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_naive_8xb1-80k_cityscapes-768x768.py create mode 100644 Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_naive_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_pup_8xb1-80k_cityscapes-768x768.py create mode 100644 Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_pup_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/stdc1_4xb12-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/stdc1_in1k-pre_4xb12-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/stdc2_4xb12-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/stdc/stdc2_in1k-pre_4xb12-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/swin/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window12-in1k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-large-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-large-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-small-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/swin/swin-tiny-patch4-window7_upernet_1xb8-20k_levir-256x256.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/twins/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-b_uperhead_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_svt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_svt-b_uperhead_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_svt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_svt-l_uperhead_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/twins/twins_svt-s_uperhead_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/unet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/unet/my_unet_deeplabv3_g1-40k_check_4000_my_dataset_model-128x128-testslide.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_drive-64x64.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_hrf-256x256.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_stare-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_chase-db1-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_drive-64x64.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_hrf-256x256.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_stare-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_chase-db1-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_drive-64x64.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_hrf-256x256.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_stare-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_chase-db1-128x128.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-40k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-80k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-80k_cityscapes-769x769.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-20k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-40k_voc12aug-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/vit/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_upernet_8xb2-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_mln_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_upernet_8xb2-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/configs/vpd/README.md create mode 100644 Seg_All_In_One_MMSeg/configs/vpd/metafile.yaml create mode 100644 Seg_All_In_One_MMSeg/configs/vpd/vpd_sd_4xb8-25k_nyu-480x480.py create mode 100644 Seg_All_In_One_MMSeg/configs/vpd/vpd_sd_4xb8-25k_nyu-512x512.py create mode 100644 Seg_All_In_One_MMSeg/dataset-index.yml create mode 100644 Seg_All_In_One_MMSeg/demo/MMSegmentation_Tutorial.ipynb create mode 100644 Seg_All_In_One_MMSeg/demo/classroom__rgb_00283.jpg create mode 100644 Seg_All_In_One_MMSeg/demo/demo.png create mode 100644 Seg_All_In_One_MMSeg/demo/image_demo.py create mode 100644 Seg_All_In_One_MMSeg/demo/image_demo_with_inferencer.py create mode 100644 Seg_All_In_One_MMSeg/demo/inference_demo.ipynb create mode 100644 Seg_All_In_One_MMSeg/demo/rs_image_inference.py create mode 100644 Seg_All_In_One_MMSeg/demo/video_demo.py create mode 100644 Seg_All_In_One_MMSeg/docker/Dockerfile create mode 100644 Seg_All_In_One_MMSeg/docker/serve/Dockerfile create mode 100644 Seg_All_In_One_MMSeg/docker/serve/config.properties create mode 100644 Seg_All_In_One_MMSeg/docker/serve/entrypoint.sh create mode 100644 Seg_All_In_One_MMSeg/docs/en/.readthedocs.yaml create mode 100644 Seg_All_In_One_MMSeg/docs/en/Makefile create mode 100644 Seg_All_In_One_MMSeg/docs/en/_static/css/readthedocs.css create mode 100644 Seg_All_In_One_MMSeg/docs/en/_static/images/mmsegmentation.png create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_datasets.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_metrics.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_models.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_transforms.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/customize_runtime.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/data_flow.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/datasets.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/engine.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/evaluation.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/index.rst create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/models.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/structures.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/training_tricks.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/advanced_guides/transforms.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/api.rst create mode 100644 Seg_All_In_One_MMSeg/docs/en/conf.py create mode 100644 Seg_All_In_One_MMSeg/docs/en/device/npu.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/get_started.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/index.rst create mode 100644 Seg_All_In_One_MMSeg/docs/en/make.bat create mode 100644 Seg_All_In_One_MMSeg/docs/en/migration/index.rst create mode 100644 Seg_All_In_One_MMSeg/docs/en/migration/interface.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/migration/package.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/model_zoo.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/modelzoo_statistics.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/notes/changelog.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/notes/changelog_v0.x.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/notes/faq.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/overview.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/stat.py create mode 100644 Seg_All_In_One_MMSeg/docs/en/switch_language.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/1_config.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/2_dataset_prepare.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/3_inference.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/4_train_test.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/5_deployment.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/index.rst create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/useful_tools.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/visualization.md create mode 100644 Seg_All_In_One_MMSeg/docs/en/user_guides/visualization_feature_map.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/.readthedocs.yaml create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/Makefile create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/_static/css/readthedocs.css create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/_static/images/mmsegmentation.png create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_datasets.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_metrics.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_models.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_transforms.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/contribute_dataset.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/customize_runtime.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/data_flow.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/datasets.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/engine.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/evaluation.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/index.rst create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/models.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/structures.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/training_tricks.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/transforms.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/api.rst create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/conf.py create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/device/npu.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/get_started.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/imgs/zhihu_qrcode.jpg create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/index.rst create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/make.bat create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/migration/index.rst create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/migration/interface.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/migration/package.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/model_zoo.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/modelzoo_statistics.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/notes/faq.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/overview.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/stat.py create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/switch_language.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/1_config.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/2_dataset_prepare.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/3_inference.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/4_train_test.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/5_deployment.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/deploy_jetson.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/index.rst create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/useful_tools.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/visualization.md create mode 100644 Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/visualization_feature_map.md create mode 120000 Seg_All_In_One_MMSeg/mmseg/.mim/configs create mode 120000 Seg_All_In_One_MMSeg/mmseg/.mim/dataset-index.yml create mode 120000 Seg_All_In_One_MMSeg/mmseg/.mim/model-index.yml create mode 120000 Seg_All_In_One_MMSeg/mmseg/.mim/tools create mode 100644 Seg_All_In_One_MMSeg/mmseg/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/apis/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/apis/inference.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/apis/mmseg_inferencer.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/apis/remote_sense_inferencer.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/apis/utils.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/datasets/loveda.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/datasets/potsdam.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/default_runtime.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_160k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_20k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_240k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_25k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_300e.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_320k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_40k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_80k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/ade.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/basesegdataset.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/bdd100k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/chase_db1.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/cityscapes.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/coco_stuff.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/dark_zurich.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/dataset_wrappers.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/decathlon.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/drive.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/dsdl.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/hrf.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/hsi_drive.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/isaid.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/isprs.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/levir.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/lip.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/loveda.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/mapillary.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/my_dataset.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/my_dataset_model.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/night_driving.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/nyu.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/pascal_context.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/potsdam.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_autolaparo.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_cholecseg8k.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_dresden.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_endovis_2017.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_endovis_2018.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/refuge.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/stare.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/synapse.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/transforms/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/transforms/formatting.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/transforms/loading.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/transforms/transforms.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/datasets/voc.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/engine/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/engine/hooks/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/engine/hooks/visualization_hook.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/engine/optimizers/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/engine/optimizers/force_default_constructor.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/engine/optimizers/layer_decay_optimizer_constructor.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/engine/schedulers/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/engine/schedulers/poly_ratio_scheduler.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/evaluation/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/citys_metric.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/depth_metric.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/iou_metric.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/assigners/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/assigners/base_assigner.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/assigners/hungarian_assigner.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/assigners/match_cost.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/beit.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/bisenetv1.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/bisenetv2.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/cgnet.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/ddrnet.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/en_bisenetv2.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/erfnet.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/fast_scnn.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/hrnet.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/icnet.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/mae.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/mit.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/mobilenet_v2.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/mobilenet_v3.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/mscan.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A1.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A1_add_A2.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A2.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/pidnet.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/resnest.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/resnet.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/resnext.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/stdc.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/swin.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/timm_backbone.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/twins.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/unet.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/vit.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/backbones/vpd.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/builder.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/data_preprocessor.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ann_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/apc_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/aspp_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/cascade_decode_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/cc_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/da_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ddr_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/decode_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dm_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dnl_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dpt_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ema_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/enc_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/fcn_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/fpn_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/gc_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ham_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/isa_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/knet_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/lraspp_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/mask2former_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/maskformer_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/nl_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ocr_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/pid_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/point_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/psa_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/psp_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/san_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/segformer_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/segmenter_mask_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/sep_aspp_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/sep_fcn_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/setr_mla_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/setr_up_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/stdc_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/uper_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/decode_heads/vpd_depth_head.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/accuracy.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/boundary_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/cross_entropy_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/dice_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/focal_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/huasdorff_distance_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/kldiv_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/lovasz_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/ohem_cross_entropy_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/silog_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/tversky_loss.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/losses/utils.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/necks/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/necks/featurepyramid.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/necks/fpn.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/necks/ic_neck.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/necks/jpu.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/necks/mla_neck.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/necks/multilevel_neck.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/segmentors/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/segmentors/base.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/segmentors/cascade_encoder_decoder.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/segmentors/depth_estimator.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/segmentors/encoder_decoder.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/segmentors/multimodal_encoder_decoder.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/segmentors/seg_tta.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/text_encoder/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/text_encoder/clip_text_encoder.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/basic_block.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/embed.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/encoding.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/inverted_residual.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/make_divisible.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/point_sample.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/ppm.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/res_layer.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/san_layers.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/se_layer.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/self_attention_block.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/shape_convert.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/up_conv_block.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/models/utils/wrappers.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/registry/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/registry/registry.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/structures/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/structures/sampler/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/structures/sampler/base_pixel_sampler.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/structures/sampler/builder.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/structures/sampler/ohem_pixel_sampler.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/structures/seg_data_sample.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/bpe_simple_vocab_16e6.txt.gz create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/class_names-ori.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/class_names.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/collect_env.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/get_templates.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/io.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/mask_classification.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/misc.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/set_env.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/tokenizer.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/utils/typing_utils.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/version.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/visualization/__init__.py create mode 100644 Seg_All_In_One_MMSeg/mmseg/visualization/local_visualizer.py create mode 100644 Seg_All_In_One_MMSeg/model-index.yml create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/backbones/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/backbones/adabins_backbone.py create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/datasets/nyu.py create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/default_runtime.py create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/models/Adabins.py create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/configs/adabins/adabins_efficient_b5_4x16_25e_NYU_416x544.py create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/configs/adabins/adabins_efficient_b5_4x16_25e_kitti_352x704.py create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/decode_head/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/Adabins/decode_head/adabins_head.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/cat_aggregator.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/cat_head.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/clip_ovseg.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/bpe_vocab/bpe_simple_vocab_16e6.txt.gz create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_model.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_templates.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_wrapper.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/self_attention_block.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/tokenizer.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/ade20k_384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/coco-stuff164k_384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/pascal_context_59_384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/default_runtime.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/schedules/schedule_80k.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_ade20k-384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_pascal-context-59-384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb2-warmcoslr2e-4-adamw-80k_coco-stuff164k-384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitg-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vith-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitl-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/CAT-Seg/utils/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/XDecoder/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/bdd100k_dataset/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/bdd100k_dataset/configs/_base_/datasets/bdd100k.py create mode 100644 Seg_All_In_One_MMSeg/projects/bdd100k_dataset/configs/pspnet_r50-d8_4xb2-80k_bdd100k-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/projects/bdd100k_dataset/docs/en/user_guides/2_dataset_prepare.md create mode 100644 Seg_All_In_One_MMSeg/projects/bdd100k_dataset/docs/zh_cn/user_guides/2_dataset_prepare.md create mode 100644 Seg_All_In_One_MMSeg/projects/bdd100k_dataset/mmseg/datasets/bdd100k.py create mode 100644 Seg_All_In_One_MMSeg/projects/example_project/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/example_project/configs/fcn_dummy-r50-d8_4xb2-40k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/projects/example_project/dummy/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/example_project/dummy/dummy_resnet.py create mode 100644 Seg_All_In_One_MMSeg/projects/faq.md create mode 100644 Seg_All_In_One_MMSeg/projects/gid_dataset/configs/_base_/datasets/gid.py create mode 100644 Seg_All_In_One_MMSeg/projects/gid_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_gid-256x256.py create mode 100644 Seg_All_In_One_MMSeg/projects/gid_dataset/mmseg/datasets/gid.py create mode 100644 Seg_All_In_One_MMSeg/projects/gid_dataset/tools/dataset_converters/gid.py create mode 100644 Seg_All_In_One_MMSeg/projects/gid_dataset/tools/dataset_converters/gid_select15imgFromAll.py create mode 100644 Seg_All_In_One_MMSeg/projects/gid_dataset/user_guides/2_dataset_prepare.md create mode 100644 Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/configs/_base_/datasets/hsi_drive.py create mode 100644 Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/configs/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py create mode 100644 Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/docs/en/user_guides/2_dataset_prepare.md create mode 100644 Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/docs/zh_cn/user_guides/2_dataset_prepare.md create mode 100644 Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/mmseg/datasets/hsi_drive.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/datasets/cityscapes.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/default_runtime.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/models/deeplabv3plus_r50-d8_vd_contrast.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/schedules/schedule_80k.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/configs/hssn/hieraseg_deeplabv3plus_r101-d8_4xb2-80l_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/decode_head/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/decode_head/sep_aspp_contrast_head.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/losses/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/losses/hiera_triplet_loss_cityscape.py create mode 100644 Seg_All_In_One_MMSeg/projects/hssn/losses/tree_triplet_loss.py create mode 100644 Seg_All_In_One_MMSeg/projects/isnet/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/isnet/configs/isnet_r50-d8_8xb2-160k_cityscapes-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/projects/isnet/decode_heads/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/isnet/decode_heads/isnet_head.py create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v1.py create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v1_65.py create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v2.py create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v1-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v2-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/pspnet_r101-d8_4xb2-240k_mapillay_v1-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/pspnet_r101-d8_4xb2-240k_mapillay_v2-512x1024.py create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/docs/en/user_guides/2_dataset_prepare.md create mode 100644 Seg_All_In_One_MMSeg/projects/mapillary_dataset/mmseg/datasets/mapillary.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/cranium_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_cranium-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_cranium-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_cranium-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_cranium-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/datasets/cranium_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2016-task1-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2016-task1-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2016-task1-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/isic2016-task1_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/datasets/isic2016-task1_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2017-task1-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2017-task1-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2017-task1-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/isic2017-task1_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/datasets/isic2017-task1_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_kvasir-seg-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_kvasir-seg-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_kvasir-seg-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_kvasir-seg-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/kvasir-seg_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/datasets/kvasir-seg_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_kvasir-seg-aliyun-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_kvasir-seg-aliyun-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_kvasir-seg-aliyun-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_kvasir-seg-aliyun-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/kvasir-seg-aliyun_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/datasets/kvasir-seg-aliyun_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_vampire-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_vampire-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_vampire-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/vampire_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/datasets/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/datasets/vampire_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/dr-hagis_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_dr-hagis-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_dr-hagis-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_dr-hagis-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/datasets/dr-hagis_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_gamma3-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_gamma3-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_gamma3-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/gamma3_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/datasets/gamma3_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_orvs-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_orvs-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_orvs-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/orvs_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/datasets/orvs_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_rite-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_rite-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_rite-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_rite-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/rite_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/datasets/rite_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/breastCancerCellSegmentation_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_breastCancerCellSegmentation-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_breastCancerCellSegmentation-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_breastCancerCellSegmentation-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/datasets/breastCancerCellSegmentation_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/breast-cancer-cell-seg_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_breast-cancer-cell-seg-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_breast-cancer-cell-seg-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_breast-cancer-cell-seg-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/datasets/breast-cancer-cell-seg_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/conic2022-seg_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_conic2022-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_conic2022-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_conic2022-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/conic2022_seg_dataset.png create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/datasets/conic2022-seg_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/consep_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_consep-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_consep-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_consep-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/datasets/consep_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_fusc2021-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_fusc2021-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_fusc2021-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_fusc2021-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fusc2021_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/datasets/fusc2021_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_bactteria-detection-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_pannuke-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_pannuke-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_pannuke-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/pannuke_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/datasets/pannuke_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_pcam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_pcam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_pcam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_pcam-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/pcam_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/datasets/pcam_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_ravir-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_ravir-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_ravir-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/ravir_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/datasets/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/datasets/ravir_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/2pm-vessel_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_2pm-vessel-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_2pm-vessel-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_2pm-vessel-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_bactteria-detection-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/datasets/2pm-vessel_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/bactteria-detection_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_bactteria-detection-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_bactteria-detection-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_bactteria-detection-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/datasets/bactteria-detection_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/tools/split_seg_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/chest-image-pneum_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-image-pneum-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-image-pneum-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-image-pneum-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/datasets/chest-image-pneum_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/chest-x-ray-images-with-pneumothorax-masks_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/datasets/chest-x-ray-images-with-pneumothorax-masks_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/covid-19-ct-cxr_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_covid-19-ct-cxr-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_covid-19-ct-cxr-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_covid-19-ct-cxr-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_covid-19-ct-cxr-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/datasets/covid-19-ct-cxr_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/crass_512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_crass-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_crass-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_crass-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-lr0.01-sigmoid-20k_crass-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/datasets/crass_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/tools/prepare_dataset.py create mode 100644 Seg_All_In_One_MMSeg/projects/nvidia_jetson/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/backbones/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/backbones/strideformer.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/datasets/ade20k.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/default_runtime.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/models/pp_mobile.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/schedules/schedule_80k.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_base.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_tiny.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/decode_head/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/decode_head/pp_mobileseg_head.py create mode 100644 Seg_All_In_One_MMSeg/projects/pp_mobileseg/inference_onnx.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/common.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/mask_decoder.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/prompt_encoder.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/sam.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/transformer.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/sam_inferencer.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/amg.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/transforms.py create mode 100644 Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam_image_demo.ipynb create mode 100644 Seg_All_In_One_MMSeg/projects/van/README.md create mode 100644 Seg_All_In_One_MMSeg/projects/van/backbones/__init__.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/backbones/van.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/_base_/datasets/ade20k.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/_base_/models/van_fpn.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/_base_/models/van_upernet.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b0_fpn_8xb4-40k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b1_fpn_8xb4-40k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b2_fpn_8xb4-40k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b2_upernet_4xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b3_fpn_8xb4-40k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b3_upernet_4xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b4-in22kpre_upernet_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b4_upernet_4xb4-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b5-in22kpre_upernet_4xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/projects/van/configs/van/van-b6-in22kpre_upernet_4xb2-160k_ade20k-512x512.py create mode 100644 Seg_All_In_One_MMSeg/requirements.txt create mode 100644 Seg_All_In_One_MMSeg/requirements/albu.txt create mode 100644 Seg_All_In_One_MMSeg/requirements/docs.txt create mode 100644 Seg_All_In_One_MMSeg/requirements/mminstall.txt create mode 100644 Seg_All_In_One_MMSeg/requirements/multimodal.txt create mode 100644 Seg_All_In_One_MMSeg/requirements/optional.txt create mode 100644 Seg_All_In_One_MMSeg/requirements/readthedocs.txt create mode 100644 Seg_All_In_One_MMSeg/requirements/runtime.txt create mode 100644 Seg_All_In_One_MMSeg/requirements/tests.txt create mode 100644 Seg_All_In_One_MMSeg/setup.cfg create mode 100644 Seg_All_In_One_MMSeg/setup.py create mode 100644 Seg_All_In_One_MMSeg/tests/__init__.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_apis/test_inferencer.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_apis/test_rs_inferencer.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_apis/utils.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_config.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_datasets/test_dataset.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_datasets/test_dataset_builder.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_datasets/test_formatting.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_datasets/test_loading.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_datasets/test_transform.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_datasets/test_tta.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_digit_version.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_engine/test_layer_decay_optimizer_constructor.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_engine/test_optimizer.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_engine/test_visualization_hook.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_citys_metric.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_depth_metric.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_iou_metric.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/__init__.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_assigners/test_hungarian_assigner.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/__init__.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_beit.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_bisenetv1.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_bisenetv2.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_blocks.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_cgnet.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_clip_text_encoder.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_erfnet.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_fast_scnn.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_hrnet.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_icnet.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mae.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mit.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mobilenet_v3.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mscan.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_pidnet.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnest.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnet.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnext.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_stdc.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_swin.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_timm_backbone.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_twins.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_unet.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_vit.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_vpd.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_backbones/utils.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_data_preprocessor.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_forward.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/__init__.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ann_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_apc_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_aspp_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_cc_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_decode_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dm_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dnl_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dpt_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ema_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_fcn_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_gc_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ham_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_isa_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_lraspp_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_mask2former_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_maskformer_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_nl_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ocr_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_pidnet_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_psa_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_psp_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_san_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_segformer_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_segmenter_mask_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_setr_mla_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_setr_up_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_uper_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_vpd_depth_head.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_heads/utils.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_cross_entropy_loss.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_dice_loss.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_huasdorff_distance_loss.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_kldiv_loss.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_silog_loss.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_tversky_loss.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_necks/__init__.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_feature2pyramid.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_fpn.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_ic_neck.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_jpu.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_mla_neck.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_multilevel_neck.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/__init__.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_cascade_encoder_decoder.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_depth_estimator.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_encoder_decoder.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_multimodal_encoder_decoder.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_seg_tta_model.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/utils.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_utils/__init__.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_utils/test_embed.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_models/test_utils/test_shape_convert.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_sampler.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_structures/test_seg_data_sample.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_utils/test_io.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_utils/test_set_env.py create mode 100644 Seg_All_In_One_MMSeg/tests/test_visualization/test_local_visualizer.py create mode 100644 Seg_All_In_One_MMSeg/tools/analysis_tools/analyze_logs.py create mode 100644 Seg_All_In_One_MMSeg/tools/analysis_tools/benchmark.py create mode 100644 Seg_All_In_One_MMSeg/tools/analysis_tools/browse_dataset.py create mode 100644 Seg_All_In_One_MMSeg/tools/analysis_tools/confusion_matrix.py create mode 100644 Seg_All_In_One_MMSeg/tools/analysis_tools/get_flops.py create mode 100644 Seg_All_In_One_MMSeg/tools/analysis_tools/visualization_cam.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/chase_db1.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/cityscapes.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/coco_stuff10k.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/coco_stuff164k.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/drive.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/hrf.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/isaid.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/levircd.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/loveda.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/nyu.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/pascal_context.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/potsdam.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/refuge.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/stare.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/synapse.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/vaihingen.py create mode 100644 Seg_All_In_One_MMSeg/tools/dataset_converters/voc_aug.py create mode 100644 Seg_All_In_One_MMSeg/tools/deployment/pytorch2torchscript.py create mode 100644 Seg_All_In_One_MMSeg/tools/dist_test.sh create mode 100644 Seg_All_In_One_MMSeg/tools/dist_train.sh create mode 100644 Seg_All_In_One_MMSeg/tools/misc/browse_dataset.py create mode 100644 Seg_All_In_One_MMSeg/tools/misc/print_config.py create mode 100644 Seg_All_In_One_MMSeg/tools/misc/publish_model.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/beit2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/clip2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/mit2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/san2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/stdc2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/swin2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/twins2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/vit2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/model_converters/vitjax2mmseg.py create mode 100644 Seg_All_In_One_MMSeg/tools/slurm_test.sh create mode 100644 Seg_All_In_One_MMSeg/tools/slurm_train.sh create mode 100644 Seg_All_In_One_MMSeg/tools/test.py create mode 100644 Seg_All_In_One_MMSeg/tools/torchserve/mmseg2torchserve.py create mode 100644 Seg_All_In_One_MMSeg/tools/torchserve/mmseg_handler.py create mode 100644 Seg_All_In_One_MMSeg/tools/torchserve/test_torchserve.py create mode 100644 Seg_All_In_One_MMSeg/tools/train.py create mode 100644 Seg_All_In_One_MMSeg/※使用手册/1_MedSAM环境配置.txt create mode 100644 Seg_All_In_One_MMSeg/※使用手册/1_环境配置 create mode 100644 Seg_All_In_One_MMSeg/※使用手册/2_报错与解决 create mode 100644 Seg_All_In_One_MMSeg/※使用手册/3_操作手册 create mode 100644 Seg_All_In_One_MMSeg/※使用手册/4_算法相关信息 create mode 100644 Seg_All_In_One_MMSeg/※使用手册/MMseg_操作手册.txt create mode 100644 Seg_All_In_One_MMSeg/※使用手册/mmseg数据集生成相关 create mode 100644 Seg_All_In_One_MMSeg/※使用手册/※2025_9_23_MMSeg使用手册 create mode 100644 Seg_All_In_One_SegModel/1_predict.py create mode 100644 Seg_All_In_One_SegModel/1_predict_raw_masks_check.py create mode 100644 Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V1.py create mode 100644 Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V2.py create mode 100644 Seg_All_In_One_SegModel/3_predict_matrics_from_log.py create mode 100644 Seg_All_In_One_SegModel/Tool_Copy_Best_Model.sh create mode 100644 Seg_All_In_One_SegModel/Tool_benchmark_smp.py create mode 100644 Seg_All_In_One_SegModel/Tool_get_params_and_FLOPs.py create mode 100644 Seg_All_In_One_SegModel/config.py create mode 100644 Seg_All_In_One_SegModel/dataset.py create mode 100644 Seg_All_In_One_SegModel/loss.py create mode 100644 Seg_All_In_One_SegModel/predict.sh create mode 100644 Seg_All_In_One_SegModel/requirements.txt create mode 100644 Seg_All_In_One_SegModel/train.py create mode 100644 Seg_All_In_One_SegModel/train.sh create mode 100644 Seg_All_In_One_SegModel/utils.py create mode 100644 Seg_All_In_One_SegModel/※2025_9_21_使用手册 create mode 100644 Seg_All_In_One_YoloModel/Tool_Yolo_Copy_Best_Model.sh create mode 100644 Seg_All_In_One_YoloModel/Yolo可视化测试/Yolo可视化测试-使用手册 create mode 100644 Seg_All_In_One_YoloModel/Yolo可视化测试/yolo_layer_tester.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/0_1_check_picture_pair.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_TOOL_stack_pics.sh create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_stack_picture.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/1_deal_labels.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_ori_label.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_sort_label.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_Classes_And_Palette.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_convert_bmp_jpg_to_png.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_deal_labels.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_resize_pics.py create mode 100644 Seg_All_In_One_YoloModel/Yolo数据集构建/Yolo数据集构建_使用手册 create mode 100644 Seg_All_In_One_YoloModel/dataset.yaml create mode 100644 Seg_All_In_One_YoloModel/yolo12-seg.yaml create mode 100644 Seg_All_In_One_YoloModel/yolo_config.py create mode 100644 Seg_All_In_One_YoloModel/yolo_predict.sh create mode 100644 Seg_All_In_One_YoloModel/yolo_predict_V1.py create mode 100644 Seg_All_In_One_YoloModel/yolo_predict_V2.py create mode 100644 Seg_All_In_One_YoloModel/yolo_predict_V2_compare_all.py create mode 100644 Seg_All_In_One_YoloModel/yolo_predict_raw_masks_check.py create mode 100644 Seg_All_In_One_YoloModel/yolo_predict_visualize_nn.py create mode 100644 Seg_All_In_One_YoloModel/yolo_train.py create mode 100644 Seg_All_In_One_YoloModel/yolo_train.sh create mode 100644 Seg_All_In_One_YoloModel/※2025_9_21_使用手册 create mode 100644 Seg_Predict_Own_Video_V2/1_Save_Frame_V1.py create mode 100644 Seg_Predict_Own_Video_V2/1_Save_Frame_V2.py create mode 100644 Seg_Predict_Own_Video_V2/使用手册 create mode 100644 Seg_Predict_YoloModel/yolo_Seg_Video-V1-Visible.py create mode 100644 Seg_Predict_YoloModel/yolo_Seg_Video-V2-UnVisible.py create mode 100644 Seg_Predict_YoloModel/yolo_config.py create mode 100644 Seg_Predict_YoloModel/yolo_train.py create mode 100644 Tool-可视化/0_图片Labels生成/4_deal_labels.py create mode 100644 Tool-可视化/0_图片Labels生成/Tool_deal_labels.py create mode 100644 Tool-可视化/Tool_Check_and_Gen_Txt_Label_ori_label.py create mode 100644 Tool-可视化/Tool_Check_and_Gen_Txt_Label_sort_label.py create mode 100644 Tool-可视化/Tool_Gen_8_Bit_PNG[没用,不认].py create mode 100644 Tool-可视化/get_FPS.py create mode 100644 Tool-可视化/inference.py create mode 100644 Tool-可视化/my_dataset.yaml create mode 100644 Tool-可视化/train.py create mode 100644 Tool-可视化/yolov11_heatmap_V1.py create mode 100644 Tool-可视化/yolov11_heatmap_V2.py create mode 100644 Tool-可视化/使用流程.txt create mode 100644 Tool-图片堆叠/1_check_picture_pair.py create mode 100644 Tool-图片堆叠/2_TOOL_stack_pics.sh create mode 100644 Tool-图片堆叠/2_stack_picture.py create mode 100644 Tool-图片堆叠/※使用手册 create mode 100644 requirements-seg_smp.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed33d98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,92 @@ +# Python caches +__pycache__/ +*.py[cod] +*$py.class +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.ipynb_checkpoints/ + +# Conda / virtual environments +.conda/ +.venv/ +venv/ +env/ + +# Build artifacts +build/ +dist/ +*.egg-info/ + +# Logs and temporary files +*.log +.goutputstream-* +*~ +*.tmp +*.bak +*.swp +logs_parallel_*/ +predict_logs_parallel_*/ +yolo_train_logs_parallel_*/ +yolo_predict_logs_parallel_*/ +wandb/ +mlruns/ +.cache/ +tmp/ +temp/ + +# Large datasets, predictions, training outputs, and backups +DataSet_Public/ +DataSet_Public_outputs/ +BestMode_Predict_Results_DataSet_Public/ +Hardisk/ +Nas_BackUp_Seg/ + +# Keep DataSet_Own preprocessing code/manuals, ignore actual image data +DataSet_Own/* +!DataSet_Own/1. 图片预处理(内含使用手册)/ +!DataSet_Own/1. 图片预处理(内含使用手册)/** +DataSet_Own/1. 图片预处理(内含使用手册)/error*.txt + +# Local model caches and heavy pretrained weights +Seg_All_In_One_MMSeg/My_Local_Model/ +Seg_All_In_One_MMSeg/work_dirs/ +Seg_All_In_One_MMSeg/flops_results/ +Seg_All_In_One_MMSeg/tests/data/ + +# Generated analysis figures; keep CSV/SVG summaries +Seg_All_In_One_Analysis/*/*.png + +# Demo/output media and generated visual data +Seg_Predict_Own_Video_V2/*.mp4 +Seg_Predict_YoloModel/*.mp4 +Seg_Predict_YoloModel/output_*/ +Seg_Predict_YoloModel/YOLO*/ +Seg_All_In_One_YoloModel/Yolo数据集构建/Data/ +Seg_All_In_One_YoloModel/Yolo数据集构建/Label/ +Seg_All_In_One_YoloModel/Yolo数据集构建/ORI/ +Seg_All_In_One_YoloModel/Yolo数据集构建/ORI_GT_label_fold/ +Seg_All_In_One_YoloModel/Yolo数据集构建/ORI_pro_label_fold/ +Tool-图片堆叠/ori/ +Tool-图片堆叠/label/ +Tool-图片堆叠/result_*/ +Tool-可视化/Data/ +Tool-可视化/runs/ +Tool-可视化/0_图片Labels生成/save_*_label_fold/ +Tool-可视化/0_图片Labels生成/*.png + +# Large model/video/archive formats +*.pt +*.pth +*.onnx +*.engine +*.mp4 +*.avi +*.mov +*.mkv +*.zip +*.tar +*.tar.gz +*.tgz +*.7z +*.rar diff --git a/Back_Up.sh b/Back_Up.sh new file mode 100644 index 0000000..5000a76 --- /dev/null +++ b/Back_Up.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# sync_interactive.sh —— 交互式同步脚本 (v3) +# 脚本会自动以其自身所在的位置为根目录,并提供不同的同步备份模式。 + +set -euo pipefail + +# --- 根目录设置 --- +# 获取脚本文件所在的绝对路径,并将其作为所有操作的根目录 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + + +# --- 配置区域 --- +# 算法源目录 (路径基于脚本位置) +SRC_DIRS=( + "$SCRIPT_DIR/Seg_All_In_One_YoloModel" + "$SCRIPT_DIR/Seg_All_In_One_SegModel" + "$SCRIPT_DIR/Seg_All_In_One_MMSeg" + "$SCRIPT_DIR/Seg_All_In_One_Analysis" +) +# 本地镜像/中转目录 (路径基于脚本位置) +LOCAL_DST_ROOT="$SCRIPT_DIR/Hardisk" +# NAS备份目标目录 (路径基于脚本位置) +NAS_DST_ROOT="$SCRIPT_DIR/Nas_BackUp_Seg" + + +# --- 用户选择操作 --- +echo "--- 请选择要执行的同步操作 ---" +echo " 1. [更新并备份算法文件] 从源头更新算法文件到Hardisk,并立即备份到NAS" +echo " 2. [备份Hardisk] 将整个Hardisk目录的当前内容,完全拷贝到NAS【--delete】" +echo " 3. [退出] 不执行任何操作" +echo "------------------------------------------------" + +read -p "请输入选项 [1, 2, 或 3]: " choice + +case $choice in + 1) + echo "--- 您选择了 [1]: 更新并备份算法文件 (源->Hardisk->NAS) ---" + + # --- 第 1 步: 从源目录更新文件到 Hardisk --- + echo "" + echo "--> (1/2) 正在从源目录更新文件到 $LOCAL_DST_ROOT..." + for src_path in "${SRC_DIRS[@]}"; do + if [ ! -d "$src_path" ]; then + echo " 警告: 源目录 '$src_path' 不存在,已跳过。" + continue + fi + + dst_dir_name=$(basename "$src_path") + dst_path=$(mkdir -p "$LOCAL_DST_ROOT/$dst_dir_name" && realpath "$LOCAL_DST_ROOT/$dst_dir_name") + + echo " >>> 正在同步 $src_path -> $dst_path" + rsync -avh --delete "$src_path/" "$dst_path/" + done + echo "--> (1/2) 本地 Hardisk 更新完成。" + + # --- 第 2 步: 从 Hardisk 备份到 NAS --- + echo "" + echo "--> (2/2) 正在将更新后的算法文件从 Hardisk 备份到 $NAS_DST_ROOT..." + for dir_full_path in "${SRC_DIRS[@]}"; do + dir_name=$(basename "$dir_full_path") + src_from_hardisk="$LOCAL_DST_ROOT/$dir_name" + dst_to_nas="$NAS_DST_ROOT/$dir_name" + + if [ ! -d "$src_from_hardisk" ]; then + echo " 警告: 源目录 '$src_from_hardisk' 在 Hardisk 中不存在,已跳过备份。" + continue + fi + + mkdir -p "$dst_to_nas" + dst_path_final=$(realpath "$dst_to_nas") + + echo " >>> 正在备份 $src_from_hardisk -> $dst_path_final" + rsync -avh --delete "$src_from_hardisk/" "$dst_path_final/" + done + echo "--> (2/2) 指定的算法文件已成功备份到 NAS!" + ;; + 2) + echo "--- 您选择了 [2]: 仅备份Hardisk (Hardisk->NAS) ---" + src="$LOCAL_DST_ROOT/" + dst="$NAS_DST_ROOT/" + + if [ ! -d "$src" ]; then + echo "错误: 源目录 '$src' 不存在,无法继续。" + exit 1 + fi + + mkdir -p "$dst" + + echo ">>> 正在将 $src 的全部内容备份到 $dst" + rsync -avh --delete "$src" "$dst" # 使用增量复制 + echo ">>> Hardisk 目录已完全备份到 NAS!" + ;; + 3) + echo "--- 您选择了 [3]: 退出 ---" + echo "操作已取消。" + ;; + *) + echo "无效选项 '$choice'。请输入 1, 2, 或 3。" + exit 1 + ;; +esac + +echo "" +echo ">>> 全部任务完成!" \ No newline at end of file diff --git a/Check_Graph_Card.sh b/Check_Graph_Card.sh new file mode 100644 index 0000000..c9bad07 --- /dev/null +++ b/Check_Graph_Card.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# 检查系统识别到的 GPU 卡数和驱动状态 +echo "======== PCIe 在位 ==========" +lspci -d 10de: | grep -i vga +echo +echo "======== 驱动认到几卡 ========" +nvidia-smi --list-gpus | wc -l +echo +# nvidia: probe of 0000:04:06.0 failed with error -1 +# NVRM: The NVIDIA probe routine failed for 1 device(s). +echo "======== dmesg 关键报错 ======" +dmesg | grep -iE 'nvidia.*fail|nvidia.*error|Xid.*79|GSP.*timeout' | tail -10 + +# 当前信息记录: +# Ubuntu中识别到的7张卡 +# 卡0:00000000:04:00.0 +# 卡1:00000000:04:02.0 +# 卡2:00000000:04:04.0 +# 坏卡-第一次:卡3:00000000:04:06.0 +# 卡4:00000000:05:00.0 +# 坏卡-第二次:卡5:00000000:05:02.0 +# 卡6:00000000:05:04.0 +# 卡7:00000000:05:06.0 +# Exsi服务器识别到的7张卡 +# 卡0:0000:16:00.0 +# 卡1:0000:38:00.0 +# 卡2:0000:49:00.0 +# 坏卡卡槽-第一次:卡3:0000:5a:00.0 +# 卡4:0000:98:00.0 +# 坏卡卡槽-第二次:卡5:0000:b8:00.0 +# 卡6:0000:c8:00.0 +# 卡7:0000:d8:00.0 + +# 解决方案尝试 +# V1. +# 1. 关闭图形界面 +# sudo systemctl set-default multi-user.target +# sudo systemctl set-default multi-user.target +# 2. 立刻关 GSP 重新加载驱动 +# sudo modprobe -r nvidia_drm nvidia_modeset nvidia nvidia_uvm +# sudo modprobe nvidia NVreg_EnableGpuFirmware=0 +# sudo nvidia-smi +# 3. 若 8 卡出现 → 就是 GSP 问题,长期生效: +# echo "options nvidia NVreg_EnableGpuFirmware=0" | sudo tee /etc/modprobe.d/nvidia-disable-gsp.conf +# sudo update-initramfs -u + +# V2. +# 1. 看是不是 BAR 空间不足 +# sudo dmesg | grep -i "BAR 0\|resource 0" | grep 04:06.0 # TODO 变为对应的显卡号 +# [ 4.747643] pci 0000:04:06.0: BAR 0 [mem 0xea000000-0xeaffffff] +# 内核已经成功为 04:06.0 分配了 BAR 0,大小 16 MiB,没有报 “can’t allocate” 或 “failed”,因此 BAR 空间不足/Above 4G Decoding 问题可以排除 + +# V3. +# 彻底冷复位 +# 宿主机或 云控制台 → 断电 10 秒再上电 +# \ No newline at end of file diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/1_rename_pics.sh b/DataSet_Own/1. 图片预处理(内含使用手册)/1_rename_pics.sh new file mode 100755 index 0000000..0c44caa --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/1_rename_pics.sh @@ -0,0 +1,308 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 -i -l [-h]" + echo "对image图片和label图片进行处理" + echo "-i:原始图片的路径,-l:原始标签的路径,-h:帮助" +} + +ori_image_directorys="" +ori_label_directorys="" + +while getopts "hl:i:" opt; do + case $opt in + h) + usage + exit 0 + ;; + i) + ori_image_directorys=$OPTARG + ;; + l) + ori_label_directorys=$OPTARG + ;; + *) + echo -e '\033[31m!!! Error, Illegal input !!!\033[0m' + usage + exit 1 + ;; + esac +done + +# 判断输入地址是否都为空 +if [ -z "$ori_label_directorys" ] && [ -z "$ori_image_directorys" ]; then + echo -e "\033[31m输入地址 -i -l 都为空\033[0m" + usage + exit 1 +fi + +# 地址转化 +ori_image_directory=$(readlink -f "$ori_image_directorys") +ori_label_directory=$(readlink -f "$ori_label_directorys") +if [ -z "$ori_label_directory" ] && [ -z "$ori_image_directory" ]; then + echo "无法解析地址,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directory" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi +if [ ! -d "$ori_label_directory" ] && [ ! -d "$ori_image_directory" ]; then + echo "image、label两目录都不存在,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directory" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi + +# 获取当前脚本的路径和名称 +script_path=$(dirname "$0") +# 将当前目录更改为脚本所在的路径 +cd "$script_path" + +echo -e "\033[32m______ 1_rename_data.sh _____\033[0m" +while true; do + PS3='Please enter your choice: ' + options=("Move all pics in dir to dir" "Delete all space in filename" "Add jpg to no suffix filename" "Replace content in filename" "Extract filename between prefix and suffix" "Add content before filename" "Add content behand filename" "Quit") + echo "一般处理流程1(移动文件)、2(删除所有空格)、3(文件后缀添加.jpg)、4(删除内容)\"-恢复的\"/\"-副本\"、5(取出前缀、后缀中的内容)(方案1:Still/\"\",方案2:\"\"/.Still)、6(添加前缀)、7(添加后缀)、8(关闭)" + select opt in "${options[@]}" + do + case $opt in + ### 选项1将所有在目录下的图片移动到目录中 ### + "Move all pics in dir to dir") + # 判断label图片路径是否存在 + if [ -d "$ori_label_directory" ]; then + echo "**** Processing ori_label_directory: $ori_label_directory ****" + # 遍历 标签图片 目录中的所有后缀为 .png 或 .PNG 的文件,将其移动到主目录 + find "$ori_label_directory" -not -path "*/error/*" \( -iname "*.png" -o -iname "*.PNG" \) -type f -print0 | + while IFS= read -r -d $'\0' file; do + echo cp -n "$file" "$ori_label_directory" + cp -n "$file" "$ori_label_directory" + done + fi + echo "" + # 判断image图片路径是否存在 + if [ -d "$ori_image_directory" ]; then + echo "**** Processing ori_image_directory: $ori_image_directory ****" + # 遍历 原图片 目录中的所有后缀为 .png 或 .PNG 的文件,将其移动到主目录 + find "$ori_image_directory" -not -path "*/error/*" \( -iname "*.png" -o -iname "*.PNG" \) -type f -print0 | + while IFS= read -r -d $'\0' file; do + echo cp -n "$file" "$ori_image_directory" + cp -n "$file" "$ori_image_directory" + done + fi + echo -e "" + break + ;; + + ### 选项2替换文件名中的空格 ### + "Delete all space in filename") + # 输入待删除的内容 + Del_str=" " + Replace_str="" + # 判断image图片路径是否存在 + if [ -d "$ori_image_directory" ]; then + echo "**** Processing ori_image_directory: $ori_image_directory ****" + ls "$ori_image_directory" | grep -e "$Del_str" | awk -v ori_image_directory="$ori_image_directory" -v Replace_str="$Replace_str" -F "$Del_str" '{s1=$0; gsub(/ /, ""); print "mv \""ori_image_directory"/"s1"\" \""ori_image_directory"/"$1""Replace_str""$2"\""}' + ls "$ori_image_directory" | grep -e "$Del_str" | awk -v ori_image_directory="$ori_image_directory" -v Replace_str="$Replace_str" -F "$Del_str" '{s1=$0; gsub(/ /, ""); print "mv \""ori_image_directory"/"s1"\" \""ori_image_directory"/"$1""Replace_str""$2"\""}' | bash + echo -e "" + else + echo "**** image图片目录不存在: $ori_image_directory ****" + echo -e "" + fi + + # 判断label图片路径是否存在 + if [ -d "$ori_label_directory" ]; then + echo "**** Processing ori_label_directory: $ori_label_directory ****" + ls "$ori_label_directory" | grep -e "$Del_str" | awk -v ori_label_directory="$ori_label_directory" -v Replace_str="$Replace_str" -F "$Del_str" '{s1=$0; gsub(/ /, ""); print "mv \""ori_label_directory"/"s1"\" \""ori_label_directory"/"$1""Replace_str""$2"\""}' + ls "$ori_label_directory" | grep -e "$Del_str" | awk -v ori_label_directory="$ori_label_directory" -v Replace_str="$Replace_str" -F "$Del_str" '{s1=$0; gsub(/ /, ""); print "mv \""ori_label_directory"/"s1"\" \""ori_label_directory"/"$1""Replace_str""$2"\""}' | bash + echo -e "" + else + echo "**** label图片目录不存在: $ori_label_directory ****" + echo -e "" + fi + break + ;; + + ### 选项3在无后缀文件后添加.jpg后缀 ### + "Add jpg to no suffix filename") + # 判断image图片路径是否存在 + if [ -d "$ori_image_directory" ]; then + echo "**** Processing ori_image_directory: $ori_image_directory ****" + find "$ori_image_directory" -type f ! -name "*.*" | awk -F/ '{print $NF}' | awk -v ori_image_directory="$ori_image_directory" '{print "mv \""ori_image_directory"/"$0"\" \""ori_image_directory"/"$0".jpg\""}' + find "$ori_image_directory" -type f ! -name "*.*" | awk -F/ '{print $NF}' | awk -v ori_image_directory="$ori_image_directory" '{print "mv \""ori_image_directory"/"$0"\" \""ori_image_directory"/"$0".jpg\""}' | bash + echo -e "" + else + echo "**** image图片目录不存在: $ori_image_directory ****" + echo -e "" + fi + + # 判断label图片路径是否存在 + if [ -d "$ori_label_directory" ]; then + echo "**** Processing ori_label_directory: $ori_label_directory ****" + find "$ori_label_directory" -type f ! -name "*.*" | awk -F/ '{print $NF}' | awk -v ori_label_directory="$ori_label_directory" '{print "mv \""ori_label_directory"/"$0"\" \""ori_label_directory"/"$0".jpg\""}' + find "$ori_label_directory" -type f ! -name "*.*" | awk -F/ '{print $NF}' | awk -v ori_label_directory="$ori_label_directory" '{print "mv \""ori_label_directory"/"$0"\" \""ori_label_directory"/"$0".jpg\""}' | bash + echo -e "" + else + echo "**** label图片目录不存在: $ori_label_directory ****" + echo -e "" + fi + break + ;; + + ### 选项4替换文件名中内容 ### + "Replace content in filename") + # 输入待删除的内容 + echo -n "Please input the content to be deleted = " + read -r Del_str + echo -n "Please input the content to be replace(default is None) = " + read -r Replace_str + # 判断image图片路径是否存在 + if [ -d "$ori_image_directory" ]; then + echo "**** Processing ori_image_directory: $ori_image_directory ****" + ls "$ori_image_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | grep -e "$Del_str" | awk -v ori_image_directory="$ori_image_directory" -v Del_str="$Del_str" -v Replace_str="$Replace_str" -F "$Del_str" '{s1=$0; gsub(Del_str, Replace_str); print "mv \""ori_image_directory"/"s1"\" \""ori_image_directory"/"$0"\""}' + ls "$ori_image_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | grep -e "$Del_str" | awk -v ori_image_directory="$ori_image_directory" -v Del_str="$Del_str" -v Replace_str="$Replace_str" -F "$Del_str" '{s1=$0; gsub(Del_str, Replace_str); print "mv \""ori_image_directory"/"s1"\" \""ori_image_directory"/"$0"\""}' | bash + echo -e "" + else + echo "**** image图片目录不存在: $ori_image_directory ****" + echo -e "" + fi + + # 判断label图片路径是否存在 + if [ -d "$ori_label_directory" ]; then + echo "**** Processing ori_label_directory: $ori_label_directory ****" + ls "$ori_label_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | grep -e "$Del_str" | awk -v ori_label_directory="$ori_label_directory" -v Del_str="$Del_str" -v Replace_str="$Replace_str" -F "$Del_str" '{s1=$0; gsub(Del_str, Replace_str); print "mv \""ori_label_directory"/"s1"\" \""ori_label_directory"/"$0"\""}' + ls "$ori_label_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | grep -e "$Del_str" | awk -v ori_label_directory="$ori_label_directory" -v Del_str="$Del_str" -v Replace_str="$Replace_str" -F "$Del_str" '{s1=$0; gsub(Del_str, Replace_str); print "mv \""ori_label_directory"/"s1"\" \""ori_label_directory"/"$0"\""}' | bash + echo -e "" + else + echo "**** label图片目录不存在: $ori_label_directory ****" + echo -e "" + fi + break + ;; + + ### 选项5删除文件名前缀、后缀之间的内容 ### + "Extract filename between prefix and suffix") + # 输入待删除的内容 + echo -n "Please input the prefix to be deleted = " + read -r prefix + echo -n "Please input the suffix to be deleted = " + read -r suffix + + + # 判断image图片路径是否存在 + if [ -d "$ori_image_directory" ]; then + echo "**** Processing ori_image_directory: $ori_image_directory ****" + # ls -1 以回车显示 + ls -1 "$ori_image_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | grep -e "$prefix" | grep -e "$suffix" | while read file_name; do + # 提取出新的文件名 + if [ -z $prefix ];then + file_name_new=$(echo $file_name | sed "s/\(.*\)$suffix.*\(\.jpg\|\.png\|\.bmp\|\.JPG\|\.PNG\|\.BMP\)$/\1\2/") + else + echo "sed "s/^.*$prefix\(.*\)$suffix.*\(\.jpg\|\.png\|\.bmp\|\.JPG\|\.PNG\|\.BMP\)$/\1\2/"" + file_name_new=$(echo $file_name | sed "s/^.*$prefix\(.*\)$suffix.*\(\.jpg\|\.png\|\.bmp\|\.JPG\|\.PNG\|\.BMP\)$/\1\2/") + fi + echo "$file_name -> $file_name_new" + echo "mv "$ori_image_directory/$file_name" "$ori_image_directory/$file_name_new"" + mv "$ori_image_directory/$file_name" "$ori_image_directory/$file_name_new" + done + # ls "$ori_image_directory" | grep -e "$Del_str" | awk -v ori_image_directory="$ori_image_directory" -v Del_str="$Del_str" '{ s1=$0; match($0, Del_str); sub(substr($0, 1, RSTART + RLENGTH - 1), ""); print "mv \""ori_image_directory"/"s1"\" \""ori_image_directory"/"$0"\""}' + # ls "$ori_image_directory" | grep -e "$Del_str" | awk -v ori_image_directory="$ori_image_directory" -v Del_str="$Del_str" '{ s1=$0; match($0, Del_str); sub(substr($0, 1, RSTART + RLENGTH - 1), ""); print "mv \""ori_image_directory"/"s1"\" \""ori_image_directory"/"$0"\""}' | bash + echo -e "" + else + echo "**** image图片目录不存在: $ori_image_directory ****" + echo -e "" + fi + + # 判断label图片路径是否存在 + if [ -d "$ori_label_directory" ]; then + echo "**** Processing ori_label_directory: $ori_label_directory ****" + ls -1 "$ori_label_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | grep -e "$prefix" | grep -e "$suffix" | while read file_name; do + # 提取出新的文件名 + if [ -z $prefix ];then + file_name_new=$(echo $file_name | sed "s/\(.*\)$suffix.*\(\.jpg\|\.png\|\.bmp\|\.JPG\|\.PNG\|\.BMP\)$/\1\2/") + else + echo "sed "s/^.*$prefix\(.*\)$suffix.*\(\.jpg\|\.png\|\.bmp\|\.JPG\|\.PNG\|\.BMP\)$/\1\2/"" + file_name_new=$(echo $file_name | sed "s/^.*$prefix\(.*\)$suffix.*\(\.jpg\|\.png\|\.bmp\|\.JPG\|\.PNG\|\.BMP\)$/\1\2/") + fi + echo "$file_name -> $file_name_new" + echo "mv "$ori_label_directory/$file_name" "$ori_label_directory/$file_name_new"" + mv "$ori_label_directory/$file_name" "$ori_label_directory/$file_name_new" + done + # ls "$ori_label_directory" | grep -e "$Del_str" | awk -v ori_label_directory="$ori_label_directory" -v Del_str="$Del_str" '{ s1=$0; match($0, Del_str); sub(substr($0, 1, RSTART + RLENGTH - 1), ""); print "mv \""ori_label_directory"/"s1"\" \""ori_label_directory"/"$0"\""}' + # ls "$ori_label_directory" | grep -e "$Del_str" | awk -v ori_label_directory="$ori_label_directory" -v Del_str="$Del_str" '{ s1=$0; match($0, Del_str); sub(substr($0, 1, RSTART + RLENGTH - 1), ""); print "mv \""ori_label_directory"/"s1"\" \""ori_label_directory"/"$0"\""}' | bash + echo -e "" + else + echo "**** label图片目录不存在: $ori_label_directory ****" + echo -e "" + fi + break + ;; + + ### 选项6在文件名前添加内容 ### + "Add content before filename") + echo -n "Please input the content to be added = " + read Group_str + # 判断image图片路径是否存在 + if [ -d "$ori_image_directory" ]; then + echo "**** Processing ori_image_directory: $ori_image_directory ****" + ls "$ori_image_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | awk -v ori_image_directory="$ori_image_directory" -v Group_str="$Group_str" '{print "mv \""ori_image_directory"/"$0"\" \""ori_image_directory"/"Group_str""$0"\""}' + ls "$ori_image_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | awk -v ori_image_directory="$ori_image_directory" -v Group_str="$Group_str" '{print "mv \""ori_image_directory"/"$0"\" \""ori_image_directory"/"Group_str""$0"\""}' | bash + echo -e "" + else + echo "**** image图片目录不存在: $ori_image_directory ****" + echo -e "" + fi + + # 判断label图片路径是否存在 + if [ -d "$ori_label_directory" ]; then + echo "**** Processing ori_label_directory: $ori_label_directory ****" + ls "$ori_label_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | awk -v ori_label_directory="$ori_label_directory" -v Group_str="$Group_str" '{print "mv \""ori_label_directory"/"$0"\" \""ori_label_directory"/"Group_str""$0"\""}' + ls "$ori_label_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | awk -v ori_label_directory="$ori_label_directory" -v Group_str="$Group_str" '{print "mv \""ori_label_directory"/"$0"\" \""ori_label_directory"/"Group_str""$0"\""}' | bash + echo -e "" + else + echo "**** label图片目录不存在: $ori_label_directory ****" + echo -e "" + fi + break + ;; + + ### 选项7在文件名后添加内容 ### + "Add content behand filename") + echo -n "Please input the content to be added = " + read Group_str + # 判断image图片路径是否存在 + if [ -d "$ori_image_directory" ]; then + echo "**** Processing ori_image_directory: $ori_image_directory ****" + ls "$ori_image_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | awk -v ori_image_directory="$ori_image_directory" -v Group_str="$Group_str" -F . '{print "mv \""ori_image_directory"/"$0"\" \""ori_image_directory"/"substr($0, 1, length($0)-length($NF)-1)""Group_str"."$NF"\""}' + ls "$ori_image_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | awk -v ori_image_directory="$ori_image_directory" -v Group_str="$Group_str" -F . '{print "mv \""ori_image_directory"/"$0"\" \""ori_image_directory"/"substr($0, 1, length($0)-length($NF)-1)""Group_str"."$NF"\""}' | bash + echo -e "" + else + echo "**** image图片目录不存在: $ori_image_directory ****" + echo -e "" + fi + + # 判断label图片路径是否存在 + if [ -d "$ori_label_directory" ]; then + echo "**** Processing ori_label_directory: $ori_label_directory ****" + ls "$ori_label_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | awk -v ori_label_directory="$ori_label_directory" -v Group_str="$Group_str" -F . '{print "mv \""ori_label_directory"/"$0"\" \""ori_label_directory"/"substr($0, 1, length($0)-length($NF)-1)""Group_str"."$NF"\""}' + ls "$ori_label_directory" | grep -E "\.(png|jpg|PNG|JPG|BMP|bmp)$" | awk -v ori_label_directory="$ori_label_directory" -v Group_str="$Group_str" -F . '{print "mv \""ori_label_directory"/"$0"\" \""ori_label_directory"/"substr($0, 1, length($0)-length($NF)-1)""Group_str"."$NF"\""}' | bash + echo -e "" + else + echo "**** label图片目录不存在: $ori_label_directory ****" + echo -e "" + fi + break + ;; + + ### 选项8 退出 ### + "Quit") + echo "Exiting..." + exit 0 + ;; + + *) + echo "Invalid option: $REPLY" + echo -e "" + break + ;; + esac + done +done diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/2_1_Trans_to_png.py b/DataSet_Own/1. 图片预处理(内含使用手册)/2_1_Trans_to_png.py new file mode 100755 index 0000000..02e4645 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/2_1_Trans_to_png.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -* +import os, cv2, sys + +def transform(input_path, output_path): + for root, dirs, files in os.walk(input_path): + for name in files: + print("2_1_正在检索", os.path.join(root, name)) + # 如果图片是jpg图片 + if name.endswith('.jpg') or name.endswith('.JPG') : + # convert一下图片 + print("convert \""+os.path.join(root, name)+"\" \""+os.path.join(input_path, name)+"\"") + os.system("convert \""+os.path.join(root, name)+"\" \""+os.path.join(input_path, name)+"\"") + file = os.path.join(root, name) + if os.path.basename(root) == 'error': + break + try: + # 读取图片并且改变存储方式 + im = cv2.imread(file) + if output_path: + # 压缩度调为0 + cv2.imwrite(os.path.join(output_path, name.replace('jpg', 'png').replace('JPG', 'png')), im, [cv2.IMWRITE_PNG_COMPRESSION, 100, cv2.IMWRITE_PNG_COMPRESSION, 0]) + else: + print('transform:' + file.replace('jpg', 'png').replace('JPG', 'png')) + os.system("rm \""+file+"\"") + # 压缩度调为0 + cv2.imwrite(file.replace('jpg', 'png').replace('JPG', 'png'), im, [cv2.IMWRITE_PNG_COMPRESSION, 100, cv2.IMWRITE_PNG_COMPRESSION, 0]) + except: + os.system("echo "+file+" >> error.txt") + # 检查文件夹是否存在 + if not os.path.exists(os.path.join(root, 'error')): + # 如果不存在,创建文件夹 + os.mkdir(os.path.join(root, 'error')) + os.system("mv "+file+" "+os.path.join(root, 'error')) + # 如果图片是jpg图片 + if name.endswith('.bmp') or name.endswith('.BMP') : + # convert一下图片 + print("convert "+os.path.join(root, name)+" "+os.path.join(input_path, name)) + os.system("convert "+os.path.join(root, name)+" "+os.path.join(input_path, name)) + file = os.path.join(root, name) + if os.path.basename(root) == 'error': + break + try: + # 读取图片并且改变存储方式 + im = cv2.imread(file) + if output_path: + # 压缩度调为0 + cv2.imwrite(os.path.join(output_path, name.replace('bmp', 'png').replace('BMP', 'png')), im, [cv2.IMWRITE_PNG_COMPRESSION, 100, cv2.IMWRITE_PNG_COMPRESSION, 0]) + else: + print('transform:' + os.path.join(root, name.replace('bmp', 'png').replace('BMP', 'png'))) + os.system("rm \""+file+"\"") + # 压缩度调为0 + cv2.imwrite(os.path.join(root, name.replace('bmp', 'png').replace('BMP', 'png')), im, [cv2.IMWRITE_PNG_COMPRESSION, 100, cv2.IMWRITE_PNG_COMPRESSION, 0]) + except: + os.system("echo \""+file+"\" >> error.txt") + # 检查文件夹是否存在 + if not os.path.exists(os.path.join(root, 'error')): + # 如果不存在,创建文件夹 + os.mkdir(os.path.join(root, 'error')) + os.system("mv \""+file+"\" \""+os.path.join(root, 'error')+"\"") + + + +if __name__ == '__main__': + input_path = sys.argv[1] # './2.C组/Group_label_C0' + output_path = None + if not os.path.exists(input_path): + print("文件夹不存在!") + else: + print("Start to transform 2_1_Trans_to_png!") + transform(input_path, output_path) + print("Transform end 2_1_Trans_to_png!") + print() diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/2_2_Resize.py b/DataSet_Own/1. 图片预处理(内含使用手册)/2_2_Resize.py new file mode 100755 index 0000000..0ea6fb8 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/2_2_Resize.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -* +import cv2, sys +import os + +break_up = None +break_up_point = False + +# 重新改变图像大小 +def Resize(input_path, output_path, interpolation=False, default_height=1920, default_width=1080): + global break_up, break_up_point + # 遍历输入路径 + for root, dirs, files in os.walk(input_path): + # 遍历所有文件 + for name in files: + file = os.path.join(root, name) + if(name == break_up or break_up == None): # 如果没到达断点或者没有断点 + break_up_point = True + if break_up_point == False: + continue + + if not name.endswith(('.png', '.jpg', '.PNG', '.JPG')): + continue + + if os.path.basename(root) == 'error': + break + print("2_2_正在处理", file) + try: + # print("convert "+os.path.join(root, name)+" "+os.path.join(input_path, name)) + print("Processing: ","convert \""+file+"\" \""+file+"\"") + status = os.system("convert \""+file+"\" \""+file+"\"") # 改变文件格式 + # 如果返回状态不为0,则移动对应图片到Error文件夹中 + if status != 0: + os.system("echo \""+file+"\" >> error.txt") + # 检查文件夹是否存在 + if not os.path.exists(os.path.join(root, 'error')): + # 如果不存在,创建文件夹 + os.mkdir(os.path.join(root, 'error')) + os.system("mv \""+file+"\" \""+os.path.join(root, 'error')+'\"') + print("此文件是问题文件 ","mv \""+file+"\" \""+os.path.join(root, 'error')+'\"') + continue + + + # 读取图片 + im = cv2.imread(file) + height, width, channels = im.shape + # 判断高度和宽度是否符合要求 + if height == default_height and width == default_width: + print("符合要求") + continue + else: + # 是否满足最近临要求 + if interpolation == False: + im = cv2.resize(im, (default_width, default_height)) + else: + im = cv2.resize(im, (default_width, default_height),interpolation = cv2.INTER_NEAREST) + # 输出影像 + if output_path: + cv2.imwrite(os.path.join(output_path, name), im, [cv2.IMWRITE_PNG_COMPRESSION, 100, cv2.IMWRITE_PNG_COMPRESSION, 0]) + else: + print('Resize:' + file) + os.system("rm \""+file+"\"") # TODO + # 压缩度调为0 + cv2.imwrite(file, im, [cv2.IMWRITE_PNG_COMPRESSION, 100, cv2.IMWRITE_PNG_COMPRESSION, 0]) + except: + os.system("echo \""+file+"\" >> error.txt") + # 检查文件夹是否存在 + if not os.path.exists(os.path.join(root, 'error')): + # 如果不存在,创建文件夹 + os.mkdir(os.path.join(root, 'error')) + os.system("mv \""+file+"\" \""+os.path.join(root, 'error')+"\"") + +if __name__ == '__main__': + input_path = sys.argv[1] # './2.C组/Group_label_C0' + output_path = None + try: + default_width = int(sys.argv[3]) + except: + default_width = 1920 # 默认宽度 + try: + default_height = int(sys.argv[4]) + except: + default_height = 1080 # 默认高度 + if default_width == 0 or default_height == 0: + print("发生错误,default_width、default_height不应该为0") + sys.exit() + interpolation = sys.argv[2] # 最近临插值 + if interpolation == "False": + interpolation = False + elif interpolation == "True": + interpolation = True + else: + print("interpolation must be True or False!") + quit + + if not os.path.exists(input_path): + print(input_path) + print("文件夹不存在!") + else: + print("Start to transform_2_2_Resize.py!") + print(input_path, output_path, default_height,default_width) + Resize(input_path, output_path, interpolation=interpolation, default_height=default_height, default_width=default_width) + print("Transform end_2_2_Resize.py!") diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/2_reformate_pics.sh b/DataSet_Own/1. 图片预处理(内含使用手册)/2_reformate_pics.sh new file mode 100755 index 0000000..2461772 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/2_reformate_pics.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 -i -l -w -h [-help]" + echo "对image图片和label图片进行处理,将其转为PNG格式,并调整图片的宽和高和格式" + echo "-i:原始图片的路径,-l:原始标签的路径,-w:图片宽度,-h:图片高度,-help:帮助" +} + +ori_image_directorys="" +ori_label_directorys="" +pic_width=1920 +pic_height=1080 + +while getopts "l:i:h:w:" opt; do + case $opt in + h) + if [[ $OPTARG =~ ^-?[0-9]+$ ]];then + pic_height=$OPTARG + echo pic_height is $pic_height + elif [ $OPTARG == 'elp' ];then + usage + exit 0 + else + echo "-h(pic_height)必须为整数" + usage + exit 1 + fi + ;; + i) + ori_image_directorys=$OPTARG + ;; + l) + ori_label_directorys=$OPTARG + ;; + w) + if [[ $OPTARG =~ ^-?[0-9]+$ ]];then + pic_width=$OPTARG + echo pic_width is $pic_width + else + echo "-w(pic_height)必须为整数" + usage + exit 1 + fi + ;; + *) + echo -e '\033[31m!!! Error, Illegal input !!!\033[0m' + usage + exit 1 + ;; + esac +done + +# 判断输入地址是否都为空 +if [ -z "$ori_label_directorys" ] && [ -z "$ori_image_directorys" ]; then + echo -e "\033[31m输入地址 -i -l 都为空\033[0m" + usage + exit 1 +fi + +# 地址转化 +ori_image_directory=$(readlink -f "$ori_image_directorys") +ori_label_directory=$(readlink -f "$ori_label_directorys") +if [ -z "$ori_label_directory" ] && [ -z "$ori_image_directory" ]; then + echo "无法解析地址,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi +if [ ! -d "$ori_label_directory" ] && [ ! -d "$ori_image_directory" ]; then + echo "image、label两目录都不存在,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi + +echo -e "\033[32m_____ 2_reformate_data.sh _____\033[0m" + +# 获取当前脚本的路径和名称 +script_path=$(dirname "$0") +# 将当前目录更改为脚本所在的路径 +cd "$script_path" + +# 激活conda环境 +source /home/"$USER"/miniconda/bin/activate Deal_pics + +# 判断image图片路径是否存在 +if [ -d "$ori_image_directory" ]; then + echo "**** Processing ori_image_directory: $ori_image_directory ****" + echo "1.Trans pics to png" + python 2_1_Trans_to_png.py "$ori_image_directory" + echo -e "" + echo "2.Resize image pics with nearest" + echo -e "\033[35m运行:\033[0mpython 2_2_Resize.py "$ori_image_directory" False $pic_width $pic_height " + python 2_2_Resize.py "$ori_image_directory" False $pic_width $pic_height # False 是不使用最近邻插值 + echo -e "" +else + echo "**** image图片目录不存在: $ori_image_directory ****" + echo -e "" +fi + +# 判断label图片路径是否存在 +if [ -d "$ori_label_directory" ]; then + echo "**** Processing ori_label_directory: $ori_label_directory ****" + echo -e "\033[33m__ 1.Trans pics to png __\033[0m" + echo -e "\033[35m运行:\033[0mpython 2_1_Trans_to_png.py "$ori_label_directory"" + python 2_1_Trans_to_png.py "$ori_label_directory" + echo -e "" + echo -e "\033[33m__ 2.Resize label pics without nearest __\033[0m" + echo -e "\033[35m运行:\033[0mpython 2_2_Resize.py "$ori_label_directory" True $pic_width $pic_height" + python 2_2_Resize.py "$ori_label_directory" True $pic_width $pic_height # True 是使用最近邻插值 + echo -e "" +else + echo -e "\033[33m**** label图片目录不存在: $ori_image_directory ****\033[0m" + echo -e "" +fi + +source /home/"$USER"/miniconda/bin/deactivate + diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/3_pair_ori_label.sh b/DataSet_Own/1. 图片预处理(内含使用手册)/3_pair_ori_label.sh new file mode 100755 index 0000000..4787fab --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/3_pair_ori_label.sh @@ -0,0 +1,186 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 -i -l [ -p -s -h]" + echo "对image图片和label图片进行匹配(-i、-l均不能为空)(-p -s默认为空"") " + echo "-i:原始image的路径,-l:原始label的路径,-p:前缀内容,-s:后缀内容(不用管文件后缀名),-h:帮助" + echo "e.g. 3_pair_ori_label.sh -i ./C组未标注 -l ./C组标注图片 -p Group_C_ -s _label" +} + +ori_image_directorys="" +ori_label_directorys="" +prefix="" +suffix="" + +while getopts "hl:i:p:s:" opt; do + case $opt in + h) + usage + exit 0 + ;; + i) + ori_image_directorys=$OPTARG + ;; + l) + ori_label_directorys=$OPTARG + ;; + p) + prefix=$OPTARG + ;; + s) + suffix=$OPTARG + ;; + *) + echo "$opt" + echo -e '\033[31m!!! Error, Illegal input !!!\033[0m' + usage + exit 1 + ;; + esac +done + +# 判断输入地址是否为空 +if [ -z "$ori_label_directorys" ] || [ -z "$ori_image_directorys" ]; then + echo -e "\033[31m输入地址 -i -l 存在空地址\033[0m" + usage + exit 1 +fi + +# 地址转化 +ori_image_directory=$(readlink -f "$ori_image_directorys") +ori_label_directory=$(readlink -f "$ori_label_directorys") +if [ -z "$ori_label_directory" ] || [ -z "$ori_image_directory" ]; then + echo "无法解析地址,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi +if [ ! -d "$ori_label_directory" ] || [ ! -d "$ori_image_directory" ]; then + echo "image、label两目录有一个不存在,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi + +# 获取当前脚本的路径和名称 +script_path=$(dirname "$0") +# 将当前目录更改为脚本所在的路径 +cd "$script_path" + +echo -e "\033[32m_____ 3_pair_ori_label.sh _____\033[0m" +# find -name 中"*"表示通配,与'.*'不同 +# 遍历img目录 +for file_path in "$ori_image_directory"/*; do + # 判断是否是文件 + if [[ -f "$file_path" ]]; then + file_name=$(basename "$file_path") + # 判断文件名是否符合规范 + if [[ "$file_name" =~ .*\.(jpg|png|bmp|JPG|PNG|BMP) ]]; then # 判断是否有为图片 + # if [[ "$file_name" =~ "$prefix"".*$suffix".*\.(jpg|png|bmp|JPG|PNG|BMP)$ ]]; then # 判断是否有满足要求的文件名 + # 抽取文件名(有前缀、后缀的抽取前缀、后缀里面的,没有的返回整个) + if [ -z $prefix ];then + file_name_extract=$(echo $file_name | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_extract=$(echo $file_name | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + echo "$file_name -> $file_name_extract" + # 从label目录中看是否有此文件,sed匹配时$suffix后要有.* + if [ -z $prefix ];then + file_name_other=$(ls $ori_label_directory | grep $file_name_extract | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_other=$(ls $ori_label_directory | grep $file_name_extract | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + # 如果另一个目录没有此文件的话 + if [ -z "$file_name_other" ]; then + echo "$file_name image中内容未在$ori_label_directory搜索到" + # 建立相关存储文件夹 + if [ ! -d "$ori_image_directory/Not_pair_pics" ]; then + mkdir -p "$ori_image_directory/Not_pair_pics" # 建立存储文件夹 + fi + # 移动相关文件 + echo "$file_name" >> "$ori_image_directory/Not_pair_pics/not_pair.txt" + mv "$ori_image_directory/$file_name" "$ori_image_directory/Not_pair_pics" + fi + + fi + fi +done + +# 遍历label目录 +for file_path in "$ori_label_directory"/*; do + # 判断是否是文件 + if [[ -f "$file_path" ]]; then + file_name=$(basename "$file_path") + # 判断文件名是否符合规范 + if [[ "$file_name" =~ .*\.(jpg|png|bmp|JPG|PNG|BMP) ]]; then # 判断是否有为图片 + # if [[ "$file_name" =~ "$prefix"".*$suffix".*\.(jpg|png|bmp|JPG|PNG|BMP)$ ]]; then # 判断是否有满足要求的文件名 + # 抽取文件名(有前缀、后缀的抽取前缀、后缀里面的,没有的返回整个) + if [ -z $prefix ];then + file_name_extract=$(echo $file_name | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_extract=$(echo $file_name | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + echo "$file_name -> $file_name_extract" + # 从image目录中看是否有此文件 + if [ -z $prefix ];then + file_name_other=$(ls $ori_image_directory | grep $file_name_extract | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_other=$(ls $ori_image_directory | grep $file_name_extract | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + + # 如果另一个目录没有此文件的话 + if [ -z "$file_name_other" ]; then + echo "$file_name label中对应内容未在$ori_image_directory搜索到" + # 建立相关存储文件夹 + if [ ! -d "$ori_label_directory/Not_pair_pics" ]; then + mkdir -p "$ori_label_directory/Not_pair_pics" # 建立存储文件夹 + fi + # 移动相关文件 + mv "$ori_label_directory/$file_name" "$ori_label_directory/Not_pair_pics" + echo "$file_name" >> "$ori_label_directory/Not_pair_pics/not_pair.txt" + fi + + fi + fi +done + +# 第二次遍历img目录 +for file_path in "$ori_image_directory"/*; do + # 判断是否是文件 + if [[ -f "$file_path" ]]; then + file_name=$(basename "$file_path") + # 判断文件名是否符合规范 + if [[ "$file_name" =~ .*\.(jpg|png|bmp|JPG|PNG|BMP) ]]; then # 判断是否有为图片 + # if [[ "$file_name" =~ "$prefix"".*$suffix".*\.(jpg|png|bmp|JPG|PNG|BMP)$ ]]; then # 判断是否有满足要求的文件名 + # 抽取文件名(有前缀、后缀的抽取前缀、后缀里面的,没有的返回整个) + if [ -z $prefix ];then + file_name_extract=$(echo $file_name | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_extract=$(echo $file_name | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + echo "$file_name -> $file_name_extract" + # 从label目录中看是否有此文件,sed匹配时$suffix后要有.* + if [ -z $prefix ];then + file_name_other=$(ls $ori_label_directory | grep $file_name_extract | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_other=$(ls $ori_label_directory | grep $file_name_extract | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + # 如果另一个目录没有此文件的话 + if [ -z "$file_name_other" ]; then + echo "$file_name image中内容未在$ori_label_directory搜索到" + # 建立相关存储文件夹 + if [ ! -d "$ori_image_directory/Not_pair_pics" ]; then + mkdir -p "$ori_image_directory/Not_pair_pics" # 建立存储文件夹 + fi + # 移动相关文件 + echo "$file_name" >> "$ori_image_directory/Not_pair_pics/not_pair.txt" + mv "$ori_image_directory/$file_name" "$ori_image_directory/Not_pair_pics" + fi + + fi + fi +done + + + + diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels.py b/DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels.py new file mode 100755 index 0000000..0c06ec0 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels.py @@ -0,0 +1,445 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -* +import os,time,sys,threading, colorsys, argparse +import asyncio, cv2, multiprocessing, random +from PIL import Image +import numpy as np +from Tool_deal_labels import edge_detection, detect_connected_regions, Tool_color_connected_array, fill_white_regions, color_connected_regions + +def getFileList(dir,Filelist=[], ext=None, Max_layer=1, layer=0, Donot_Search=['1_边缘检测并膨胀', '2_连通区域检测', '3_分水岭算法填充']): + """ + 获取文件夹及其子文件夹中文件列表 + 输入 dir:文件夹根目录 + 输入 ext: 扩展名 + 返回: 文件路径列表 + """ + newDir = dir + if os.path.isfile(dir): + if ext is None: + Filelist.append(dir) + else: + if ext in dir[-3:]: + Filelist.append(dir) + + elif os.path.isdir(dir): + file_name = os.path.basename(dir) + # 判断是否在禁搜名单中 + if file_name in Donot_Search: + return Filelist + for s in os.listdir(dir): + newDir=os.path.join(dir,s) + if layer <= Max_layer: + getFileList(newDir, Filelist, ext, Max_layer, layer+1) + + return Filelist + +class Deal_image(): + def __init__(self, Annotate_CLASSES = ('肝脏','胆囊'), Annotate_PALETTE = [[255,91,0],[255,234,0]], src_label_fold = "./Label", save_pro_label_fold = "./LABEL_PNG_new", save_GT_label_fold = "./Label_Generate", GT_channel = 1, pro_append_name="_label", GT_append_name="_gtFine_labelTrainIds", ori_img_folder="./ORI_PNG", res_label_folder="./Result_label", save_merge_pic_folder="./Result_merge", back_gnd_color=0, first_class_color=1, pic_type="png", Max_width = 10000, Label_Max_Search_layer=1000, save_process_pics=False, bg_PALETTE = [0,0,0]): + # 背景最好放在最后 + # self.src_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','背景') + # self.src_PALETTE = np.array([[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66],[0,0,0]]) + # self.src_CLASSES_NUM = np.shape(self.src_CLASSES)[0] + self.bg_PALETTE = bg_PALETTE # 背景颜色 TODO + + self.Annotate_CLASSES = Annotate_CLASSES # 待分类的类 + self.Annotate_PALETTE = np.array(Annotate_PALETTE) # 每一类的像素直 + self.Annotate_CLASSES_NUM = np.shape(Annotate_CLASSES)[0] # 类数量 + + self.save_process_pics = save_process_pics # 保存中间过程图片 + + self.src_label_fold = src_label_fold # 原始标签图片 保存位置 + self.save_pro_label_fold = save_pro_label_fold # 优化后标签图片 保存位置 + self.save_GT_label_fold = save_GT_label_fold # GT标签图片 保存位置 + + self.ori_img_folder = ori_img_folder # 最原始手术图片 保存位置 + self.res_label_folder = res_label_folder # 训练出来的label 保存位置 + self.save_merge_pic_folder = save_merge_pic_folder # 融合图像保存位置 + + self.pro_append_name = pro_append_name # 优化后标签图片后缀 + self.GT_append_name = GT_append_name # GT标签图片后缀 + self.GT_channel = GT_channel # GT标签图片通道数 + + self.Max_width = Max_width # 最大图片宽度(匹配时候用) + self.pic_type = pic_type # 图片类型 + self.back_gnd_color = back_gnd_color # 背景颜色 + self.first_class_color = first_class_color # 第一类上的颜色 + self.Label_Max_Search_layer=Label_Max_Search_layer # 文件夹最大搜索深度 + try: + self.labellist_src = getFileList(src_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到ori_label图片 '+str(len(self.labellist_src))+' 张图像') + except: + self.labellist_src = None + print("没有ori_label相关文件") + + try: + # print(save_pro_label_fold) + self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像') + except: + self.labellist_pro = None + print("没有pro_label相关文件") + + try: + self.imglist_src = getFileList(ori_img_folder, [], pic_type, self.Label_Max_Search_layer) + self.reslist_src = getFileList(res_label_folder, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到ori原始图片 '+str(len(self.imglist_src))+' 张图像') + print('本次执行检索到训练train_result图片 '+str(len(self.reslist_src))+' 张图像') + except: + self.imglist_src = None + self.reslist_src = None + print("没有train_result和原始图片相关文件") + + # 获取单张图片各个通路信息 + def get_single_pic_rgb(self, imgpath): + print(imgpath) + image = Image.open(imgpath).convert('RGB') # 转为RGB图片 + # 将 RGB 色值分离 + image.load() + r, g, b = image.split() + r = np.array(r) + g = np.array(g) + b = np.array(b) + return image, r, g, b + + # 将单个pro图片变成GT图片 + def Conver_pro_label_pic_2_GT_pic(self, imgpath, imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + image, r,g,b = self.get_single_pic_rgb(imgpath) + + result_gt = np.ones(np.shape(image))*self.back_gnd_color # 初始化填充内容为back_gnd_color + gt_number = self.first_class_color # 第一类上色颜色确定 + + # PALETTE中排除掉 '背景' [0,0,0] + PALETTE_No_Bg = self.Annotate_PALETTE[~np.all(self.Annotate_PALETTE == self.bg_PALETTE, axis=1)] + + # 遍历所有待识别颜色 + for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in PALETTE_No_Bg: + # 查找三原色匹配位置 + locate_r = np.where( r == Annotate_PALETTE_r ) + locate_g = np.where( g == Annotate_PALETTE_g ) + locate_b = np.where( b == Annotate_PALETTE_b ) + + # 查找都匹配位置(交集) + # 将矩阵换一种表示形式 + locate_r = np.array(locate_r[0]) * Max_width + np.array(locate_r[1]) + locate_g = np.array(locate_g[0]) * Max_width + np.array(locate_g[1]) + locate_b = np.array(locate_b[0]) * Max_width + np.array(locate_b[1]) + + # 用自带函数寻找匹配项 + matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b) + matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0) + result_gt[matched[0],matched[1], :] = gt_number + gt_number = gt_number + 1 + + # 输出GT图片 + if(int(self.GT_channel) == 1): + result_gt = result_gt[:,:,0] + elif(int(self.GT_channel) == 3): + result_gt = cv2.cvtColor(np.float32(result_gt), cv2.COLOR_RGB2BGR) # rgb颜色互换 + else: + print("GT_channel 必须为1或3") + quit + try: # 新建文件夹 + os.mkdir(self.save_GT_label_fold) + except: + print("已有"+self.save_GT_label_fold) + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_GT_label_fold, os.path.basename(imgname).rpartition('.')[0]+self.GT_append_name+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_GT_label_fold, os.path.basename(imgname)+self.GT_append_name+'.'+self.pic_type) + cv2.imwrite(save_dir, result_gt) + print("GT图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将处理好的图片转化为GT图片 + def Conver_pro_label_pic_2_GT_pic_all(self): + print("\033[33m**** 进行转换将Pro_label_pic转换为GT_label_pic ****\033[0m") + print("\033[33mPro_label_pic存储位置为:\033[0m", self.save_pro_label_fold) + print("\033[33mGT_label_pic生成位置为:\033[0m", self.save_GT_label_fold) + try: + # print(save_pro_label_fold) + self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像') + except: + self.labellist_pro = None + print("没有pro_label相关文件") + try: + os.mkdir(self.save_GT_label_fold) # 新建存储文件夹 + except: + print("已有"+self.save_GT_label_fold) + + # 指定最大进程数为 3 + max_processes = 20 + # 创建Pool对象 + pool = multiprocessing.Pool(processes=max_processes) + # 创建并启动进程 + args_list1 = [] + args_list2 = [] + + # 遍历整个文件夹 + for imgpath in self.labellist_pro: + imgname = os.path.basename(imgpath).rpartition('.')[0].replace(self.pro_append_name,"") + args_list1.append(imgpath) + args_list2.append(imgname) + args_list = zip(args_list1, args_list2) + # 使用进程池并行执行任务 + pool.starmap(self.Conver_pro_label_pic_2_GT_pic, args_list) + # 关闭进程池 + pool.close() + pool.join() + + def Conver_ori_label_pic_2_pro_pic(self, imgpath, imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + image = cv2.imread(imgpath) + + # 1. 边缘检测并膨胀 + dilated_image = edge_detection(image) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_Edge'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀', os.path.basename(imgname)+self.pro_append_name+'_Edge'+'.'+self.pic_type) + cv2.imwrite(save_dir, dilated_image) + print("中间态-边缘检测并膨胀 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 2. 检测连通区域 + filtered_labeled_array, _ = detect_connected_regions(dilated_image) + colored_image_filtered = Tool_color_connected_array(filtered_labeled_array) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '2_连通区域检测', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_Region'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '2_连通区域检测', os.path.basename(imgname)+self.pro_append_name+'_Region'+'.'+self.pic_type) + cv2.imwrite(save_dir, colored_image_filtered) + print("中间态-连通区域检测 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 3. 分水岭填充白色区域 + filled_labeled_array = fill_white_regions(filtered_labeled_array) + colored_image_filled = Tool_color_connected_array(filled_labeled_array) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '3_分水岭算法填充', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_FillEdge'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '3_分水岭算法填充', os.path.basename(imgname)+self.pro_append_name+'_FillEdge'+'.'+self.pic_type) + cv2.imwrite(save_dir, colored_image_filled) + print("中间态-分水岭算法填充 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 4. 对连通区域最终上色 + ori_labeled_image = image + result_pro = color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, self.Annotate_PALETTE) + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, os.path.basename(imgname)+self.pro_append_name+'.'+self.pic_type) + print("Pro图片已保存", save_dir) + cv2.imwrite(save_dir, result_pro) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将原始src图片转化为处理好的pro图片 + def Conver_ori_label_pic_2_pro_pic_all(self): + print("\033[33m**** 进行转换将Ori_label_pic转换为Pro_label_pic ****\033[0m") + print("\033[33mOri_label_pic存储位置为:\033[0m", self.src_label_fold) + print("\033[33mPro_label_pic生成位置为:\033[0m", self.save_pro_label_fold) + # 输出颜色预处理图片 + try: + os.mkdir(self.save_pro_label_fold) # 新建存储文件夹 + except: + print("已有"+self.save_pro_label_fold) + if(self.save_process_pics == True): + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀')) # 新建存储1_边缘检测并膨胀文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀')) + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '2_连通区域检测')) # 新建存储2_连通区域检测文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '2_连通区域检测')) + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '3_分水岭算法填充')) # 新建存储1_边缘检测并膨胀文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '3_分水岭算法填充')) + + # 指定最大进程数为 20,多参数函数并行 + max_processes = 20 + # 创建Pool对象 + pool = multiprocessing.Pool(processes=max_processes) + # 创建并启动进程 + args_list1 = [] + args_list2 = [] + + # 遍历整个文件夹 + for imgpath in self.labellist_src: + if imgpath.lower().endswith(('.jpg', '.png')): + imgname= os.path.basename(imgpath).rpartition('.')[0].replace(self.pro_append_name,"") + else: + imgname= os.path.basename(imgpath).replace(self.pro_append_name,"") + try: + print("Processing: ", imgname, "...") + # self.Conver_ori_label_pic_2_pro_pic(imgpath, imgname)s + # args_list.append({'imgpath': imgpath, 'imgname': imgname}) + args_list1.append(imgpath) + args_list2.append(imgname) + except: + os.system("echo "+imgname+" >> error_1.txt") + args_list = zip(args_list1, args_list2) + # 使用进程池并行执行任务 + pool.starmap(self.Conver_ori_label_pic_2_pro_pic, args_list) # 使用starmap进行多参数并行 + # 关闭进程池 + pool.close() + pool.join() + + # 图片堆叠 + def Merge_ori_pic_and_label_pic(self, res_img_path, res_imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + ori_img_path = os.path.join(self.ori_img_folder, res_imgname+'.'+self.pic_type) + if not os.path.exists(ori_img_path): + print("****照片不存在:****", ori_img_path) + return -1 + ori_image, ori_r, ori_g, ori_b = self.get_single_pic_rgb(ori_img_path) + res_image, res_r, res_g, res_b = self.get_single_pic_rgb(res_img_path) + + merge_img = np.array(ori_image) # merge图片初始化,默认图片背景为0.0.0 + + # 遍历所有待识别颜色 + for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in self.Annotate_PALETTE: + # 查找三原色匹配位置 + locate_r = np.where( res_r == Annotate_PALETTE_r ) + locate_g = np.where( res_g == Annotate_PALETTE_g ) + locate_b = np.where( res_b == Annotate_PALETTE_b ) + + # 查找都匹配位置(交集) + # 将矩阵换一种表示形式 + locate_r = np.array(locate_r[0]) * self.Max_width + np.array(locate_r[1]) + locate_g = np.array(locate_g[0]) * self.Max_width + np.array(locate_g[1]) + locate_b = np.array(locate_b[0]) * self.Max_width + np.array(locate_b[1]) + + # 用自带函数寻找匹配项 + matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b) + matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0) + merge_img[matched[0],matched[1], 0] = Annotate_PALETTE_r + merge_img[matched[0],matched[1], 1] = Annotate_PALETTE_g + merge_img[matched[0],matched[1], 2] = Annotate_PALETTE_b + + # 转成cv2形式 + merge_img = cv2.cvtColor(np.float32(merge_img), cv2.COLOR_RGB2BGR) + + try: # 新建文件夹 + os.mkdir(self.save_merge_pic_folder) + except: + print("已有"+self.save_merge_pic_folder) + if res_imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_merge_pic_folder, os.path.basename(res_imgname).rpartition('.')[0]+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_merge_pic_folder, os.path.basename(res_imgname)+'.'+self.pic_type) + + + cv2.imwrite(save_dir, merge_img) + print("Merge图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将label图片与原图片重合 + def Merge_ori_pic_and_label_pic_all(self): + # 遍历整个文件夹 + for res_img_path in self.reslist_src: + if res_img_path.lower().endswith(('.jpg', '.png')): + res_imgname = os.path.basename(res_img_path).rpartition('.')[0].replace(self.pro_append_name,"") + else: + res_imgname = os.path.basename(res_img_path).replace(self.pro_append_name,"") + print("Processing: ", res_imgname, "...") + self.Merge_ori_pic_and_label_pic(res_img_path, res_imgname) + +if __name__ == "__main__": + + Annotate_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','背景') # 待分类的类 + Annotate_PALETTE = [[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66],[0,0,0]] # 每一类的像素直 + bg_PALETTE = [0,0,0] # 背景的RGB + + # 创建参数解析器 + parser = argparse.ArgumentParser(description='Process some files.') + # 添加参数选项 + parser.add_argument('-src_fold', dest='src_label_fold', default='', help='source label folder') + parser.add_argument('-save_pro_fold', dest='save_pro_label_fold', default='./save_pro_label_fold', help='processed label folder') + parser.add_argument('-save_GT_fold', dest='save_GT_label_fold', default='./save_GT_label_fold', help='ground truth folder') + parser.add_argument('-fold_search_depth', dest='Label_Max_Search_layer', default='1000', type=int, help='Folder Search Depth') + parser.add_argument('-pro_suffix_name', dest='pro_append_name', default='_label', help='Pro file suffix') + parser.add_argument('-GT_suffix_name', dest='GT_append_name', default='_gtFine_labelTrainIds', help='GT file suffix') + parser.add_argument('-GT_channel', dest='GT_channel', default='1', type=int, help='GT file channel(1 or 3)') + parser.add_argument('-back_gnd_color', dest='back_gnd_color', default='0', type=int, help='Color of "Back ground"(0 or 255)') + parser.add_argument('-first_class_color', dest='first_class_color', default='1', type=int, help='Color of "First Class"') + parser.add_argument('-pic_type', dest='pic_type', default='png', help='type of picture(Do not add ".")') + parser.add_argument('-Max_width', dest='Max_width', default='10000', type=int, help='Max width of picture') + parser.add_argument('-Rebuild_from', dest='Rebuild_from', default='label', help='Source to Rebuild Labels(label/pro)') + parser.add_argument('-Rebuild_to', dest='Rebuild_to', default='GT', help='Destination of Rebuild Labels(pro/GT)') + parser.add_argument('-save_process_pics', dest='save_process_pics', default='False', help='Save the processed pics(e.g.Gray_pics,Color_pics) in generating pro_pics') + + # 解析命令行参数 + args = parser.parse_args() + + src_label_fold = args.src_label_fold + save_pro_label_fold = args.save_pro_label_fold + save_GT_label_fold = args.save_GT_label_fold + Label_Max_Search_layer = args.Label_Max_Search_layer + pro_append_name = args.pro_append_name + GT_append_name = args.GT_append_name + GT_channel = args.GT_channel + back_gnd_color = args.back_gnd_color + first_class_color = args.first_class_color + pic_type = args.pic_type + Max_width = args.Max_width + Rebuild_from = args.Rebuild_from + Rebuild_to = args.Rebuild_to + save_process_pics = args.save_process_pics + + + try: # 遍历文件深度,最小为1 + Label_Max_Search_layer=int(Label_Max_Search_layer) + except: + Label_Max_Search_layer=1000 + try: # GT标签图片通道数 + GT_channel=int(GT_channel) + except: + GT_channel=1 + try: # 背景颜色(背景选择0或255) + back_gnd_color=int(back_gnd_color) + except: + back_gnd_color=0 + try: # 第一类上的颜色(如果背景为0,选择1;) + first_class_color=int(first_class_color) + except: + first_class_color=1 + try: # 最大图片宽度(匹配时候用) + Max_width=int(Max_width) + except: + Max_width=10000 + if(save_process_pics.lower() == 'false'): + save_process_pics = False + elif(save_process_pics.lower() == 'true'): + save_process_pics = True + else: + save_process_pics = False + + D = Deal_image(Annotate_CLASSES=Annotate_CLASSES, Annotate_PALETTE=Annotate_PALETTE, src_label_fold=src_label_fold, save_pro_label_fold=save_pro_label_fold, save_GT_label_fold=save_GT_label_fold, GT_channel=GT_channel, pro_append_name=pro_append_name, GT_append_name=GT_append_name, back_gnd_color=back_gnd_color, first_class_color=first_class_color, pic_type=pic_type, Max_width=Max_width, Label_Max_Search_layer=Label_Max_Search_layer, save_process_pics=save_process_pics, bg_PALETTE = bg_PALETTE) + # print(D.src_CLASSES_NUM) + if Rebuild_from == 'label': + # 1.先将所有原始图片转为pro图片 + D.Conver_ori_label_pic_2_pro_pic_all() + pass + if Rebuild_to == 'GT': + # 2.再将pro图片转为GT图片 + D.Conver_pro_label_pic_2_GT_pic_all() + pass \ No newline at end of file diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels_old(老版程序).py b/DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels_old(老版程序).py new file mode 100755 index 0000000..c99cf65 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/4_deal_labels_old(老版程序).py @@ -0,0 +1,463 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -* +import os,time,sys,threading, colorsys, argparse +import asyncio, cv2, multiprocessing +from PIL import Image +import numpy as np +def getFileList(dir,Filelist=[], ext=None, Max_layer=1, layer=0, Donot_Search=['1_颜色预处理', '2_灰度化', '3_边缘化']): + """ + 获取文件夹及其子文件夹中文件列表 + 输入 dir:文件夹根目录 + 输入 ext: 扩展名 + 返回: 文件路径列表 + """ + newDir = dir + if os.path.isfile(dir): + if ext is None: + Filelist.append(dir) + else: + if ext in dir[-3:]: + Filelist.append(dir) + + elif os.path.isdir(dir): + file_name = os.path.basename(dir) + # 判断是否在禁搜名单中 + if file_name in Donot_Search: + return Filelist + for s in os.listdir(dir): + newDir=os.path.join(dir,s) + if layer <= Max_layer: + getFileList(newDir, Filelist, ext, Max_layer, layer+1) + + return Filelist + +class Deal_image(): + def __init__(self, Annotate_CLASSES = ('肝脏','胆囊'), Annotate_PALETTE = [[255,91,0],[255,234,0]], src_label_fold = "./Label", save_pro_label_fold = "./LABEL_PNG_new", save_GT_label_fold = "./Label_Generate", GT_channel = 1, pro_append_name="_label", GT_append_name="_gtFine_labelTrainIds", ori_img_folder="./ORI_PNG", res_label_folder="./Result_label", save_merge_pic_folder="./Result_merge", back_gnd_color=0, first_class_color=1, pic_type="png", Max_width = 10000, Label_Max_Search_layer=1000, save_process_pics=False): + self.src_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','背景') + self.src_PALETTE = np.array([[255,91,0],[255,234,0],[85, 107, 179],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[0,0,0]]) + self.src_CLASSES_NUM = np.shape(self.src_CLASSES)[0] + + self.Annotate_CLASSES = Annotate_CLASSES # 待分类的类 + self.Annotate_PALETTE = np.array(Annotate_PALETTE) # 每一类的像素直 + self.Annotate_CLASSES_NUM = np.shape(Annotate_CLASSES)[0] # 类数量 + + self.save_process_pics = save_process_pics # 保存中间过程图片 + + self.src_label_fold = src_label_fold # 原始标签图片 保存位置 + self.save_pro_label_fold = save_pro_label_fold # 优化后标签图片 保存位置 + self.save_GT_label_fold = save_GT_label_fold # GT标签图片 保存位置 + + self.ori_img_folder = ori_img_folder # 最原始手术图片 保存位置 + self.res_label_folder = res_label_folder # 训练出来的label 保存位置 + self.save_merge_pic_folder = save_merge_pic_folder # 融合图像保存位置 + + self.pro_append_name = pro_append_name # 优化后标签图片后缀 + self.GT_append_name = GT_append_name # GT标签图片后缀 + self.GT_channel = GT_channel # GT标签图片通道数 + + self.Max_width = Max_width # 最大图片宽度(匹配时候用) + self.pic_type = pic_type # 图片类型 + self.back_gnd_color = back_gnd_color # 背景颜色 + self.first_class_color = first_class_color # 第一类上的颜色 + self.Label_Max_Search_layer=Label_Max_Search_layer # 文件夹最大搜索深度 + try: + self.labellist_src = getFileList(src_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到ori_label图片 '+str(len(self.labellist_src))+' 张图像') + except: + self.labellist_src = None + print("没有ori_label相关文件") + + try: + # print(save_pro_label_fold) + self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像') + except: + self.labellist_pro = None + print("没有pro_label相关文件") + + try: + self.imglist_src = getFileList(ori_img_folder, [], pic_type, self.Label_Max_Search_layer) + self.reslist_src = getFileList(res_label_folder, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到ori原始图片 '+str(len(self.imglist_src))+' 张图像') + print('本次执行检索到训练train_result图片 '+str(len(self.reslist_src))+' 张图像') + except: + self.imglist_src = None + self.reslist_src = None + print("没有train_result和原始图片相关文件") + + # 获取单张图片各个通路信息 + def get_single_pic_rgb(self, imgpath): + print(imgpath) + image = Image.open(imgpath).convert('RGB') # 转为RGB图片 + # 将 RGB 色值分离 + image.load() + r, g, b = image.split() + r = np.array(r) + g = np.array(g) + b = np.array(b) + return image, r, g, b + + # 将单个pro图片变成GT图片 + def Conver_pro_label_pic_2_GT_pic(self, imgpath, imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + image, r,g,b = self.get_single_pic_rgb(imgpath) + + result_gt = np.ones(np.shape(image))*self.back_gnd_color # 初始化填充内容为back_gnd_color + gt_number = self.first_class_color # 第一类上色颜色确定 + + # 遍历所有待识别颜色 + for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in self.Annotate_PALETTE: + # 查找三原色匹配位置 + locate_r = np.where( r == Annotate_PALETTE_r ) + locate_g = np.where( g == Annotate_PALETTE_g ) + locate_b = np.where( b == Annotate_PALETTE_b ) + + # 查找都匹配位置(交集) + # 将矩阵换一种表示形式 + locate_r = np.array(locate_r[0]) * Max_width + np.array(locate_r[1]) + locate_g = np.array(locate_g[0]) * Max_width + np.array(locate_g[1]) + locate_b = np.array(locate_b[0]) * Max_width + np.array(locate_b[1]) + + # 用自带函数寻找匹配项 + matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b) + matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0) + result_gt[matched[0],matched[1], :] = gt_number + gt_number = gt_number + 1 + + # 输出GT图片 + if(int(self.GT_channel) == 1): + result_gt = result_gt[:,:,0] + elif(int(self.GT_channel) == 3): + result_gt = cv2.cvtColor(np.float32(result_gt), cv2.COLOR_RGB2BGR) # rgb颜色互换 + else: + print("GT_channel 必须为1或3") + quit + try: # 新建文件夹 + os.mkdir(self.save_GT_label_fold) + except: + print("已有"+self.save_GT_label_fold) + save_dir = os.path.join(self.save_GT_label_fold, os.path.splitext(imgname)[0]+self.GT_append_name+'.'+self.pic_type) + cv2.imwrite(save_dir, result_gt) + print("GT图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将处理好的图片转化为GT图片 + def Conver_pro_label_pic_2_GT_pic_all(self): + print("\033[33m**** 进行转换将Pro_label_pic转换为GT_label_pic ****\033[0m") + print("\033[33mPro_label_pic存储位置为:\033[0m", self.save_pro_label_fold) + print("\033[33mGT_label_pic生成位置为:\033[0m", self.save_GT_label_fold) + try: + # print(save_pro_label_fold) + self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像') + except: + self.labellist_pro = None + print("没有pro_label相关文件") + try: + os.mkdir(self.save_GT_label_fold) # 新建存储文件夹 + except: + print("已有"+self.save_GT_label_fold) + + # 指定最大进程数为 3 + max_processes = 20 + # 创建Pool对象 + pool = multiprocessing.Pool(processes=max_processes) + # 创建并启动进程 + args_list1 = [] + args_list2 = [] + + # 遍历整个文件夹 + for imgpath in self.labellist_pro: + imgname= os.path.splitext(os.path.basename(imgpath))[0].replace(self.pro_append_name,"") + args_list1.append(imgpath) + args_list2.append(imgname) + args_list = zip(args_list1, args_list2) + # 使用进程池并行执行任务 + pool.starmap(self.Conver_pro_label_pic_2_GT_pic, args_list) + # 关闭进程池 + pool.close() + pool.join() + + def Conver_ori_label_pic_2_pro_pic(self, imgpath, imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + image, r, g, b = self.get_single_pic_rgb(imgpath) + result_pro = np.zeros(np.shape(image)) # pro图片初始化,默认图片背景为0.0.0 + + # 生成距离初始矩阵 width*height*num_of_classes + Dis_mat = np.zeros((np.shape(image)[0], np.shape(image)[1], self.src_CLASSES_NUM)) + + # 遍历所有类 + i = 0 # 顺序指示物 + # 遍历寻找最短距离是第几个内容 + for palette in self.src_PALETTE: + Dis_mat[:,:,i] = np.abs((r - palette[0])) + np.abs((g - palette[1])) + np.abs((b - palette[2])) + i = i + 1 + Min_Dis_mat = np.argmin(Dis_mat, axis = 2) + + # 给图片上色 + for number_class in range(self.src_CLASSES_NUM): + # 查找图片种类 + loc_of_class_pic = np.where(Min_Dis_mat == number_class) + # 否则给图片上对应颜色 + result_pro[loc_of_class_pic[0], loc_of_class_pic[1], :] = self.src_PALETTE[number_class] + result_pro = cv2.cvtColor(np.float32(result_pro), cv2.COLOR_RGB2BGR) # rgb颜色互换 + + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + save_dir = os.path.join(self.save_pro_label_fold, '1_颜色预处理', os.path.splitext(imgname)[0]+self.pro_append_name+'_preproc'+'.'+self.pic_type) + cv2.imwrite(save_dir, result_pro) + print("中间态-颜色图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 边界区域大小 + half_region_len=(5-1)//2 + pic_height = result_pro.shape[0] + pic_width = result_pro.shape[1] + # 2_灰度化 + gray = cv2.cvtColor(result_pro, cv2.COLOR_BGR2GRAY) + gray = (gray*255).astype(np.uint8) # cv2对图片格式要求较为严格 + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + save_dir = os.path.join(self.save_pro_label_fold, '2_灰度化', os.path.splitext(imgname)[0]+self.pro_append_name+'_gray'+'.'+self.pic_type) + cv2.imwrite(save_dir, gray) + print("中间态-灰度图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + # 边缘检测 + edges = cv2.Canny(gray, 50, 150) + # 定义膨胀核 + kernel = np.ones((3, 3), np.uint8) + # 对Canny输出图像进行膨胀 + edges = cv2.dilate(edges, kernel, iterations=1) + + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + save_dir = os.path.join(self.save_pro_label_fold, '3_边缘化', os.path.splitext(imgname)[0]+self.pro_append_name+'_edge'+'.'+self.pic_type) + cv2.imwrite(save_dir, edges) + print("中间态-边缘图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 查找边缘 + nonzero_idx = np.array(np.nonzero(edges)).T + # nonzero_idx, counts = np.unique(nonzero_idx, axis=0, return_counts=True) + print("边缘个数:",np.shape(nonzero_idx)[0]) + + for k in range(len(nonzero_idx)): + i = nonzero_idx[k][0] + j = nonzero_idx[k][1] + # 遍历周围3*3的区域 + # 找到与当前像素值附近5*5*3(channel)的像素,并将矩阵变为25(5*5)*3(channel) + if i <= half_region_len: + i = half_region_len + elif i >= pic_height-half_region_len: + i = pic_height-half_region_len + if j <= half_region_len: + j = half_region_len + elif j >= pic_width-half_region_len: + j = pic_width-half_region_len + region_x = i + region_y = j + region = np.array(result_pro[region_x-half_region_len:region_x+half_region_len, region_y-half_region_len:region_y+half_region_len,:]).reshape(-1, 3) + # 计算唯一列及其出现次数 + # print(region_x-half_region_len, region_x+half_region_len, region_y-half_region_len, region_y+half_region_len) + unique_columns, counts = np.unique(region, axis=0, return_counts=True) + # 找出出现次数最多的列的索引 + max_count_index = np.argmax(counts) + # 将其赋值为出现次数最多的列 + result_pro[i, j, :] = unique_columns[max_count_index] + # result_pro[i, j, :] = [0,0,0] + + save_dir = os.path.join(self.save_pro_label_fold, os.path.splitext(imgname)[0]+self.pro_append_name+'.'+self.pic_type) + print("Pro图片已保存", save_dir) + cv2.imwrite(save_dir, result_pro) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将原始src图片转化为处理好的pro图片 + def Conver_ori_label_pic_2_pro_pic_all(self): + print("\033[33m**** 进行转换将Ori_label_pic转换为Pro_label_pic ****\033[0m") + print("\033[33mOri_label_pic存储位置为:\033[0m", self.src_label_fold) + print("\033[33mPro_label_pic生成位置为:\033[0m", self.save_pro_label_fold) + # 输出颜色预处理图片 + try: + os.mkdir(self.save_pro_label_fold) # 新建存储文件夹 + except: + print("已有"+self.save_pro_label_fold) + if(self.save_process_pics == True): + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '1_颜色预处理')) # 新建存储1_颜色预处理文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '1_颜色预处理')) + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '2_灰度化')) # 新建存储2_灰度化文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '2_灰度化')) + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '3_边缘化')) # 新建存储1_颜色预处理文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '3_边缘化')) + + # 指定最大进程数为 20,多参数函数并行 + max_processes = 20 + # 创建Pool对象 + pool = multiprocessing.Pool(processes=max_processes) + # 创建并启动进程 + args_list1 = [] + args_list2 = [] + + # 遍历整个文件夹 + for imgpath in self.labellist_src: + try: + imgname= os.path.splitext(os.path.basename(imgpath))[0].replace(self.pro_append_name,"") + print("Processing: ", imgname, "...") + # self.Conver_ori_label_pic_2_pro_pic(imgpath, imgname)s + # args_list.append({'imgpath': imgpath, 'imgname': imgname}) + args_list1.append(imgpath) + args_list2.append(imgname) + except: + os.system("echo "+imgname+" >> error_1.txt") + args_list = zip(args_list1, args_list2) + # 使用进程池并行执行任务 + pool.starmap(self.Conver_ori_label_pic_2_pro_pic, args_list) # 使用starmap进行多参数并行 + # 关闭进程池 + pool.close() + pool.join() + + # 图片堆叠 + def Merge_ori_pic_and_label_pic(self, res_img_path, res_imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + ori_img_path = os.path.join(self.ori_img_folder, res_imgname+'.'+self.pic_type) + if not os.path.exists(ori_img_path): + print("****照片不存在:****", ori_img_path) + return -1 + ori_image, ori_r, ori_g, ori_b = self.get_single_pic_rgb(ori_img_path) + res_image, res_r, res_g, res_b = self.get_single_pic_rgb(res_img_path) + + merge_img = np.array(ori_image) # merge图片初始化,默认图片背景为0.0.0 + + # 遍历所有待识别颜色 + for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in self.Annotate_PALETTE: + # 查找三原色匹配位置 + locate_r = np.where( res_r == Annotate_PALETTE_r ) + locate_g = np.where( res_g == Annotate_PALETTE_g ) + locate_b = np.where( res_b == Annotate_PALETTE_b ) + + # 查找都匹配位置(交集) + # 将矩阵换一种表示形式 + locate_r = np.array(locate_r[0]) * self.Max_width + np.array(locate_r[1]) + locate_g = np.array(locate_g[0]) * self.Max_width + np.array(locate_g[1]) + locate_b = np.array(locate_b[0]) * self.Max_width + np.array(locate_b[1]) + + # 用自带函数寻找匹配项 + matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b) + matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0) + merge_img[matched[0],matched[1], 0] = Annotate_PALETTE_r + merge_img[matched[0],matched[1], 1] = Annotate_PALETTE_g + merge_img[matched[0],matched[1], 2] = Annotate_PALETTE_b + + # 转成cv2形式 + merge_img = cv2.cvtColor(np.float32(merge_img), cv2.COLOR_RGB2BGR) + + try: # 新建文件夹 + os.mkdir(self.save_merge_pic_folder) + except: + print("已有"+self.save_merge_pic_folder) + save_dir = os.path.join(self.save_merge_pic_folder, os.path.splitext(res_imgname)[0]+'.'+self.pic_type) + cv2.imwrite(save_dir, merge_img) + print("Merge图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将label图片与原图片重合 + def Merge_ori_pic_and_label_pic_all(self): + # 遍历整个文件夹 + for res_img_path in self.reslist_src: + res_imgname = os.path.splitext(os.path.basename(res_img_path))[0].replace(self.pro_append_name,"") + print("Processing: ", res_imgname, "...") + self.Merge_ori_pic_and_label_pic(res_img_path, res_imgname) + +if __name__ == "__main__": + + Annotate_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹') # 待分类的类 + Annotate_PALETTE = [[255,91,0],[255,234,0],[85, 107, 179],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255]]# [[255,91,0],[255,234,0]] # 每一类的像素直 + + # 创建参数解析器 + parser = argparse.ArgumentParser(description='Process some files.') + # 添加参数选项 + parser.add_argument('-src_fold', dest='src_label_fold', default='', help='source label folder') + parser.add_argument('-save_pro_fold', dest='save_pro_label_fold', default='./save_pro_label_fold', help='processed label folder') + parser.add_argument('-save_GT_fold', dest='save_GT_label_fold', default='./save_GT_label_fold', help='ground truth folder') + parser.add_argument('-fold_search_depth', dest='Label_Max_Search_layer', default='1000', type=int, help='Folder Search Depth') + parser.add_argument('-pro_suffix_name', dest='pro_append_name', default='_label', help='Pro file suffix') + parser.add_argument('-GT_suffix_name', dest='GT_append_name', default='_gtFine_labelTrainIds', help='GT file suffix') + parser.add_argument('-GT_channel', dest='GT_channel', default='1', type=int, help='GT file channel(1 or 3)') + parser.add_argument('-back_gnd_color', dest='back_gnd_color', default='0', type=int, help='Color of "Back ground"(0 or 255)') + parser.add_argument('-first_class_color', dest='first_class_color', default='1', type=int, help='Color of "First Class"') + parser.add_argument('-pic_type', dest='pic_type', default='png', help='type of picture(Do not add ".")') + parser.add_argument('-Max_width', dest='Max_width', default='10000', type=int, help='Max width of picture') + parser.add_argument('-Rebuild_from', dest='Rebuild_from', default='label', help='Source to Rebuild Labels(label/pro)') + parser.add_argument('-Rebuild_to', dest='Rebuild_to', default='GT', help='Destination of Rebuild Labels(pro/GT)') + parser.add_argument('-save_process_pics', dest='save_process_pics', default='False', help='Save the processed pics(e.g.Gray_pics,Color_pics) in generating pro_pics') + + # 解析命令行参数 + args = parser.parse_args() + + src_label_fold = args.src_label_fold + save_pro_label_fold = args.save_pro_label_fold + save_GT_label_fold = args.save_GT_label_fold + Label_Max_Search_layer = args.Label_Max_Search_layer + pro_append_name = args.pro_append_name + GT_append_name = args.GT_append_name + GT_channel = args.GT_channel + back_gnd_color = args.back_gnd_color + first_class_color = args.first_class_color + pic_type = args.pic_type + Max_width = args.Max_width + Rebuild_from = args.Rebuild_from + Rebuild_to = args.Rebuild_to + save_process_pics = args.save_process_pics + + + try: # 遍历文件深度,最小为1 + Label_Max_Search_layer=int(Label_Max_Search_layer) + except: + Label_Max_Search_layer=1000 + try: # GT标签图片通道数 + GT_channel=int(GT_channel) + except: + GT_channel=1 + try: # 背景颜色(背景选择0或255) + back_gnd_color=int(back_gnd_color) + except: + back_gnd_color=0 + try: # 第一类上的颜色(如果背景为0,选择1;) + first_class_color=int(first_class_color) + except: + first_class_color=1 + try: # 最大图片宽度(匹配时候用) + Max_width=int(Max_width) + except: + Max_width=10000 + if(save_process_pics.lower() == 'false'): + save_process_pics = False + elif(save_process_pics.lower() == 'true'): + save_process_pics = True + else: + save_process_pics = False + + D = Deal_image(Annotate_CLASSES=Annotate_CLASSES, Annotate_PALETTE=Annotate_PALETTE, src_label_fold=src_label_fold, save_pro_label_fold=save_pro_label_fold, save_GT_label_fold=save_GT_label_fold, GT_channel=GT_channel, pro_append_name=pro_append_name, GT_append_name=GT_append_name, back_gnd_color=back_gnd_color, first_class_color=first_class_color, pic_type=pic_type, Max_width=Max_width, Label_Max_Search_layer=Label_Max_Search_layer, save_process_pics=save_process_pics) + # print(D.src_CLASSES_NUM) + if Rebuild_from == 'label': + # 1.先将所有原始图片转为pro图片 + D.Conver_ori_label_pic_2_pro_pic_all() + pass + if Rebuild_to == 'GT': + # 2.再将pro图片转为GT图片 + D.Conver_pro_label_pic_2_GT_pic_all() + pass \ No newline at end of file diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/4_rebuild_labels.sh b/DataSet_Own/1. 图片预处理(内含使用手册)/4_rebuild_labels.sh new file mode 100755 index 0000000..d514879 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/4_rebuild_labels.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 l [ -h ]" + echo "对label图片进行处理及转化(-l不能为空) " + echo "-l:原始label的路径,-h:帮助" + echo "e.g. 4_rebuild_labels.sh -l ./C组标注图片" + echo "接下来一路回车就ok" +} + +ori_label_directorys="" + +while getopts "hl:" opt; do + case $opt in + h) + usage + exit 0 + ;; + l) + ori_label_directorys=$OPTARG + ;; + *) + echo -e '\033[31m!!! Error, Illegal input !!!\033[0m' + usage + exit 1 + ;; + esac +done + +# 判断输入地址是否为空 +if [ -z "$ori_label_directorys" ]; then + echo -e "\033[31m输入地址 -i -l 存在空地址\033[0m" + usage + exit 1 +fi + +# 地址转化 +ori_label_directory=$(readlink -f "$ori_label_directorys") +if [ -z "$ori_label_directory" ]; then + echo -e "\033[31m无法解析地址,程序退出\033[0m" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi +if [ ! -d "$ori_label_directory" ]; then + echo -e "\033[31mlabel目录不存在,程序退出\033[0m" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi + +echo -e "\033[32m_____ 4_rebuild_labels.sh _____\033[0m" + + +echo -n "请选择label图片搜索深度(默认为1):" +read -r fold_search_depth +if [ -z $fold_search_depth ]; then + fold_search_depth='1' +fi + +save_pro_folds=""${ori_label_directory%/}"_pro_label_fold" # 去掉末尾的足/ +save_pro_fold=$(readlink -f "$save_pro_folds") +echo -n "请选择初步处理label后label_pro图片存储位置(默认为$save_pro_fold):" +read -r save_pro_folds +if [ -z $save_pro_folds ]; then + save_pro_folds=""${ori_label_directory%/}"_pro_label_fold" +fi +save_pro_fold=$(readlink -f "$save_pro_folds") + +echo -n "请选择label_pro图片后缀(默认为\"_label\"):" +read -r pro_suffix_name +if [ -z $pro_suffix_name ]; then + pro_suffix_name='_label' +fi + +save_GT_folds=""${ori_label_directory%/}"_GT_label_fold" +save_GT_fold=$(readlink -f "$save_GT_folds") +echo -n "请选择处理label_pro后label_GT图片存储位置(默认为$save_GT_fold):" +read -r save_pro_folds +if [ -z $save_pro_folds ]; then + save_GT_folds=""${ori_label_directory%/}"_GT_label_fold" +fi +save_GT_fold=$(readlink -f "$save_GT_folds") + +echo -n "请选择label_GT图片后缀(默认为\"_gtFine_labelTrainIds\"):" +read -r GT_suffix_name +if [ -z $GT_suffix_name ]; then + GT_suffix_name='_gtFine_labelTrainIds' +fi + +echo -n "请选择label_GT图片通道数(1或3,默认为1):" +read -r GT_channel +if [ -z $GT_channel ]; then + GT_channel='1' +fi +if [[ $GT_channel != '1' && $GT_channel != '3' ]]; then + echo -e "\033[35mGT_channel只能为1或3,输入有误,将其默认变为1\033[0m" + GT_channel='1' +fi + +echo -n "请选择GT图片背景颜色(0或255,默认为0(黑色))):" +read -r back_gnd_color +if [ -z $back_gnd_color]; then + back_gnd_color='0' +fi + +echo -n "请选择GT图片中第一类的颜色(黑色背景(0)下默认为1,白色背景(255)下默认为0):" +read -r first_class_color +if [ -z $first_class_color]; then + if [ $back_gnd_color == '255' ]; then + first_class_color='0' + else + first_class_color='1' + fi +fi + +echo -n "请选择图片类型(png或jpg,默认为png(没有\".\"))):" +read -r pic_type +if [ -z $pic_type ]; then + pic_type='png' +fi + +echo -n "请选择重建起始目录(label或pro,默认为label):" +read -r Rebuild_from +if [ -z $Rebuild_from ]; then + Rebuild_from='label' +fi +if [[ $Rebuild_from != 'label' && $Rebuild_from != 'pro' ]]; then + echo -e "\033[35mRebuild_from只能为label或pro,输入有误,将其默认变为label\033[0m" + Rebuild_from='label' +fi + +echo -n "请选择重建最终目标(pro或GT,默认为GT):" +read -r Rebuild_to +if [ -z $Rebuild_to ]; then + Rebuild_to='GT' +fi +if [[ $Rebuild_to != 'GT' && $Rebuild_to != 'pro' ]]; then + echo -e "\033[35mRebuild_to只能为GT或pro,输入有误,将其默认变为GT\033[0m" + Rebuild_to='GT' +fi + +echo -n "请选择是否保存pro图片生成中间状态(e.g.灰度图等)(false或true,默认为false):" +read -r save_process_pics +if [ -z $save_process_pics ]; then + save_process_pics='false' +fi +if [[ $save_process_pics != 'true' && $save_process_pics != 'false' ]]; then + echo -e "\033[35msave_process_pics只能为true或false,输入有误,将其默认变为false\033[0m" + save_process_pics='false' +fi + +# 获取当前脚本的路径和名称 +script_path=$(dirname "$0") +# 将当前目录更改为脚本所在的路径 +cd "$script_path" + +# 激活conda环境 +source /home/"$USER"/miniconda/bin/activate Deal_pics +echo -e "\033[35m运行:\033[0mpython 4_deal_labels.py -src_fold $ori_label_directory -save_pro_fold $save_pro_fold -save_GT_fold $save_GT_fold -fold_search_depth $fold_search_depth -pro_suffix_name $pro_suffix_name -GT_suffix_name $GT_suffix_name -GT_channel $GT_channel -back_gnd_color $back_gnd_color -first_class_color $first_class_color -pic_type $pic_type -Rebuild_from $Rebuild_from -Rebuild_to $Rebuild_to -save_process_pics $save_process_pics " +echo "" +python 4_deal_labels.py -src_fold $ori_label_directory -save_pro_fold $save_pro_fold -save_GT_fold $save_GT_fold -fold_search_depth $fold_search_depth -pro_suffix_name $pro_suffix_name -GT_suffix_name $GT_suffix_name -GT_channel $GT_channel -back_gnd_color $back_gnd_color -first_class_color $first_class_color -pic_type $pic_type -Rebuild_from $Rebuild_from -Rebuild_to $Rebuild_to -save_process_pics $save_process_pics + +echo "4_rebuild_label_pics.sh重构完毕" + diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/5_TOOL_stack_pics.sh b/DataSet_Own/1. 图片预处理(内含使用手册)/5_TOOL_stack_pics.sh new file mode 100755 index 0000000..7bde03e --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/5_TOOL_stack_pics.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 -i -l -r [ -a -p -s -h]" + echo "对image图片和label图片进行匹配(-i、-l -r均不能为空)(-p -s默认为空"" -a默认为\"0.3\") " + echo "-i:原始image的路径,-l:原始label的路径,-p:前缀内容,-s:后缀内容(不用管文件后缀名),-h:帮助" + echo "e.g. 5_TOOL_stack_pics.sh -i ./C组未标注 -l ./C组标注图片 -r ./C组result_0.3透明度 -a 0.3 -p Group_C_ -s _label" +} + +ori_image_directorys="" +ori_label_directorys="" +stack_result_directorys="" +prefix="" +suffix="" +alpha="0.3" + +while getopts "hl:i:r:p:s:a:" opt; do + case $opt in + h) + usage + exit 0 + ;; + i) + ori_image_directorys=$OPTARG + ;; + l) + ori_label_directorys=$OPTARG + ;; + p) + prefix=$OPTARG + ;; + s) + suffix=$OPTARG + ;; + r) + stack_result_directorys=$OPTARG + ;; + a) + alpha=$OPTARG + ;; + *) + echo -e '\033[31m!!! Error, Illegal input !!!\033[0m' + usage + exit 1 + ;; + esac +done + +# 判断输入地址是否为空 +if [ -z "$ori_label_directorys" ] || [ -z "$ori_image_directorys" ] || [ -z "$stack_result_directorys" ]; then + echo -e "\033[31m输入地址 -i -l -z 存在空地址\033[0m" + usage + exit 1 +fi + +# 地址转化 +ori_image_directory=$(readlink -f "$ori_image_directorys") +ori_label_directory=$(readlink -f "$ori_label_directorys") +stack_result_directory=$(readlink -f "$stack_result_directorys") +if [ -z "$ori_label_directory" ] || [ -z "$ori_image_directory" ]|| [ -z "$stack_result_directory" ]; then + echo "image、label、result存在无法解析地址,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + echo -e "\033[31mori_label_directory\033[0m: $stack_result_directorys" + exit 1 +fi +if [ ! -d "$ori_label_directory" ] || [ ! -d "$ori_image_directory" ]; then + echo "image、label两目录有一个不存在,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directory" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi + +# 获取当前脚本的路径和名称 +script_path=$(dirname "$0") +# 将当前目录更改为脚本所在的路径 +cd "$script_path" + +# 激活conda环境 +source /home/"$USER"/miniconda/bin/activate Deal_pics + +echo -e "\033[32m_____ 5_TOOL_stack_pics.sh _____\033[0m" +echo -e "\033[33mimage所在文件夹为$ori_image_directory\nlable所在文件夹为$ori_label_directory\033[0m" +# 遍历label目录 +for file_path in "$ori_label_directory"/*; do + # 判断是否是文件 + if [[ -f "$file_path" ]]; then + file_name=$(basename "$file_path") + # 判断文件名是否符合规范 + if [[ "$file_name" =~ .*\.(jpg|png|bmp|JPG|PNG|BMP) ]]; then # 判断是否有为图片 + # if [[ "$file_name" =~ "$prefix".*"$suffix".*\.(jpg|png|bmp|JPG|PNG|BMP)$ ]]; then # 判断是否有满足要求的文件名 + # 抽取文件名(有前缀、后缀的抽取前缀、后缀里面的,没有的返回整个) + if [ -z $prefix ];then + file_name_extract=$(echo $file_name | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_extract=$(echo $file_name | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + # 从label目录中看是否有此文件 + file_name_other=$(ls $ori_image_directory | grep $file_name_extract) + file_name_other=$(echo "$(echo "$file_name_other" | sed '/^$/d')" | head -n1) # 提取出文件名 + # 如果另一个目录没有此文件的话 + if [ -z "$file_name_other" ]; then + echo "$file_name label中对应内容未在$ori_image_directory搜索到" + # 建立相关存储文件夹 + if [ ! -d "$ori_label_directory/Not_pair_pics" ]; then + mkdir -p "$ori_label_directory/Not_pair_pics" # 建立存储文件夹 + fi + # 移动相关文件 + cp "$ori_label_directory/$file_name" "$ori_label_directory/Not_pair_pics" + echo "$file_name" >> "$ori_label_directory/Not_pair_pics/not_pair.txt" + else # 如果另一个目录有此配对文件的话,则运行相关程序 + echo "image中的$file_name_other,与lable中的$file_name" + mkdir -p "$stack_result_directory" + python 5_stack_picture.py "$ori_image_directory/$file_name_other" "$ori_label_directory/$file_name" "$stack_result_directory" "$alpha" + echo "" + fi + + fi + else + echo "$file_path不是文件" + fi +done diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/5_stack_picture.py b/DataSet_Own/1. 图片预处理(内含使用手册)/5_stack_picture.py new file mode 100755 index 0000000..7fcf130 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/5_stack_picture.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import cv2, os, sys + +def Stack_pic(Background_path, Overlay_path, Result_dir, alpha=0.3): + # 读取两张没有alpha通道的图片 + img1 = cv2.imread(Background_path) # 底层图片 + img2 = cv2.imread(Overlay_path) # 顶层图片 + + Result_name = os.path.splitext(os.path.basename(Background_path))[0] + + # 将img2调整为与img1大小相同 + img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0])) + + # 将img2的透明度调整为20% + overlay_alpha = alpha + + # 将img2叠加到img1上 + overlay = cv2.addWeighted(img1, 1 - overlay_alpha, img2, overlay_alpha, 0) + + # 保存结果 + if not os.path.exists(Result_dir): + os.makedirs(Result_dir) + cv2.imwrite(os.path.join(Result_dir, Result_name+'.png'), overlay) + print("堆叠图片写入地址:", os.path.join(Result_dir, Result_name+'.png')) + + +if __name__ == '__main__': + Background_path = sys.argv[1] # 背景所在路径 + Overlay_path = sys.argv[2] # 上层图片所在路径 + Result_dir = sys.argv[3] # 结果所在目录 + # 透明度,默认为0.3 + try: + alpha = float(sys.argv[4]) + if(alpha > 1 or alpha < 0): + print("alpha 透明度输入不正确,其值应该在0~1之间") + alpha = 0.3 + except: + alpha = 0.3 + # 进行对叠程序 + Stack_pic(Background_path, Overlay_path, Result_dir, alpha) \ No newline at end of file diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/6_TOOL_stitch_pics.sh b/DataSet_Own/1. 图片预处理(内含使用手册)/6_TOOL_stitch_pics.sh new file mode 100755 index 0000000..594e177 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/6_TOOL_stitch_pics.sh @@ -0,0 +1,167 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 -i -l -r [ -p -s -h]" + echo "对image图片和label图片进行拼接(-i -r不能为空)(-l为填写项-p -s默认为空"") " + echo "-i:原始image的路径,-l:拼接label的路径,-p:前缀内容,-s:后缀内容(不用管文件后缀名),-h:帮助" + echo "e.g. 6_TOOL_stitch_pics.sh -i ./C组未标注 -l ./C组标注图片 -r ./C组result_stitch -p Group_C_ -s _label" +} + +ori_image_directorys="" +ori_label_directorys="" +stitch_result_directorys="" +prefix="" +suffix="" + +while getopts "hl:i:r:p:s:a:" opt; do + case $opt in + h) + usage + exit 0 + ;; + i) + ori_image_directorys=$OPTARG + ;; + l) + ori_label_directorys=$OPTARG + ;; + p) + prefix=$OPTARG + ;; + s) + suffix=$OPTARG + ;; + r) + stitch_result_directorys=$OPTARG + ;; + *) + echo -e '\033[31m!!! Error, Illegal input !!!\033[0m' + usage + exit 1 + ;; + esac +done + +# 如果堆叠地址为空,生成默认堆叠地址 +if [ -z "$stitch_result_directorys" ]; then + stitch_result_directorys=""$ori_label_directorys"_拼接" + echo -n "请输入堆叠结果存储目录(默认为$stitch_result_directorys):" + read -r temp + if [ ! -z $temp ]; then + stitch_result_directorys=$temp + fi +fi + +# 判断输入地址是否为空 +if [ -z "$ori_label_directorys" ] || [ -z "$ori_image_directorys" ] || [ -z "$stitch_result_directorys" ]; then + echo -e "\033[31m输入地址 -i -l -z 存在空地址\033[0m" + usage + exit 1 +fi + +# 地址转化 +ori_image_directory=$(readlink -f "$ori_image_directorys") +ori_label_directory=$(readlink -f "$ori_label_directorys") +stitch_result_directory=$(readlink -f "$stitch_result_directorys") +if [ -z "$ori_label_directory" ] || [ -z "$ori_image_directory" ]|| [ -z "$stitch_result_directory" ]; then + echo "image、label、result存在无法解析地址,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + echo -e "\033[31mori_label_directory\033[0m: $stitch_result_directorys" + exit 1 +fi +if [ ! -d "$ori_label_directory" ] || [ ! -d "$ori_image_directory" ]; then + echo "image、label两目录有一个不存在,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directory" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi + +echo -e "\033[32m_____ 6_TOOL_stitch_pics.sh _____\033[0m" + +# 图片相对位置 +PS3='Please enter your choice:' +options=("Imge ↑ Label ↓" "Label ↑ Imge ↓" "Imge ← Label →" "Label ← Imge →" ) +echo "请选择图片与Label的相对位置,默认为1.\"Imge↑Label↓\"" +select opt in "${options[@]}" + do + case $opt in + # 这里面返回的结果都是img在前,label在后的 + "Imge ↑ Label ↓") + relative_pos="Img_up_label_down" + break + ;; + "Label ↑ Imge ↓") + relative_pos="Img_down_label_up" + break + ;; + "Imge ← Label →") + relative_pos="Img_left_label_right" + break + ;; + "Label ← Imge →") + relative_pos="Img_right_label_left" + break + ;; + *) + echo "Set to default 1.\"Imge↑Label↓\"" + relative_pos="Img_up_label_down" + break + ;; + esac +done + +# 获取当前脚本的路径和名称 +script_path=$(dirname "$0") +# 将当前目录更改为脚本所在的路径 +cd "$script_path" + +# 激活conda环境 +source /home/"$USER"/miniconda/bin/activate Deal_pics + +echo -e "\033[33mimage所在文件夹为$ori_image_directory\nlable所在文件夹为$ori_label_directory\033[0m" + +# 遍历label目录 +for file_path in "$ori_label_directory"/*; do + # 判断是否是文件 + if [[ -f "$file_path" ]]; then + file_name=$(basename "$file_path") + # 判断文件名是否符合规范 + if [[ "$file_name" =~ .*\.(jpg|png|bmp|JPG|PNG|BMP) ]]; then # 判断是否有为图片 + # if [[ "$file_name" =~ "$prefix".*"$suffix".*\.(jpg|png|bmp|JPG|PNG|BMP)$ ]]; then # 判断是否有满足要求的文件名 + # 抽取文件名(有前缀、后缀的抽取前缀、后缀里面的,没有的返回整个) + if [ -z $prefix ];then + file_name_extract=$(echo $file_name | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_extract=$(echo $file_name | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + # 从label目录中看是否有此文件 + file_name_other=$(ls $ori_image_directory | grep $file_name_extract) + file_name_other=$(echo "$(echo "$file_name_other" | sed '/^$/d')" | head -n1) # 提取出文件名 + # 如果另一个目录没有此文件的话 + if [ -z "$file_name_other" ]; then + echo "$file_name label中对应内容未在$ori_image_directory搜索到" + # 建立相关存储文件夹 + if [ ! -d "$ori_label_directory/Not_pair_pics" ]; then + mkdir -p "$ori_label_directory/Not_pair_pics" # 建立存储文件夹 + fi + # 移动相关文件 + cp "$ori_label_directory/$file_name" "$ori_label_directory/Not_pair_pics" + echo "$file_name" >> "$ori_label_directory/Not_pair_pics/not_pair.txt" + else # 如果另一个目录有此配对文件的话,则运行相关程序 + echo "image中的$file_name_other,与lable中的$file_name" + if [ ! -d "$stitch_result_directory" ]; then + echo "创建stitch_result_directory存储文件夹" + echo -e "\033[35运行:\033[0m mkdir -p $stitch_result_directory" + mkdir -p $stitch_result_directory + fi + echo -e "\033[35运行:\033[0mpython 6_stitch_picture.py "$ori_image_directory/$file_name_other" "$ori_label_directory/$file_name" "$stitch_result_directory" "$relative_pos"" + python 6_stitch_picture.py "$ori_image_directory/$file_name_other" "$ori_label_directory/$file_name" "$stitch_result_directory" "$relative_pos" + echo "" + fi + + fi + else + echo "$file_path不是文件" + fi +done diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/6_stitch_picture.py b/DataSet_Own/1. 图片预处理(内含使用手册)/6_stitch_picture.py new file mode 100755 index 0000000..d28cfb0 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/6_stitch_picture.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import cv2, os, sys, re + +def Stitch_pic(Ori_image_path, Ori_label_path, Result_dir, img_pos, label_pos): + + # 读取两张没有stitch_pos通道的图片 + img1 = cv2.imread(Ori_image_path) # 底层图片 + img2 = cv2.imread(Ori_label_path) # 顶层图片 + + Result_name = os.path.splitext(os.path.basename(Ori_image_path))[0] + + # 将img2调整为与img1大小相同 + if img1.shape != img2.shape: + img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0])) + + # 拼接图片 + if img_pos == 'up' and label_pos == 'down': + result = cv2.vconcat([img1, img2]) + elif img_pos == 'down' and label_pos == 'up': + result = cv2.vconcat([img2, img1]) + elif img_pos == 'left' and label_pos == 'right': + result = cv2.hconcat([img1, img2]) + elif img_pos == 'right' and label_pos == 'left': + result = cv2.hconcat([img2, img1]) + else: + RED = '\033[91m' + END = '\033[0m' + print(RED + "The input of relative_pos is wrong, img_pos is " + img_pos + " label_pos is " + label_pos + END) + os.exit() + + # 保存结果 + if not os.path.exists(Result_dir): + os.makedirs(Result_dir) + cv2.imwrite(os.path.join(Result_dir, Result_name+'.png'), result) + print("堆叠图片写入地址:", os.path.join(Result_dir, Result_name+'.png')) + + +if __name__ == '__main__': + Ori_image_path = sys.argv[1] # 背景所在路径 + Ori_label_path = sys.argv[2] # 上层图片所在路径 + Result_dir = sys.argv[3] # 结果所在目录 + relative_pos = str.lower(sys.argv[4]) + + match_up = re.search(r'up', relative_pos) + match_down = re.search(r'down', relative_pos) + match_left = re.search(r'up', relative_pos) + match_right = re.search(r'down', relative_pos) + + if match_up and match_down: + pos_up = match_up.start() + pos_down = match_down.start() + if pos_down < pos_up : + img_pos = "down" + label_pos = "up" + else: + img_pos = "up" + label_pos = "down" + elif match_left and match_right: + pos_left = match_up.start() + pos_right = match_down.start() + if pos_left < pos_right : + img_pos = "left" + label_pos = "right" + else: + img_pos = "left" + label_pos = "right" + else: + print("Either 'up'/'down' 'left'/'right' is missing or in the text.") + print("Set to default img_pos = up label_pos = down") + img_pos = "up" + label_pos = "down" + + # 进行对叠程序 + Stitch_pic(Ori_image_path, Ori_label_path, Result_dir, img_pos, label_pos) \ No newline at end of file diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/Seg_data_run.sh b/DataSet_Own/1. 图片预处理(内含使用手册)/Seg_data_run.sh new file mode 100755 index 0000000..4c71860 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/Seg_data_run.sh @@ -0,0 +1,246 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 -i -l [-h]" + echo "对image图片和label图片进行统一处理" + echo "-i:原始图片的路径,-l:原始标签的路径,-h:帮助" +} + +ori_image_directorys="" +ori_label_directorys="" +stack_result_directorys="" +stitch_result_directorys="" + +while getopts "hl:i:" opt; do + case $opt in + h) + usage + exit 0 + ;; + i) + ori_image_directorys=$OPTARG + ;; + l) + ori_label_directorys=$OPTARG + ;; + *) + echo '!!! Error, Illegal input !!!' + usage + exit 1 + ;; + esac +done + +# 判断label、image是否都为空 +echo $ori_label_directorys $ori_image_directorys +if [ -z "$ori_label_directorys" ] && [ -z "$ori_image_directorys" ]; then + echo -e "\033[31mori_label_directory、ori_image_directory不能都为空\033[0m" + usage + exit 1 +fi + +# 进行绝对路径转化 +ori_image_directory=$(readlink -f "$ori_image_directorys") +ori_label_directory=$(readlink -f "$ori_label_directorys") +if [ ! -d "$ori_label_directory" ] && [ ! -d "$ori_image_directory" ]; then + echo "image、label都不存在,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo "$ori_image_directory" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + echo "$ori_label_directory" + exit 1 +fi + +# 激活conda环境 +# source /home/"$USER"/miniconda/bin/activate Pat_infos + +# 记录脚本所在路径 +script_path=$(dirname "$0") +echo -e "\033[32m******* 开始Run运行Seg_data_run.sh批量图片处理程序 *******\033[0m" +# 进行操作选择 +while true; do + PS3='Please enter your choice: ' + options=("移动图片与重命名" "图片统一大小与类型" "image、label配对检测" "对label进行重建" "TOOL_image、label堆叠" "TOOL_image、label拼接" "Quit") + echo -e "\033[35m____ Seg_data_run选择 ____\033[0m" + echo -e "\033[35mImage所在地址:\033[0m$ori_image_directory" + echo -e "\033[35mLabel所在地址:\033[0m$ori_label_directory" + select opt in "${options[@]}" + do + case $opt in + ### 选项1 ### + "移动图片与重命名") + echo -e "\033[31mRun运行:\033[0mbash $script_path/1_rename_pics.sh -i $ori_image_directory -l $ori_label_directory" + bash $script_path/1_rename_pics.sh -i "$ori_image_directory" -l "$ori_label_directory" + echo "" + break + ;; + + ### 选项2 ### + "图片统一大小与类型") + echo -n "请输入宽度(默认1920):" + read -r width + echo -n "请输入高度(默认1080):" + read -r height + if [ -z "$width" ]; then + width=1920 + fi + if [ -z "$height" ]; then + height=1080 + fi + # 如果输入图片路径为空 + if [ -z $ori_image_directory ];then + echo -e "\033[31mRun运行:\033[0mbash $script_path/2_reformate_pics.sh -l $ori_label_directory -w $width -h $height " + bash $script_path/2_reformate_pics.sh -l $ori_label_directory -w $width -h $height + elif [ -z $ori_label_directory ];then + echo -e "\033[31mRun运行:\033[0mbash $script_path/2_reformate_pics.sh -l $ori_label_directory -w $width -h $height " + bash $script_path/2_reformate_pics.sh -i $ori_image_directory -w $width -h $height + else + echo -e "\033[31mRun运行:\033[0mbash $script_path/2_reformate_pics.sh -i $ori_image_directory -l $ori_label_directory -w $width -h $height" + bash $script_path/2_reformate_pics.sh -i $ori_image_directory -l $ori_label_directory -w $width -h $height + fi + echo "" + break + ;; + + ### 选项3 ### + "image、label配对检测") + while [ ! -d "$ori_image_directory" ]; do + echo -e "\033[31mImage地址为:$ori_image_directorys,其不存在\033[0m" + echo -n "请输入image所在地址:" + read -r ori_image_directorys + ori_image_directory=$(readlink -f "$ori_image_directorys") + done + while [ ! -d "$ori_label_directory" ]; do + echo -e "\033[31mLabel地址为:$ori_label_directorys,其不存在\033[0m" + echo -n "请输入label所在地址:" + read -r ori_label_directorys + ori_label_directory=$(readlink -f "$ori_label_directorys") + done + echo -n "请输入图片前缀文本(默认为\"\"):" + read -r prefix # 禁止转译 + echo -n "请输入图片后缀文本(非.png类后缀名,默认为\"\"):" + read -r suffix + if [ -z $preffix ];then + preffix="" + fi + if [ -z $suffix ];then + suffix="" + fi + echo -e "\033[31mRun运行:\033[0mbash $script_path/3_pair_ori_label.sh -i $ori_image_directory -l $ori_label_directory -p $prefix -s $suffix" + bash $script_path/3_pair_ori_label.sh -i $ori_image_directory -l $ori_label_directory -p "$prefix" -s "$suffix" + echo "" + break + ;; + + ### 选项4 ### + "对label进行重建") + # 判断Label目录是否存在 + while [ -z "$ori_label_directory" ]; do + echo -e "\033[31mLabel地址为:$ori_label_directorys,其存在异常\033[0m" + echo -n "请输入堆叠结果存储目录地址:" + read -r ori_label_directorys + ori_label_directory=$(readlink -f "$ori_label_directorys") + done + echo -e "\033[31mRun运行:\033[0mbash $script_path/4_rebuild_labels.sh -l $ori_label_directory" + bash $script_path/4_rebuild_labels.sh -l $ori_label_directory + echo "" + break + ;; + + ### 选项5 ### + "TOOL_image、label堆叠") + while [ ! -d "$ori_image_directory" ]; do + echo -e "\033[31mImage地址为:$ori_image_directorys,其不存在\033[0m" + echo -n "请输入image所在地址:" + read -r ori_image_directorys + ori_image_directory=$(readlink -f "$ori_image_directorys") + done + while [ ! -d "$ori_label_directory" ]; do + echo -e "\033[31mLabel地址为:$ori_label_directorys,其不存在\033[0m" + echo -n "请输入label所在地址:" + read -r ori_label_directorys + ori_label_directory=$(readlink -f "$ori_label_directorys") + done + + echo -n "请输入堆叠图片透明程度(0~1,0为最透明,默认为0.3)" + read -r alpha + echo -n "请输入图片前缀文本(默认为\"\"):" + read -r prefix + echo -n "请输入图片后缀文本(非.png类后缀名,默认为\"\"):" + read -r suffix + if [ -z "$alpha" ]; then + alpha="0.3" + fi + stack_result_directory=""$ori_label_directory"_堆叠_"$alpha"_透明度" + echo -n "请输入堆叠结果存储目录(默认为$stack_result_directory):" + read -r stack_result_directorys + # 判断堆叠目录非为空则进行转换 + if [ ! -z "$stack_result_directorys" ]; then + stack_result_directory=$(readlink -f "$stack_result_directorys") + fi + # 判断Label目录是否存在 + while [ -z "$ori_label_directory" ]; do + echo -e "\033[31mLabel地址为:$ori_label_directorys,其存在异常\033[0m" + echo -n "请输入堆叠结果存储目录地址:" + read -r ori_label_directorys + ori_label_directory=$(readlink -f "$ori_label_directorys") + done + echo -e "\033[31mRun运行:\033[0mbash $script_path/5_TOOL_stack_pics.sh -i $ori_image_directory -l $ori_label_directory -p $prefix -s $suffix -a $alpha -r $stack_result_directory" + bash $script_path/5_TOOL_stack_pics.sh -i "$ori_image_directory" -l "$ori_label_directory" -p "$prefix" -s "$suffix" -a "$alpha" -r "$stack_result_directory" + echo "" + break + ;; + + ### 选项6 图片拼接 ### + "TOOL_image、label拼接") + while [ ! -d "$ori_image_directory" ]; do + echo -e "\033[31mImage地址为:$ori_image_directorys,其不存在\033[0m" + echo -n "请输入image所在地址:" + read -r ori_image_directorys + ori_image_directory=$(readlink -f "$ori_image_directorys") + done + echo "1.label目前为\"$ori_label_directory\"" + echo -n "是否调整label所在文件夹(默认为不调整,有输入视为调整):" + read -r temp + while [ ! -z "$temp" ]; do + read -r ori_label_directorys + ori_label_directorys=$(readlink -f "$ori_label_directorys") + done + while [ ! -d "$ori_label_directory" ]; do + echo -e "\033[31mLabel地址为:$ori_label_directorys,其不存在\033[0m" + echo -n "请输入label所在地址:" + read -r ori_label_directorys + ori_label_directory=$(readlink -f "$ori_label_directorys") + done + stitch_result_directory=""$ori_label_directory"_拼接" + echo -n "2.请输入堆叠结果存储目录(默认为$stitch_result_directory):" + read -r stitch_result_directorys + # 判断堆叠目录非为空则进行转换 + if [ ! -z "$stitch_result_directorys" ]; then + stitch_result_directory=$(readlink -f "$stitch_result_directorys") + fi + + echo -n "3.请输入图片前缀文本(默认为\"\"):" + read -r prefix + echo -n "4.请输入图片后缀文本(非.png类后缀名,默认为\"\"):" + read -r suffix + + echo -e "\033[31mRun运行:\033[0mbash $script_path/6_TOOL_stitch_pics.sh -i $ori_image_directory -l $ori_label_directory -p "$prefix" -s "$suffix" -r "$stitch_result_directory"" + bash $script_path/6_TOOL_stitch_pics.sh -i "$ori_image_directory" -l "$ori_label_directory" -p "$prefix" -s "$suffix" -r "$stitch_result_directory" + exit 0 + ;; + + ### 选项7 退出 ### + "Quit") + echo -e "\033[35mSeg_data_run.sh Exiting...\033[0m" + exit 0 + ;; + + *) + echo "Invalid option: $REPLY" + echo -e "" + break + ;; + esac + done +done \ No newline at end of file diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/※1_环境安装.txt b/DataSet_Own/1. 图片预处理(内含使用手册)/※1_环境安装.txt new file mode 100755 index 0000000..44dd442 --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/※1_环境安装.txt @@ -0,0 +1,14 @@ +# 创建环境 +conda create -n Deal_pics python=3.8 +sudo apt install imagemagick +# 安装包 +conda activate Deal_pics +pip install --upgrade pip +pip install opencv-python + +# 解决BUG +# 命令:python cv2.Canny(image, 50, 150) +# 错误:cv2.error: OpenCV(4.5.5) /io/opencv/modules/imgproc/src/canny.cpp:829: error: (-215:Assertion failed) _src.depth() == CV_8U in function 'Canny' +# 错误原因:Canny函数只对0~255起作用,对0~1不起作用 +# 解决方案:image = (image*255).astype(np.uint8) + diff --git a/DataSet_Own/1. 图片预处理(内含使用手册)/※2_使用手册.txt b/DataSet_Own/1. 图片预处理(内含使用手册)/※2_使用手册.txt new file mode 100755 index 0000000..98644ee --- /dev/null +++ b/DataSet_Own/1. 图片预处理(内含使用手册)/※2_使用手册.txt @@ -0,0 +1,28 @@ +# 相关程序所在位置:./1_Preprocess_pics(已加入路径) + +1.对于图片批量重命名(允许只输入-i或-l一个参数) +1_rename_pics.sh -i -l [-h] + +2.对于图片大小批量修改(允许只输入-i或-l一个参数) +2_reformate_pics.sh -i -l [ -w -h -help] +默认:-w=1920 -h=1080 + +3.对于原始图片、标签图片进行配对(必须输入-i和-l两个参数,文件可带前缀或后缀) +3_pair_ori_label.sh -i -l [ -p -s -h] +默认:-p="" -s="" + +4.对于标签图片进行进一步处理 +※ 如果Label有变,请修改 4_deal_labels.py main 中的 Annotate_CLASSES、Annotate_PALETTE +4_rebuild_labels.sh -l [ -h ] + +5.TOOL - 对于image图像、label图片进行堆叠(透明度可调,文件可带前缀或后缀) +5_TOOL_stack_pics.sh -i -l -r [ -a -p -s -h] +默认:-a=0.3 -p="" -s="" + +6.TOOL - 对于image图像、label图像进行左右放置(文件可带前缀或后缀) +6_TOOL_stitch_pics.sh -i -l -r [ -p -s -h] + +============ 用法 ============ +1. 将原始Label与现有label进行匹配,查看差异 +bash 6_TOOL_stitch_pics.sh -i ./A_Label -l ./A_Label_pro_label_fold -r ./A_Label_Compare -s _label + diff --git a/README.md b/README.md new file mode 100644 index 0000000..050d16d --- /dev/null +++ b/README.md @@ -0,0 +1,264 @@ +# Seg 图像分割项目使用说明 + +本项目是一个多路线图像分割实验与推理工程,包含自研 `segmentation_models_pytorch` 训练流程、YOLO 分割流程、MMSegmentation 流程,以及数据预处理、视频抽帧、图片叠加和结果分析工具。 + +## 1. 项目结构 + +| 路径 | 用途 | +| --- | --- | +| `Seg_All_In_One_SegModel/` | 基于 `segmentation_models_pytorch` 的语义分割训练、推理、参数量/FLOPs/FPS 统计和输出完整性检查。 | +| `Seg_All_In_One_YoloModel/` | 基于 Ultralytics YOLO 的实例/语义分割训练、推理、热力图可视化和横向对比。 | +| `Seg_All_In_One_MMSeg/` | 基于 OpenMMLab MMSegmentation 的模型配置、训练和结果汇总流程。 | +| `Seg_All_In_One_Analysis/` | 汇总不同模型的指标、FLOPs、FPS,生成表格和 mIoU/FPS 图。 | +| `DataSet_Own/1. 图片预处理(内含使用手册)/` | 自有数据的重命名、尺寸统一、图像/标签配对、标签重建、叠加和拼接检查。 | +| `Seg_Predict_Own_Video_V2/` | 将视频按固定间隔抽帧,转换为 `DataSet_Public//images/val` 格式。 | +| `Tool-图片堆叠/` | 快速检查原图和标签是否匹配,并生成透明叠加图。 | +| `Tool-可视化/` | YOLO 标签生成、热图、FPS 等可视化辅助脚本。 | +| `Back_Up.sh` | 将算法目录同步到 `Hardisk/` 和 `Nas_BackUp_Seg/` 的本地/NAS 备份脚本。 | + +大体量目录如 `DataSet_Public/`、`DataSet_Own/` 中的数据、`BestMode_Predict_Results_DataSet_Public/`、`Hardisk/`、`Nas_BackUp_Seg/`、模型权重和训练产物不进入 Git。 + +## 2. Conda 环境 + +推荐使用独立环境 `seg_smp`。本机已有可运行的 `SMP` 环境时,最快方式是克隆它: + +```bash +conda create --name seg_smp --clone SMP -y +conda activate seg_smp +python -V +python -c "import torch; print(torch.cuda.is_available(), torch.__version__)" +``` + +从零安装时可参考: + +```bash +conda create -n seg_smp python=3.9 -y +conda activate seg_smp + +pip install torch==2.8.0+cu129 torchvision==0.23.0+cu129 --index-url https://download.pytorch.org/whl/cu129 +pip install -r requirements-seg_smp.txt + +cd Seg_All_In_One_MMSeg +pip install -v -e . +cd .. +``` + +如果使用批量脚本,默认会激活 `seg_smp`。如需临时使用旧环境: + +```bash +SEG_CONDA_ENV=SMP bash yolo_train.sh +``` + +环境验证: + +```bash +conda run -n seg_smp python -c "import torch, segmentation_models_pytorch, ultralytics, mmcv, mmengine, mmseg, cv2, albumentations; print('ok')" +``` + +## 3. 数据约定 + +SegModel 默认读取: + +```text +DataSet_Public// + images/train + images/val + labels_GT/train + labels_GT/val +``` + +YOLO 默认读取: + +```text +DataSet_Public// + images/train + images/val + labels/train + labels/val +``` + +切换数据集时: + +- SegModel:修改 `Seg_All_In_One_SegModel/config.py` 中的 `DATA_DIR`、`OUTPUTS_DIR`、`PREDICT_BEST_MODEL_DIR`、类别和图像尺寸。 +- YOLO:修改 `Seg_All_In_One_YoloModel/dataset.yaml` 中的 `path`、`train`、`val`、`test`、`names`;必要时修改 `yolo_config.py` 的训练参数。 +- MMSeg:按 `Seg_All_In_One_MMSeg/※使用手册/※2025_9_23_MMSeg使用手册` 生成数据集和算法配置。 + +## 4. SegModel 使用方式 + +```bash +cd Seg_All_In_One_SegModel +conda activate seg_smp + +# 单模型训练 +CUDA_VISIBLE_DEVICES=0 python train.py -a Unet + +# 批量训练 +bash train.sh + +# 单模型推理 +CUDA_VISIBLE_DEVICES=0 python 1_predict.py -a Unet + +# 批量推理 +bash predict.sh + +# 参数量、FLOPs、FPS +CUDA_VISIBLE_DEVICES=0 python 2_predict_params_and_FLOPs_V2.py + +# 检查预测 raw mask 是否齐全 +python 1_predict_raw_masks_check.py +``` + +可选模型包括: + +```text +Unet, UnetPlusPlus, FPN, PSPNet, DeepLabV3, DeepLabV3Plus, +Linknet, MAnet, PAN, UPerNet, Segformer, DPT +``` + +训练结果先写入 `DataSet_Public_outputs/_outputs-SegModel/`,脚本结束后会移动到 `Hardisk/`;推理结果写入 `BestMode_Predict_Results_DataSet_Public/_outputs-SegModel/`。 + +## 5. YOLO 使用方式 + +```bash +cd Seg_All_In_One_YoloModel +conda activate seg_smp + +# 检查当前 dataset.yaml 解析出的路径 +python yolo_config.py + +# 单模型训练 +CUDA_VISIBLE_DEVICES=0 python yolo_train.py --model "YOLOv8n-seg" + +# 批量训练 +bash yolo_train.sh + +# 复制最佳权重到预测目录 +bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "best.pt" + +# 单模型推理 +CUDA_VISIBLE_DEVICES=0 python yolo_predict_V2.py --model "YOLOv8n-seg" --conf 0.2 --pt_name "best.pt" + +# 批量推理 +bash yolo_predict.sh --conf 0.2 --pt_name "best.pt" + +# 批量热图 +bash yolo_predict.sh --heatmap_method "All" --pt_name "best.pt" + +# 横向对比 +python yolo_predict_V2_compare_all.py --pt_name "all" + +# 检查预测 raw mask 是否齐全 +python yolo_predict_raw_masks_check.py --pt_name "best.pt" +``` + +常用模型包括: + +```text +YOLOv8n-seg, YOLOv8s-seg, YOLOv8m-seg, YOLOv8l-seg, YOLOv8x-seg, +YOLOv9c-seg, YOLOv9e-seg, +YOLO11n-seg, YOLO11s-seg, YOLO11m-seg, YOLO11l-seg, YOLO11x-seg, +YOLO12-seg +``` + +## 6. MMSeg 使用方式 + +```bash +cd Seg_All_In_One_MMSeg +conda activate seg_smp + +# 首次或新增模块后注册工程 +pip install -v -e . + +# 下载/保存必要预训练权重 +python My_All_In_One/0_Initial_Save_All_Model_locally.py + +# 生成数据配置 +python My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V2.py + +# 生成算法配置 +python My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V2.py + +# 参数量、FLOPs、FPS +CUDA_VISIBLE_DEVICES=0 python My_All_In_One/4_1_predict_params_FLOPs_FPS_V2.py + +# 指标汇总 +CUDA_VISIBLE_DEVICES=0 python My_All_In_One/4_2_predict_matrics_from_log_V2.py + +# 生成预测图和表格 +CUDA_VISIBLE_DEVICES=0 python My_All_In_One/4_3_predict_draw_pictures_and_tabels.py + +# 提取 loss 和 best mIoU +CUDA_VISIBLE_DEVICES=0 python My_All_In_One/4_4_extract_loss_and_best_miou.py +``` + +MMSeg 对 `mmcv/mmengine/mmsegmentation` 版本较敏感;若遇到 `mmcv` CUDA 算子或版本错误,优先参考 MMSeg 使用手册中的安装记录。 + +## 7. 数据预处理与视频抽帧 + +自有图片预处理: + +```bash +cd "DataSet_Own/1. 图片预处理(内含使用手册)" + +bash 1_rename_pics.sh -i -l +bash 2_reformate_pics.sh -i -l -w 1920 -h 1080 +bash 3_pair_ori_label.sh -i -l +bash 4_rebuild_labels.sh -l +bash 5_TOOL_stack_pics.sh -i -l -r -a 0.3 +bash 6_TOOL_stitch_pics.sh -i -l -r +``` + +视频抽帧: + +```bash +cd Seg_Predict_Own_Video_V2 +python 1_Save_Frame_V2.py \ + --video ./LC_Video_1.mp4 \ + --resize "1920x1080" \ + --output_dir "../DataSet_Public/5_Predict_Video" \ + --interval 0.5 +``` + +输出路径为: + +```text +DataSet_Public/5_Predict_Video//images/val +``` + +图片叠加检查: + +```bash +cd Tool-图片堆叠 +python 1_check_picture_pair.py -i ./ori -l ./label +bash 2_TOOL_stack_pics.sh -i ./ori -l ./label -r ./result_0.3透明度 -a 0.3 -s _label +``` + +## 8. 结果分析 + +```bash +cd Seg_All_In_One_Analysis +conda activate seg_smp + +python 1_Analysis_All.py \ + --input_dir ../BestMode_Predict_Results_DataSet_Public \ + --output_dir ./ +``` + +脚本会交互选择数据集,合并 SegModel/MMSeg 的指标和速度数据,并生成 CSV、PNG、SVG 等分析结果。 + +## 9. 备份与 Git + +本仓库只提交程序和轻量配置。以下内容不进入 Git: + +- 数据集:`DataSet_Public/`、除预处理脚本外的 `DataSet_Own/` +- 训练和预测产物:`DataSet_Public_outputs/`、`BestMode_Predict_Results_DataSet_Public/`、`Hardisk/`、`Nas_BackUp_Seg/` +- 大权重和视频:`*.pt`、`*.pth`、`*.onnx`、`*.mp4` +- Python/构建缓存:`__pycache__/`、`.pytest_cache/`、`build/`、`dist/` + +常用 Git 检查: + +```bash +git status --short +git ls-files | grep -E '\\.(pt|pth|mp4|zip)$|DataSet_Public|Hardisk|BestMode' || true +``` + diff --git a/Seg_All_In_One_Analysis/1_Analysis_All.py b/Seg_All_In_One_Analysis/1_Analysis_All.py new file mode 100644 index 0000000..7b4060e --- /dev/null +++ b/Seg_All_In_One_Analysis/1_Analysis_All.py @@ -0,0 +1,298 @@ +import os +import glob +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns +import re +import argparse +from collections import defaultdict + +def get_model_family(model_name): + """ + 根据模型名称提取模型族。 + 例如: 'my_bisenetv1_r50' -> 'my_bisenetv1' + 'my_fast_scnn' -> 'my_fast_scnn' + """ + # 使用正则表达式匹配,将 _rXX 或 _dXX 等后缀去掉 + match = re.match(r'^(.*?)_r\d+$', model_name) + if match: + return match.group(1) + return model_name + +def select_dataset(results_dir): + """ + 扫描目录,对数据集进行分组,让用户交互式选择一个数据集进行合并分析。 + """ + print("正在扫描可用的数据集...") + try: + # 查找所有匹配后缀的目录 + all_dirs = glob.glob(os.path.join(results_dir, '*_outputs-MMSeg')) + \ + glob.glob(os.path.join(results_dir, '*_outputs-SegModel')) + + if not all_dirs: + print(f"在 '{results_dir}' 中未找到任何数据集目录 (以 '_outputs-MMSeg' 或 '_outputs-SegModel' 结尾)。") + return None, None + + # --- 新增逻辑:按基本数据集名称对目录进行分组 --- + datasets_map = defaultdict(list) + for dir_path in all_dirs: + if os.path.isdir(dir_path): + # 提取基本名称,例如 '1_CholecSeg8k-13Type-1920x1080' + base_name = re.sub(r'_outputs-(MMSeg|SegModel)$', '', os.path.basename(dir_path)) + datasets_map[base_name].append(dir_path) + + sorted_dataset_names = sorted(datasets_map.keys()) + + except Exception as e: + print(f"扫描目录 '{results_dir}' 时出错: {e}") + return None, None + + print("\n请选择要合并分析的数据集:") + for i, name in enumerate(sorted_dataset_names): + # 显示每个数据集包含的源文件夹数量 + source_count = len(datasets_map[name]) + print(f" [{i+1}] {name} ({source_count}个源)") + + while True: + try: + choice = input(f"\n请输入选项编号 (1-{len(sorted_dataset_names)}): ") + choice_idx = int(choice) - 1 + if 0 <= choice_idx < len(sorted_dataset_names): + selected_name = sorted_dataset_names[choice_idx] + selected_dirs = datasets_map[selected_name] # 获取与所选数据集关联的所有目录 + return selected_dirs, selected_name + else: + print("无效的选项,请输入列表中的编号。") + except (ValueError, IndexError): + print("无效的输入,请输入一个数字编号。") + except (KeyboardInterrupt, EOFError): + print("\n操作已取消。") + return None, None + +def F1_plot_performance_speed(selected_dirs, dataset_name, output_base_dir): + """ + 根据选定的数据集目录列表,加载并合并数据、生成图表和表格,并保存到指定的输出目录。 + + Args: + selected_dirs (list): 用户选择的原始数据所在的所有目录的列表。 + dataset_name (str): 从目录名中提取的数据集名称。 + output_base_dir (str): 保存所有输出文件的根目录。 + """ + print(f"\n正在为数据集 '{dataset_name}' 合并数据并生成图表...") + + # 在指定的输出根目录下,为当前数据集创建一个专属的输出文件夹 + dataset_output_dir = os.path.join(output_base_dir, dataset_name) + os.makedirs(dataset_output_dir, exist_ok=True) + print(f"所有输出文件将被保存到: {dataset_output_dir}") + + # --- 修改逻辑:从多个目录加载并合并数据 --- + all_metrics = [] + all_fps = [] + + print("正在读取以下来源的数据:") + for selected_dir in selected_dirs: + print(f" - {os.path.basename(selected_dir)}") + metrics_file = os.path.join(selected_dir, f"{dataset_name}_metrics_summary_wide.csv") + fps_file = os.path.join(selected_dir, f"{dataset_name}_flops_params_fps_summary.csv") + + # 检查文件是否存在 + if not os.path.exists(metrics_file) or not os.path.exists(fps_file): + print(f" -> 警告: 在目录 '{os.path.basename(selected_dir)}' 中缺少数据文件,已跳过。") + continue + + try: + metrics_df_part = pd.read_csv(metrics_file) + all_metrics.append(metrics_df_part) + + fps_df_part = pd.read_csv(fps_file) + all_fps.append(fps_df_part) + except Exception as e: + print(f" -> 错误: 读取CSV文件时出错: {e}") + continue + + if not all_metrics or not all_fps: + print("\n错误: 未能从任何有效的源目录中加载数据,无法继续生成报告。") + return + + # 合并来自所有源的数据 + metrics_df = pd.concat(all_metrics, ignore_index=True) + fps_df = pd.concat(all_fps, ignore_index=True) + print("\n数据合并完成。") + + # 对合并后的数据进行去重处理 + if 'Epoch' in metrics_df.columns: + metrics_df = metrics_df.sort_values('Epoch', ascending=False).drop_duplicates('Algorithm') + else: + metrics_df = metrics_df.drop_duplicates('Algorithm') + + fps_df = fps_df.drop_duplicates('Model') + + # 合并两个DataFrame + merged_df = pd.merge(metrics_df, fps_df, left_on='Algorithm', right_on='Model', how='inner') + + if merged_df.empty: + print("错误: 数据合并失败。请检查 'Algorithm' 和 'Model' 列中的模型名称是否完全匹配。") + print(f" - 指标文件中的模型: {metrics_df['Algorithm'].unique()}") + print(f" - 性能文件中的模型: {fps_df['Model'].unique()}") + return + + # 调用函数创建并保存摘要表格到新的输出目录 + T1_create_and_save_summary_table(merged_df, dataset_output_dir, dataset_name) + + # 调用函数来提取和保存所有IoU数据到新的输出目录 + T2_extract_and_save_iou_data(metrics_df, dataset_output_dir, dataset_name) + + # 提取模型族 + merged_df['Family'] = merged_df['Model'].apply(get_model_family) + + # --- 绘图 --- + plt.style.use('seaborn-v0_8-whitegrid') + fig, ax = plt.subplots(figsize=(16, 10)) + + # 定义颜色和标记 + families = sorted(merged_df['Family'].unique()) + palette = sns.color_palette("husl", len(families)) + markers = ['o', 's', 'X', 'D', '^', 'P', '*', 'v', '<', '>'] + + # 循环绘制每个模型族 + for i, family in enumerate(families): + family_df = merged_df[merged_df['Family'] == family].sort_values('Average_FPS') + color = palette[i] + marker = markers[i % len(markers)] + + # 绘制散点 + ax.scatter(family_df['Average_FPS'], family_df['mIoU'], + color=color, marker=marker, s=150, label=family, zorder=3) + + # 如果族内有多个模型,则用线连接 + if len(family_df) > 1: + ax.plot(family_df['Average_FPS'], family_df['mIoU'], + color=color, linestyle='--', linewidth=1.5, zorder=2) + + # 在每个点旁边添加模型全名注释 + for j, row in family_df.iterrows(): + ax.text(row['Average_FPS'] * 1.01, row['mIoU'], row['Model'], + fontsize=9, verticalalignment='center') + + # 设置图表属性 + ax.set_title(f'Model Performance vs. Inference Speed ({dataset_name})', fontsize=18, pad=20) + ax.set_xlabel('Inference Speed (FPS)', fontsize=14) + ax.set_ylabel('Mean IoU (%)', fontsize=14) + ax.legend(title='Model Family', bbox_to_anchor=(1.02, 1), loc='upper left', borderaxespad=0.) + + plt.tight_layout(rect=[0, 0, 0.88, 1]) # 调整布局为图例留出空间 + plt.grid(True, which='both', linestyle='--', linewidth=0.5) + + # 保存图表到新的输出目录 + output_filename_png = f"F1_{dataset_name}_mIoU_vs_FPS.png" + save_file_path_png = os.path.join(dataset_output_dir, output_filename_png) + plt.savefig(save_file_path_png, dpi=600) + + output_filename_svg = f"F1_{dataset_name}_mIoU_vs_FPS.svg" + save_file_path_svg = os.path.join(dataset_output_dir, output_filename_svg) + plt.savefig(save_file_path_svg) + + print(f"\n图表已成功生成并保存为: {save_file_path_svg} 和 {save_file_path_png}") + plt.close(fig) # 关闭图形,避免在循环中使用时重复显示 + +def T1_create_and_save_summary_table(merged_df, output_dir, dataset_name): + """ + 根据合并后的数据创建、格式化并保存性能摘要表格。 + """ + print("正在创建摘要表格...") + + # 检查所需列是否存在 + required_columns = ['Model', 'mIoU', 'mAcc', 'aAcc', 'Average_FPS', 'FLOPs', 'Params'] + if not all(col in merged_df.columns for col in required_columns): + print("错误: DataFrame中缺少必要的列。请检查CSV文件内容。") + print(f" - 需要的列: {required_columns}") + print(f" - 实际的列: {merged_df.columns.tolist()}") + return + + # 提取并复制数据,避免修改原始DataFrame + summary_df = merged_df[required_columns].copy() + + # 清理和转换数据 + summary_df['FLOPs'] = summary_df['FLOPs'].astype(str).str.replace(r'\s*G', '', regex=True).astype(float) + summary_df['Params'] = summary_df['Params'].astype(str).str.replace(r'\s*M', '', regex=True).astype(float) + + # 按照用户的要求重命名列 + summary_df.rename(columns={ + 'Average_FPS': 'FPS', + 'FLOPs': 'FLOPs(G)', + 'Params': 'Params(M)' + }, inplace=True) + + # 按 mIoU 降序排序 + summary_df = summary_df.sort_values(by='mIoU', ascending=False) + + # 保存表格到CSV文件 + summary_filename = f"T1_{dataset_name}_performance_summary.csv" + summary_save_path = os.path.join(output_dir, summary_filename) + + try: + summary_df.to_csv(summary_save_path, index=False, float_format='%.3f') + print(f"摘要表格已成功保存到: {summary_save_path}") + except Exception as e: + print(f"保存摘要表格时出错: {e}") + +def T2_extract_and_save_iou_data(metrics_df, output_dir, dataset_name): + """ + 从 metrics DataFrame 中提取所有 mIoU 和 Class_IoU,并保存到新的CSV文件。 + """ + print("正在提取所有 mIoU 和 Class_IoU 数据...") + + # 检查'Algorithm'列是否存在 + if 'Algorithm' not in metrics_df.columns: + print("错误: 'Algorithm' 列未找到,无法继续。") + return + + # 找出所有与IoU相关的列 + iou_columns = ['Algorithm', 'mIoU'] + [col for col in metrics_df.columns if col.endswith('_IoU') and col != 'mIoU'] + + # 移除重复的列名(以防万一) + iou_columns = list(dict.fromkeys(iou_columns)) + + # 提取数据 + iou_df = metrics_df[iou_columns].copy() + + # 按 mIoU 降序排序,便于查看 + if 'mIoU' in iou_df.columns: + iou_df = iou_df.sort_values(by='mIoU', ascending=False) + + # 定义并保存文件 + iou_filename = f"T2_{dataset_name}_all_iou_summary.csv" + iou_save_path = os.path.join(output_dir, iou_filename) + + try: + iou_df.to_csv(iou_save_path, index=False, float_format='%.2f') + print(f"所有IoU数据已成功保存到: {iou_save_path}") + except Exception as e: + print(f"保存IoU数据时出错: {e}") + +if __name__ == '__main__': + # --- 设置命令行参数解析 --- + parser = argparse.ArgumentParser(description="从模型评估结果生成性能与速度对比图和摘要表。") + parser.add_argument( + '--input_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="包含所有数据集结果的根目录 (例如 '..._outputs-MMSeg' 或 '..._outputs-SegModel' 的父目录)。" + ) + parser.add_argument( + '--output_dir', + type=str, + default='./', + help="用于存储所有生成的图表和表格的根目录。" + ) + args = parser.parse_args() + + # 确保输出目录存在 + os.makedirs(args.output_dir, exist_ok=True) + + # 启动交互式选择 + selected_directories, selected_dataset_name = select_dataset(args.input_dir) + + # 如果用户成功选择,则生成图表和表格 + if selected_directories and selected_dataset_name: + F1_plot_performance_speed(selected_directories, selected_dataset_name, args.output_dir) \ No newline at end of file diff --git a/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/F1_1_CholecSeg8k-13Type-1920x1080_mIoU_vs_FPS.svg b/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/F1_1_CholecSeg8k-13Type-1920x1080_mIoU_vs_FPS.svg new file mode 100644 index 0000000..a76338b --- /dev/null +++ b/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/F1_1_CholecSeg8k-13Type-1920x1080_mIoU_vs_FPS.svg @@ -0,0 +1,2992 @@ + + + + + + + + 2025-10-14T18:34:18.625839 + image/svg+xml + + + Matplotlib v3.9.4, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/T1_1_CholecSeg8k-13Type-1920x1080_performance_summary.csv b/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/T1_1_CholecSeg8k-13Type-1920x1080_performance_summary.csv new file mode 100644 index 0000000..ca2840d --- /dev/null +++ b/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/T1_1_CholecSeg8k-13Type-1920x1080_performance_summary.csv @@ -0,0 +1,21 @@ +Model,mIoU,mAcc,aAcc,FPS,FLOPs(G),Params(M) +UnetPlusPlus,96.860,95.430,99.750,11.940,590.910,26.080 +UPerNet,96.670,95.380,99.740,17.250,574.480,29.600 +MAnet,96.630,94.960,99.740,23.480,271.820,31.790 +Unet,96.590,95.520,99.730,26.290,253.380,24.440 +DeepLabV3Plus,96.500,94.990,99.730,33.210,252.410,22.440 +Linknet,96.460,94.550,99.720,32.820,161.800,21.770 +Segformer,96.450,94.880,99.720,21.020,209.450,21.880 +DeepLabV3,96.420,94.730,99.720,13.860,871.240,26.010 +FPN,96.410,94.740,99.720,34.920,219.570,23.160 +PAN,96.370,94.480,99.720,37.630,238.120,21.480 +DPT,96.310,94.900,99.710,1.900,1696.580,137.810 +PSPNet,96.010,94.610,99.690,79.510,76.810,21.490 +my_fastfcn_r50,89.740,94.210,97.830,10.620,1032.000,66.346 +my_icnet_r50,88.840,93.150,97.780,58.690,122.000,47.527 +my_icnet_r18,85.760,92.400,96.600,101.260,73.869,24.873 +my_bisenetv1_r50,82.640,89.980,95.690,13.630,784.000,56.867 +my_bisenetv1_r18,82.610,89.220,94.890,66.760,118.000,13.274 +my_bisenetv2,74.610,82.580,92.090,68.050,97.578,3.353 +my_fast_scnn,69.290,76.970,93.650,179.900,7.426,1.400 +my_en_bisenetv2,30.950,44.500,67.960,66.090,62.729,2.776 diff --git a/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/T2_1_CholecSeg8k-13Type-1920x1080_all_iou_summary.csv b/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/T2_1_CholecSeg8k-13Type-1920x1080_all_iou_summary.csv new file mode 100644 index 0000000..418f25e --- /dev/null +++ b/Seg_All_In_One_Analysis/1_CholecSeg8k-13Type-1920x1080/T2_1_CholecSeg8k-13Type-1920x1080_all_iou_summary.csv @@ -0,0 +1,21 @@ +Algorithm,mIoU,10_IoU,11_IoU,12_IoU,1_IoU,2_IoU,3_IoU,4_IoU,5_IoU,6_IoU,7_IoU,8_IoU,9_IoU,背景_IoU +UnetPlusPlus,96.86,84.08,71.33,99.41,96.18,96.27,94.45,91.76,90.90,92.24,93.28,79.37,98.01,97.99 +UPerNet,96.67,83.18,71.32,99.32,95.95,96.10,94.12,90.87,90.75,92.12,92.90,78.68,97.92,97.85 +MAnet,96.63,83.84,69.46,99.34,95.94,96.05,94.00,91.09,90.14,91.92,92.85,78.50,98.05,97.82 +Unet,96.59,83.11,71.34,99.34,95.88,96.01,93.90,90.62,90.34,91.67,92.45,79.29,97.73,97.81 +DeepLabV3Plus,96.50,83.07,71.81,99.28,95.86,95.85,93.77,89.92,90.31,91.77,92.17,78.45,97.69,97.73 +Linknet,96.46,81.22,71.54,99.33,95.69,95.83,93.64,89.90,90.38,91.54,92.29,78.48,97.57,97.71 +Segformer,96.45,81.30,67.75,99.27,95.74,95.85,93.56,89.94,89.98,91.87,92.42,78.16,97.80,97.73 +DeepLabV3,96.42,82.35,67.37,99.26,95.76,95.78,93.51,88.96,90.20,91.91,92.48,76.75,97.86,97.75 +FPN,96.41,82.43,69.98,99.24,95.71,95.74,93.61,89.88,90.22,91.74,92.23,77.50,97.76,97.69 +PAN,96.37,81.33,71.47,99.24,95.65,95.77,93.51,89.62,90.08,91.60,92.43,77.88,97.81,97.61 +DPT,96.31,83.17,70.59,99.27,95.49,95.64,93.48,89.71,90.34,91.36,92.53,78.20,97.63,97.53 +PSPNet,96.01,81.24,68.49,99.13,95.33,95.30,92.75,87.61,89.36,91.01,91.76,76.03,97.51,97.45 +my_fastfcn_r50,89.74,83.23,64.19,97.57,95.43,95.86,94.40,91.26,89.54,90.49,93.22,76.89,97.98,96.56 +my_icnet_r50,88.84,80.89,61.34,97.85,94.99,95.16,93.92,89.80,89.45,89.54,91.77,75.20,97.76,97.26 +my_icnet_r18,85.76,79.53,60.25,94.73,93.08,94.07,92.81,84.45,87.61,83.91,91.24,73.36,84.31,95.45 +my_bisenetv1_r50,82.64,80.56,67.31,97.75,91.70,91.28,85.29,83.91,84.91,73.37,80.36,71.34,74.45,92.07 +my_bisenetv1_r18,82.61,74.28,59.56,94.47,88.80,91.20,88.70,87.56,84.82,78.50,86.37,68.96,80.99,89.71 +my_bisenetv2,74.61,71.97,0.00,88.12,89.12,85.45,84.42,77.65,77.16,75.13,87.12,65.45,86.14,82.19 +my_fast_scnn,69.29,0.00,0.00,92.37,86.24,88.04,82.33,78.77,76.54,80.84,84.43,63.59,76.35,91.32 +my_en_bisenetv2,30.95,0.00,0.00,79.41,56.32,44.01,28.35,27.32,47.21,20.52,26.05,27.07,0.00,46.09 diff --git a/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/F1_2_AutoLaparo-10Type-1920x1080_mIoU_vs_FPS.svg b/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/F1_2_AutoLaparo-10Type-1920x1080_mIoU_vs_FPS.svg new file mode 100644 index 0000000..26ab7d1 --- /dev/null +++ b/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/F1_2_AutoLaparo-10Type-1920x1080_mIoU_vs_FPS.svg @@ -0,0 +1,2906 @@ + + + + + + + + 2025-10-14T18:05:22.028140 + image/svg+xml + + + Matplotlib v3.9.4, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/T1_2_AutoLaparo-10Type-1920x1080_performance_summary.csv b/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/T1_2_AutoLaparo-10Type-1920x1080_performance_summary.csv new file mode 100644 index 0000000..8b6ac7f --- /dev/null +++ b/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/T1_2_AutoLaparo-10Type-1920x1080_performance_summary.csv @@ -0,0 +1,21 @@ +Model,mIoU,mAcc,aAcc,FPS,FLOPs(G),Params(M) +DeepLabV3,80.740,83.990,99.520,14.030,871.240,26.010 +PSPNet,79.980,83.730,99.500,79.650,76.810,21.490 +UPerNet,79.960,85.130,99.500,17.440,574.480,29.600 +PAN,79.730,83.990,99.500,38.020,238.120,21.480 +DeepLabV3Plus,79.610,85.070,99.480,33.420,252.410,22.440 +Segformer,79.250,83.200,99.480,21.050,209.450,21.880 +FPN,78.990,83.980,99.470,35.060,219.570,23.160 +MAnet,77.380,82.040,99.420,23.610,271.820,31.790 +UnetPlusPlus,77.250,81.010,99.440,12.080,590.910,26.080 +Unet,76.160,83.160,99.380,26.410,253.380,24.440 +Linknet,75.510,81.050,99.380,33.040,161.800,21.770 +my_fastfcn_r50,71.040,79.630,92.280,10.610,1032.000,66.346 +my_icnet_r50,70.900,78.660,94.020,59.150,122.000,47.526 +my_icnet_r18,64.370,76.040,91.130,102.830,73.857,24.873 +DPT,58.120,62.610,98.840,1.910,1696.580,137.810 +my_bisenetv1_r50,49.540,70.640,85.890,13.670,784.000,56.864 +my_bisenetv1_r18,43.630,51.500,86.400,67.190,118.000,13.273 +my_fast_scnn,35.470,53.070,78.230,178.010,7.426,1.400 +my_bisenetv2,30.770,46.870,67.040,68.880,97.479,3.350 +my_en_bisenetv2,21.060,28.780,81.280,66.830,62.629,2.773 diff --git a/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/T2_2_AutoLaparo-10Type-1920x1080_all_iou_summary.csv b/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/T2_2_AutoLaparo-10Type-1920x1080_all_iou_summary.csv new file mode 100644 index 0000000..02b1089 --- /dev/null +++ b/Seg_All_In_One_Analysis/2_AutoLaparo-10Type-1920x1080/T2_2_AutoLaparo-10Type-1920x1080_all_iou_summary.csv @@ -0,0 +1,21 @@ +Algorithm,mIoU,1_IoU,2_IoU,3_IoU,4_IoU,5_IoU,6_IoU,7_IoU,8_IoU,9_IoU,背景_IoU +DeepLabV3,80.74,68.94,77.29,80.52,86.86,67.33,78.49,40.91,85.00,80.77,95.97 +PSPNet,79.98,66.02,78.51,79.55,86.79,65.31,81.79,43.71,88.19,78.72,95.59 +UPerNet,79.96,68.17,79.22,79.58,88.29,66.22,76.92,48.90,87.01,78.32,95.69 +PAN,79.73,68.17,79.87,80.10,87.75,67.79,80.61,45.17,85.84,77.10,95.55 +DeepLabV3Plus,79.61,67.65,80.67,79.04,86.41,67.82,78.38,45.17,84.93,78.48,95.51 +Segformer,79.25,70.26,80.48,79.32,86.77,64.76,77.48,40.30,86.94,76.90,95.67 +FPN,78.99,64.32,76.67,77.73,85.13,66.86,80.62,41.37,86.36,78.77,95.61 +MAnet,77.38,68.36,75.96,76.54,85.39,64.29,75.99,42.33,80.07,76.40,95.13 +UnetPlusPlus,77.25,68.41,80.79,78.11,88.39,61.29,75.66,43.16,78.42,73.51,95.01 +Unet,76.16,65.81,75.72,77.40,86.54,64.59,78.09,41.00,86.14,71.37,94.50 +Linknet,75.51,67.53,72.66,77.15,85.43,62.20,66.99,42.97,80.32,72.87,94.71 +my_fastfcn_r50,71.04,72.94,75.20,83.46,86.34,75.83,79.99,15.77,77.42,52.27,91.19 +my_icnet_r50,70.90,70.59,79.88,82.49,88.10,75.12,84.08,0.00,76.83,58.64,93.31 +my_icnet_r18,64.37,68.27,66.15,76.69,80.02,69.07,77.24,0.00,67.25,48.86,90.15 +DPT,58.12,35.86,57.90,52.58,69.75,25.48,51.48,11.85,64.40,61.94,90.98 +my_bisenetv1_r50,49.54,41.45,52.43,51.70,70.38,31.42,58.09,4.18,58.32,42.20,85.24 +my_bisenetv1_r18,43.63,40.48,33.63,57.69,68.59,18.73,41.95,5.47,46.50,37.73,85.56 +my_fast_scnn,35.47,17.13,31.73,32.59,67.72,13.07,44.63,0.00,39.41,30.97,77.47 +my_bisenetv2,30.77,13.45,36.67,16.43,60.36,13.90,36.03,0.00,37.48,28.07,65.35 +my_en_bisenetv2,21.06,1.90,15.66,34.99,36.61,5.25,18.82,0.00,14.46,0.37,82.57 diff --git a/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/F1_3_1_Endovis_2017-8Type-512x512_mIoU_vs_FPS.svg b/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/F1_3_1_Endovis_2017-8Type-512x512_mIoU_vs_FPS.svg new file mode 100644 index 0000000..d104e0b --- /dev/null +++ b/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/F1_3_1_Endovis_2017-8Type-512x512_mIoU_vs_FPS.svg @@ -0,0 +1,2815 @@ + + + + + + + + 2025-10-14T18:05:29.946958 + image/svg+xml + + + Matplotlib v3.9.4, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/T1_3_1_Endovis_2017-8Type-512x512_performance_summary.csv b/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/T1_3_1_Endovis_2017-8Type-512x512_performance_summary.csv new file mode 100644 index 0000000..2d8c004 --- /dev/null +++ b/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/T1_3_1_Endovis_2017-8Type-512x512_performance_summary.csv @@ -0,0 +1,21 @@ +Model,mIoU,mAcc,aAcc,FPS,FLOPs(G),Params(M) +FPN,77.150,91.110,99.500,204.850,27.550,23.160 +DeepLabV3,77.110,92.370,99.490,94.790,109.330,26.010 +PAN,76.600,90.580,99.480,234.070,29.880,21.480 +UPerNet,75.930,90.910,99.450,107.590,72.100,29.600 +UnetPlusPlus,75.800,90.720,99.460,89.430,74.150,26.080 +Segformer,75.080,88.260,99.450,151.560,26.280,21.880 +PSPNet,74.850,86.440,99.450,573.660,9.640,21.490 +Unet,73.860,89.190,99.410,173.520,31.800,24.440 +DeepLabV3Plus,73.830,86.410,99.420,208.780,31.680,22.440 +Linknet,73.790,87.770,99.410,197.430,20.300,21.770 +MAnet,73.630,89.900,99.400,152.330,33.850,31.790 +my_fastfcn_r50,61.430,90.120,97.480,71.100,130.000,66.346 +DPT,61.420,82.190,99.070,30.180,212.980,137.810 +my_bisenetv1_r50,59.590,84.700,95.770,88.970,98.945,56.862 +my_icnet_r50,57.840,80.930,94.950,179.660,15.428,47.526 +my_icnet_r18,57.350,88.250,96.590,268.050,9.360,24.873 +my_bisenetv1_r18,56.730,84.610,96.770,310.400,14.827,13.273 +my_bisenetv2,45.950,73.530,94.010,223.740,12.311,3.348 +my_fast_scnn,41.590,67.120,92.870,314.130,0.936,1.400 +my_en_bisenetv2,26.470,47.770,88.930,167.350,7.907,2.771 diff --git a/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/T2_3_1_Endovis_2017-8Type-512x512_all_iou_summary.csv b/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/T2_3_1_Endovis_2017-8Type-512x512_all_iou_summary.csv new file mode 100644 index 0000000..741f7a8 --- /dev/null +++ b/Seg_All_In_One_Analysis/3_1_Endovis_2017-8Type-512x512/T2_3_1_Endovis_2017-8Type-512x512_all_iou_summary.csv @@ -0,0 +1,21 @@ +Algorithm,mIoU,1_IoU,2_IoU,3_IoU,4_IoU,6_IoU,背景_IoU,5_IoU,7_IoU +FPN,77.15,68.36,56.28,89.32,75.57,90.63,97.53,0.00,0.00 +DeepLabV3,77.11,70.40,56.63,88.94,75.66,91.14,97.59,0.00,0.00 +PAN,76.60,67.15,58.83,88.96,66.32,93.62,97.63,0.00,0.00 +UPerNet,75.93,70.51,55.94,87.54,66.97,93.12,97.42,0.00,0.00 +UnetPlusPlus,75.80,71.10,53.32,89.23,62.62,92.12,97.61,0.00,0.00 +Segformer,75.08,69.28,51.50,89.63,60.44,89.39,97.46,0.00,0.00 +PSPNet,74.85,69.13,59.87,89.43,41.93,88.80,97.14,0.00,0.00 +Unet,73.86,70.85,47.93,88.16,65.93,81.26,97.40,0.00,0.00 +DeepLabV3Plus,73.83,67.50,55.09,88.72,43.60,89.56,97.30,0.00,0.00 +Linknet,73.79,70.46,51.28,88.14,61.32,74.38,97.41,0.00,0.00 +MAnet,73.63,69.37,50.95,86.99,64.69,88.41,97.27,0.00,0.00 +my_fastfcn_r50,61.43,75.72,61.00,89.72,74.27,92.82,97.88,, +DPT,61.42,53.96,44.36,74.11,42.46,72.76,95.73,0.00,0.00 +my_bisenetv1_r50,59.59,55.03,44.88,81.05,56.63,82.73,96.78,, +my_icnet_r50,57.84,55.85,40.23,81.41,59.23,72.62,95.51,, +my_icnet_r18,57.35,67.17,51.83,88.81,66.89,87.06,97.04,, +my_bisenetv1_r18,56.73,65.55,56.03,89.65,54.60,90.81,97.18,, +my_bisenetv2,45.95,35.33,43.64,75.24,32.28,86.09,95.00,, +my_fast_scnn,41.59,33.67,19.74,60.73,38.78,84.75,95.02,, +my_en_bisenetv2,26.47,21.72,3.36,51.58,1.01,42.74,91.37,, diff --git a/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/F1_3_2_Endovis_2018-8Type-512x512_mIoU_vs_FPS.svg b/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/F1_3_2_Endovis_2018-8Type-512x512_mIoU_vs_FPS.svg new file mode 100644 index 0000000..9629fe1 --- /dev/null +++ b/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/F1_3_2_Endovis_2018-8Type-512x512_mIoU_vs_FPS.svg @@ -0,0 +1,2912 @@ + + + + + + + + 2025-10-14T18:05:36.190288 + image/svg+xml + + + Matplotlib v3.9.4, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/T1_3_2_Endovis_2018-8Type-512x512_performance_summary.csv b/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/T1_3_2_Endovis_2018-8Type-512x512_performance_summary.csv new file mode 100644 index 0000000..5f2bf98 --- /dev/null +++ b/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/T1_3_2_Endovis_2018-8Type-512x512_performance_summary.csv @@ -0,0 +1,21 @@ +Model,mIoU,mAcc,aAcc,FPS,FLOPs(G),Params(M) +MAnet,93.900,84.990,99.210,151.600,33.850,31.790 +Segformer,93.280,82.550,99.130,151.930,26.280,21.880 +UnetPlusPlus,92.970,82.760,99.090,90.260,74.150,26.080 +FPN,92.670,82.440,99.050,207.350,27.550,23.160 +DeepLabV3,92.550,82.140,99.030,92.870,109.330,26.010 +Unet,92.530,79.230,99.030,177.100,31.800,24.440 +PAN,92.480,81.380,99.020,232.430,29.880,21.480 +UPerNet,92.180,80.020,98.980,105.860,72.100,29.600 +Linknet,92.060,79.170,98.970,199.680,20.300,21.770 +PSPNet,91.940,76.940,98.950,578.550,9.640,21.490 +DeepLabV3Plus,91.500,78.630,98.890,213.500,31.680,22.440 +DPT,87.840,72.480,98.380,30.860,212.980,137.810 +my_fastfcn_r50,55.340,84.060,96.630,71.420,130.000,66.346 +my_icnet_r50,50.400,78.440,95.210,202.090,15.428,47.526 +my_bisenetv1_r50,49.620,78.030,96.150,88.850,98.945,56.862 +my_icnet_r18,47.540,76.700,94.100,275.600,9.360,24.873 +my_bisenetv1_r18,45.020,67.190,95.580,346.950,14.827,13.273 +my_bisenetv2,38.850,65.830,93.150,243.230,12.311,3.348 +my_fast_scnn,36.200,61.550,92.870,381.410,0.936,1.400 +my_en_bisenetv2,21.760,41.090,86.700,203.200,7.907,2.771 diff --git a/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/T2_3_2_Endovis_2018-8Type-512x512_all_iou_summary.csv b/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/T2_3_2_Endovis_2018-8Type-512x512_all_iou_summary.csv new file mode 100644 index 0000000..228509e --- /dev/null +++ b/Seg_All_In_One_Analysis/3_2_Endovis_2018-8Type-512x512/T2_3_2_Endovis_2018-8Type-512x512_all_iou_summary.csv @@ -0,0 +1,21 @@ +Algorithm,mIoU,1_IoU,2_IoU,3_IoU,4_IoU,6_IoU,背景_IoU,5_IoU,7_IoU +MAnet,93.90,70.53,46.86,88.84,50.14,73.06,97.91,0.00,0.00 +Segformer,93.28,68.10,50.06,85.56,36.53,75.76,97.56,0.00,0.00 +UnetPlusPlus,92.97,64.84,47.57,81.19,59.57,62.17,97.61,0.00,0.00 +FPN,92.67,63.45,39.01,89.55,41.62,55.78,97.70,0.00,0.00 +DeepLabV3,92.55,68.69,38.16,83.63,42.43,56.29,97.59,0.00,0.00 +Unet,92.53,64.81,44.44,81.98,34.55,60.02,97.52,0.00,0.00 +PAN,92.48,64.05,37.24,81.88,47.61,63.51,97.47,0.00,0.00 +UPerNet,92.18,68.07,37.63,83.54,39.83,44.67,97.68,0.00,0.00 +Linknet,92.06,57.25,42.14,86.36,31.33,61.26,97.65,0.00,0.00 +PSPNet,91.94,62.48,37.34,82.50,19.62,61.92,97.44,0.00,0.00 +DeepLabV3Plus,91.50,62.77,36.12,76.23,40.89,55.95,97.32,0.00,0.00 +DPT,87.84,52.03,29.80,67.47,24.96,44.50,94.84,0.00,0.00 +my_fastfcn_r50,55.34,71.20,55.69,85.87,60.47,72.04,97.47,, +my_icnet_r50,50.40,61.35,42.53,78.21,62.38,62.30,96.39,, +my_bisenetv1_r50,49.62,63.38,40.38,82.82,47.85,64.80,97.78,, +my_icnet_r18,47.54,48.47,27.63,83.13,55.34,70.40,95.37,, +my_bisenetv1_r18,45.02,60.13,35.97,81.80,30.29,54.89,97.10,, +my_bisenetv2,38.85,47.72,28.29,73.87,24.03,41.84,95.06,, +my_fast_scnn,36.20,36.31,19.69,59.68,31.48,46.85,95.56,, +my_en_bisenetv2,21.76,19.78,7.06,38.84,0.06,18.16,90.16,, diff --git a/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/F1_4_Dresden-11Type-512x512_mIoU_vs_FPS.svg b/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/F1_4_Dresden-11Type-512x512_mIoU_vs_FPS.svg new file mode 100644 index 0000000..72f08f1 --- /dev/null +++ b/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/F1_4_Dresden-11Type-512x512_mIoU_vs_FPS.svg @@ -0,0 +1,2906 @@ + + + + + + + + 2025-10-14T18:05:41.811015 + image/svg+xml + + + Matplotlib v3.9.4, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/T1_4_Dresden-11Type-512x512_performance_summary.csv b/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/T1_4_Dresden-11Type-512x512_performance_summary.csv new file mode 100644 index 0000000..798c23f --- /dev/null +++ b/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/T1_4_Dresden-11Type-512x512_performance_summary.csv @@ -0,0 +1,21 @@ +Model,mIoU,mAcc,aAcc,FPS,FLOPs(G),Params(M) +MAnet,92.710,58.340,99.310,152.290,33.850,31.790 +my_fastfcn_r50,37.810,51.890,96.480,71.040,130.000,66.346 +my_icnet_r50,35.930,54.750,95.850,193.830,15.430,47.526 +my_icnet_r18,34.840,47.480,95.570,286.580,9.362,24.873 +my_bisenetv1_r50,33.600,48.180,95.950,88.460,98.957,56.865 +PAN,32.230,52.260,99.570,238.750,29.880,21.480 +FPN,31.480,50.620,99.500,208.170,27.550,23.160 +UPerNet,30.810,56.260,99.540,108.960,72.100,29.600 +PSPNet,30.460,48.110,99.580,586.440,9.640,21.490 +DeepLabV3,30.440,45.870,99.550,96.470,109.330,26.010 +DeepLabV3Plus,30.390,53.560,99.520,218.940,31.680,22.440 +Segformer,30.280,50.430,99.550,153.490,26.280,21.880 +my_bisenetv1_r18,29.660,36.130,96.230,316.660,14.830,13.273 +Unet,29.560,48.490,99.500,177.730,31.800,24.440 +UnetPlusPlus,29.020,46.550,99.530,91.560,74.150,26.080 +Linknet,27.440,45.720,99.520,202.960,20.300,21.770 +my_bisenetv2,26.790,48.100,94.290,220.480,12.323,3.351 +my_fast_scnn,24.240,38.810,94.450,318.670,0.936,1.400 +DPT,12.740,27.010,99.490,30.930,212.980,137.810 +my_en_bisenetv2,12.710,29.950,85.020,202.950,7.919,2.774 diff --git a/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/T2_4_Dresden-11Type-512x512_all_iou_summary.csv b/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/T2_4_Dresden-11Type-512x512_all_iou_summary.csv new file mode 100644 index 0000000..20c8128 --- /dev/null +++ b/Seg_All_In_One_Analysis/4_Dresden-11Type-512x512/T2_4_Dresden-11Type-512x512_all_iou_summary.csv @@ -0,0 +1,21 @@ +Algorithm,mIoU,10_IoU,1_IoU,2_IoU,4_IoU,5_IoU,6_IoU,7_IoU,8_IoU,9_IoU,背景_IoU,3_IoU +MAnet,92.71,35.32,26.82,27.63,69.93,17.24,1.35,68.73,39.18,18.98,96.27,0.00 +my_fastfcn_r50,37.81,38.43,20.81,34.67,71.51,20.69,0.00,63.65,45.78,23.86,96.53, +my_icnet_r50,35.93,37.37,25.03,26.86,63.67,21.21,1.01,64.49,41.90,17.85,95.88, +my_icnet_r18,34.84,37.50,29.12,26.10,67.33,17.58,0.17,64.91,38.94,5.98,95.65, +my_bisenetv1_r50,33.60,33.75,23.22,33.49,57.73,7.82,0.54,61.04,39.39,16.52,96.05, +PAN,32.23,24.44,27.98,17.51,55.65,14.65,1.28,52.96,33.81,14.59,95.93,0.00 +FPN,31.48,26.46,27.38,16.61,54.84,13.75,0.10,52.75,30.84,14.13,95.44,0.00 +UPerNet,30.81,25.82,23.85,18.94,48.88,19.74,3.77,55.93,28.24,13.85,95.60,0.00 +PSPNet,30.46,22.59,24.88,13.77,52.96,10.33,2.15,57.02,32.44,11.20,95.99,0.00 +DeepLabV3,30.44,20.32,23.59,9.83,53.98,12.70,0.00,54.00,33.47,12.89,95.82,0.00 +DeepLabV3Plus,30.39,21.50,25.78,16.46,55.69,14.54,1.83,49.53,31.38,9.97,95.64,0.00 +Segformer,30.28,29.15,22.06,13.37,52.94,11.50,0.52,58.03,40.69,16.09,95.76,0.00 +my_bisenetv1_r18,29.66,31.09,13.25,24.55,57.82,7.06,0.00,41.10,38.13,16.98,96.25, +Unet,29.56,24.81,26.57,8.55,47.35,16.95,0.00,41.73,32.39,14.65,95.35,0.00 +UnetPlusPlus,29.02,15.46,25.18,9.08,49.10,17.60,0.33,56.31,31.64,12.25,95.60,0.00 +Linknet,27.44,20.81,22.23,14.15,48.85,15.84,0.01,54.10,28.56,10.97,95.59,0.00 +my_bisenetv2,26.79,15.92,20.58,16.00,53.72,10.36,0.42,37.73,38.00,7.52,94.43, +my_fast_scnn,24.24,0.45,20.35,10.26,57.55,0.00,0.47,33.80,24.96,0.00,94.61, +DPT,12.74,0.00,2.63,0.00,30.18,0.00,0.22,13.80,12.37,0.00,95.15,0.00 +my_en_bisenetv2,12.71,0.00,12.19,0.00,35.31,0.00,0.14,1.78,4.69,0.00,85.72, diff --git a/Seg_All_In_One_MMSeg/CITATION.cff b/Seg_All_In_One_MMSeg/CITATION.cff new file mode 100644 index 0000000..cfd7cab --- /dev/null +++ b/Seg_All_In_One_MMSeg/CITATION.cff @@ -0,0 +1,8 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: + - name: "MMSegmentation Contributors" +title: "OpenMMLab Semantic Segmentation Toolbox and Benchmark" +date-released: 2020-07-10 +url: "https://github.com/open-mmlab/mmsegmentation" +license: Apache-2.0 diff --git a/Seg_All_In_One_MMSeg/LICENSE b/Seg_All_In_One_MMSeg/LICENSE new file mode 100644 index 0000000..38e625b --- /dev/null +++ b/Seg_All_In_One_MMSeg/LICENSE @@ -0,0 +1,203 @@ +Copyright 2020 The MMSegmentation Authors. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 The MMSegmentation Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Seg_All_In_One_MMSeg/MANIFEST.in b/Seg_All_In_One_MMSeg/MANIFEST.in new file mode 100644 index 0000000..94a0fc1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/MANIFEST.in @@ -0,0 +1,5 @@ +include requirements/*.txt +include mmseg/.mim/model-index.yml +include mmseg/utils/bpe_simple_vocab_16e6.txt.gz +recursive-include mmseg/.mim/configs *.py *.yaml +recursive-include mmseg/.mim/tools *.py *.sh diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/0_Initial_Save_All_Model_locally.py b/Seg_All_In_One_MMSeg/My_All_In_One/0_Initial_Save_All_Model_locally.py new file mode 100644 index 0000000..6f29891 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/0_Initial_Save_All_Model_locally.py @@ -0,0 +1,297 @@ +import os, requests, hashlib +from tqdm import tqdm + +### 链接获取网址:https://github.com/open-mmlab/mmcv/blob/master/mmcv/model_zoo/[deprecated.json | mmcls.json | open_mmlab.json | torchvision_0.12.json] ### + +# open_mmlab JSON 数据 +open_mmlab_model_urls = { + "vgg16_caffe": "https://download.openmmlab.com/pretrain/third_party/vgg16_caffe-292e1171.pth", + "detectron/resnet50_caffe": "https://download.openmmlab.com/pretrain/third_party/resnet50_caffe-788b5fa3.pth", + "detectron2/resnet50_caffe": "https://download.openmmlab.com/pretrain/third_party/resnet50_msra-5891d200.pth", + "detectron/resnet101_caffe": "https://download.openmmlab.com/pretrain/third_party/resnet101_caffe-3ad79236.pth", + "detectron2/resnet101_caffe": "https://download.openmmlab.com/pretrain/third_party/resnet101_msra-6cc46731.pth", + "detectron2/resnext101_32x8d": "https://download.openmmlab.com/pretrain/third_party/resnext101_32x8d-1516f1aa.pth", + "resnext50_32x4d": "https://download.openmmlab.com/pretrain/third_party/resnext50-32x4d-0ab1a123.pth", + "resnext101_32x4d": "https://download.openmmlab.com/pretrain/third_party/resnext101_32x4d-a5af3160.pth", + "resnext101_64x4d": "https://download.openmmlab.com/pretrain/third_party/resnext101_64x4d-ee2c6f71.pth", + "contrib/resnet50_gn": "https://download.openmmlab.com/pretrain/third_party/resnet50_gn_thangvubk-ad1730dd.pth", + "detectron/resnet50_gn": "https://download.openmmlab.com/pretrain/third_party/resnet50_gn-9186a21c.pth", + "detectron/resnet101_gn": "https://download.openmmlab.com/pretrain/third_party/resnet101_gn-cac0ab98.pth", + "jhu/resnet50_gn_ws": "https://download.openmmlab.com/pretrain/third_party/resnet50_gn_ws-15beedd8.pth", + "jhu/resnet101_gn_ws": "https://download.openmmlab.com/pretrain/third_party/resnet101_gn_ws-3e3c308c.pth", + "jhu/resnext50_32x4d_gn_ws": "https://download.openmmlab.com/pretrain/third_party/resnext50_32x4d_gn_ws-0d87ac85.pth", + "jhu/resnext101_32x4d_gn_ws": "https://download.openmmlab.com/pretrain/third_party/resnext101_32x4d_gn_ws-34ac1a9e.pth", + "jhu/resnext50_32x4d_gn": "https://download.openmmlab.com/pretrain/third_party/resnext50_32x4d_gn-c7e8b754.pth", + "jhu/resnext101_32x4d_gn": "https://download.openmmlab.com/pretrain/third_party/resnext101_32x4d_gn-ac3bb84e.pth", + "msra/hrnetv2_w18_small": "https://download.openmmlab.com/pretrain/third_party/hrnetv2_w18_small-b5a04e21.pth", + "msra/hrnetv2_w18": "https://download.openmmlab.com/pretrain/third_party/hrnetv2_w18-00eb2006.pth", + "msra/hrnetv2_w32": "https://download.openmmlab.com/pretrain/third_party/hrnetv2_w32-dc9eeb4f.pth", + "msra/hrnetv2_w40": "https://download.openmmlab.com/pretrain/third_party/hrnetv2_w40-ed0b031c.pth", + "msra/hrnetv2_w48": "https://download.openmmlab.com/pretrain/third_party/hrnetv2_w48-d2186c55.pth", + "bninception_caffe": "https://download.openmmlab.com/pretrain/third_party/bn_inception_caffe-ed2e8665.pth", + "kin400/i3d_r50_f32s2_k400": "https://download.openmmlab.com/pretrain/third_party/i3d_r50_f32s2_k400-2c57e077.pth", + "kin400/nl3d_r50_f32s2_k400": "https://download.openmmlab.com/pretrain/third_party/nl3d_r50_f32s2_k400-fa7e7caa.pth", + "res2net101_v1d_26w_4s": "https://download.openmmlab.com/pretrain/third_party/res2net101_v1d_26w_4s_mmdetv2-f0a600f9.pth", + "regnetx_400mf": "https://download.openmmlab.com/pretrain/third_party/regnetx_400mf-a5b10d96.pth", + "regnetx_800mf": "https://download.openmmlab.com/pretrain/third_party/regnetx_800mf-1f4be4c7.pth", + "regnetx_1.6gf": "https://download.openmmlab.com/pretrain/third_party/regnetx_1.6gf-5791c176.pth", + "regnetx_3.2gf": "https://download.openmmlab.com/pretrain/third_party/regnetx_3.2gf-c2599b0f.pth", + "regnetx_4.0gf": "https://download.openmmlab.com/pretrain/third_party/regnetx_4.0gf-a88f671e.pth", + "regnetx_6.4gf": "https://download.openmmlab.com/pretrain/third_party/regnetx_6.4gf-006af45d.pth", + "regnetx_8.0gf": "https://download.openmmlab.com/pretrain/third_party/regnetx_8.0gf-3c68abe7.pth", + "regnetx_12gf": "https://download.openmmlab.com/pretrain/third_party/regnetx_12gf-4c2a3350.pth", + "resnet18_v1c": "https://download.openmmlab.com/pretrain/third_party/resnet18_v1c-b5776b93.pth", + "resnet50_v1c": "https://download.openmmlab.com/pretrain/third_party/resnet50_v1c-2cccc1ad.pth", + "resnet101_v1c": "https://download.openmmlab.com/pretrain/third_party/resnet101_v1c-e67eebb6.pth", + "mmedit/vgg16": "https://download.openmmlab.com/mmediting/third_party/vgg_state_dict.pth", + "mmedit/res34_en_nomixup": "https://download.openmmlab.com/mmediting/third_party/model_best_resnet34_En_nomixup.pth", + "mmedit/mobilenet_v2": "https://download.openmmlab.com/mmediting/third_party/mobilenet_v2.pth", + "contrib/mobilenet_v3_large": "https://download.openmmlab.com/pretrain/third_party/mobilenet_v3_large-bc2c3fd3.pth", + "contrib/mobilenet_v3_small": "https://download.openmmlab.com/pretrain/third_party/mobilenet_v3_small-47085aa1.pth", + "resnest50": "https://download.openmmlab.com/pretrain/third_party/resnest50_d2-7497a55b.pth", + "resnest101": "https://download.openmmlab.com/pretrain/third_party/resnest101_d2-f3b931b2.pth", + "resnest200": "https://download.openmmlab.com/pretrain/third_party/resnest200_d2-ca88e41f.pth", + "darknet53": "https://download.openmmlab.com/pretrain/third_party/darknet53-a628ea1b.pth", + "mmdet/mobilenet_v2": "https://download.openmmlab.com/mmdetection/v2.0/third_party/mobilenet_v2_batch256_imagenet-ff34753d.pth", + "pidnet-s": "https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/pidnet/pidnet-s_imagenet1k_20230306-715e6273.pth", + "pidnet-m": "https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/pidnet/pidnet-m_imagenet1k_20230306-39893c52.pth", + "pidnet-l": "https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/pidnet/pidnet-l_imagenet1k_20230306-67889109.pth", + "ddrnet23-s": "https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/pretrain/ddrnet23s-in1kpre_3rdparty-1ccac5b1.pth", + "ddrnet23": "https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/pretrain/ddrnet23-in1kpre_3rdparty-9ca29f62.pth", + "stdc1": "https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/stdc/stdc1_20220308-5368626c.pth", + "stdc2": "https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/stdc/stdc2_20220308-7dbd9127.pth" +} + +# deprecated_model_urls = {{ +# "resnet50_caffe": "detectron/resnet50_caffe", +# "resnet50_caffe_bgr": "detectron2/resnet50_caffe_bgr", +# "resnet101_caffe": "detectron/resnet101_caffe", +# "resnet101_caffe_bgr": "detectron2/resnet101_caffe_bgr" +# }} + +mmcls_model_urls = { + "vgg11": "https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_batch256_imagenet_20210208-4271cd6c.pth", + "vgg13": "https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_batch256_imagenet_20210208-4d1d6080.pth", + "vgg16": "https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_batch256_imagenet_20210208-db26f1a5.pth", + "vgg19": "https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_batch256_imagenet_20210208-e6920e4a.pth", + "vgg11_bn": "https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_bn_batch256_imagenet_20210207-f244902c.pth", + "vgg13_bn": "https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_bn_batch256_imagenet_20210207-1a8b7864.pth", + "vgg16_bn": "https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_bn_batch256_imagenet_20210208-7e55cd29.pth", + "vgg19_bn": "https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_bn_batch256_imagenet_20210208-da620c4f.pth", + "resnet18": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet18_8xb32_in1k_20210831-fbbb1da6.pth", + "resnet34": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet34_8xb32_in1k_20210831-f257d4e6.pth", + "resnet50": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth", + "resnet101": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet101_8xb32_in1k_20210831-539c63f8.pth", + "resnet152": "https://download.openmmlab.com/mmclassification/v0/resnet/resnet152_8xb32_in1k_20210901-4d7582fa.pth", + "resnet50_v1d": "https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d50_b32x8_imagenet_20210531-db14775a.pth", + "resnet101_v1d": "https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d101_b32x8_imagenet_20210531-6e13bcd3.pth", + "resnet152_v1d": "https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d152_b32x8_imagenet_20210531-278cf22a.pth", + "resnext50_32x4d": "https://download.openmmlab.com/mmclassification/v0/resnext/resnext50_32x4d_b32x8_imagenet_20210429-56066e27.pth", + "resnext101_32x4d": "https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x4d_b32x8_imagenet_20210506-e0fa3dd5.pth", + "resnext101_32x8d": "https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x8d_b32x8_imagenet_20210506-23a247d5.pth", + "resnext152_32x4d": "https://download.openmmlab.com/mmclassification/v0/resnext/resnext152_32x4d_b32x8_imagenet_20210524-927787be.pth", + "se-resnet50": "https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet50_batch256_imagenet_20200804-ae206104.pth", + "se-resnet101": "https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet101_batch256_imagenet_20200804-ba5b51d4.pth", + "resnest50": "https://download.openmmlab.com/mmclassification/v0/resnest/resnest50_imagenet_converted-1ebf0afe.pth", + "resnest101": "https://download.openmmlab.com/mmclassification/v0/resnest/resnest101_imagenet_converted-032caa52.pth", + "resnest200": "https://download.openmmlab.com/mmclassification/v0/resnest/resnest200_imagenet_converted-581a60f2.pth", + "resnest269": "https://download.openmmlab.com/mmclassification/v0/resnest/resnest269_imagenet_converted-59930960.pth", + "shufflenet_v1": "https://download.openmmlab.com/mmclassification/v0/shufflenet_v1/shufflenet_v1_batch1024_imagenet_20200804-5d6cec73.pth", + "shufflenet_v2": "https://download.openmmlab.com/mmclassification/v0/shufflenet_v2/shufflenet_v2_batch1024_imagenet_20200812-5bf4721e.pth", + "mobilenet_v2": "https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth", + "mobilenet_v3_small": "https://download.openmmlab.com/mmclassification/v0/mobilenet_v3/convert/mobilenet_v3_small-8427ecf0.pth", + "mobilenet_v3_large": "https://download.openmmlab.com/mmclassification/v0/mobilenet_v3/convert/mobilenet_v3_large-3ea3c186.pth", + "repvgg_A0": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-A0_3rdparty_4xb64-coslr-120e_in1k_20210909-883ab98c.pth", + "repvgg_A1": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-A1_3rdparty_4xb64-coslr-120e_in1k_20210909-24003a24.pth", + "repvgg_A2": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-A2_3rdparty_4xb64-coslr-120e_in1k_20210909-97d7695a.pth", + "repvgg_B0": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B0_3rdparty_4xb64-coslr-120e_in1k_20210909-446375f4.pth", + "repvgg_B1": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B1_3rdparty_4xb64-coslr-120e_in1k_20210909-750cdf67.pth", + "repvgg_B1g2": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B1g2_3rdparty_4xb64-coslr-120e_in1k_20210909-344f6422.pth", + "repvgg_B1g4": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B1g4_3rdparty_4xb64-coslr-120e_in1k_20210909-d4c1a642.pth", + "repvgg_B2": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B2_3rdparty_4xb64-coslr-120e_in1k_20210909-bd6b937c.pth", + "repvgg_B2g4": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B2g4_3rdparty_4xb64-autoaug-lbs-mixup-coslr-200e_in1k_20210909-7b7955f0.pth", + "repvgg_B3": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B3_3rdparty_4xb64-autoaug-lbs-mixup-coslr-200e_in1k_20210909-dda968bf.pth", + "repvgg_B3g4": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B3g4_3rdparty_4xb64-autoaug-lbs-mixup-coslr-200e_in1k_20210909-4e54846a.pth", + "repvgg_D2se": "https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-D2se_3rdparty_4xb64-autoaug-lbs-mixup-coslr-200e_in1k_20210909-cf3139b7.pth", + "res2net101_w26": "https://download.openmmlab.com/mmclassification/v0/res2net/res2net101-w26-s4_3rdparty_8xb32_in1k_20210927-870b6c36.pth", + "res2net50_w14": "https://download.openmmlab.com/mmclassification/v0/res2net/res2net50-w14-s8_3rdparty_8xb32_in1k_20210927-bc967bf1.pth", + "res2net50_w26": "https://download.openmmlab.com/mmclassification/v0/res2net/res2net50-w26-s8_3rdparty_8xb32_in1k_20210927-f547a94b.pth", + "swin_tiny": "https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_tiny_224_b16x64_300e_imagenet_20210616_090925-66df6be6.pth", + "swin_small": "https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_small_224_b16x64_300e_imagenet_20210615_110219-7f9d988b.pth", + "swin_base": "https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window7_224_22kto1k-f967f799.pth", + "swin_large": "https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_large_patch4_window7_224_22kto1k-5f0996db.pth", + "t2t_vit_t_14": "https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-14_3rdparty_8xb64_in1k_20210928-b7c09b62.pth", + "t2t_vit_t_19": "https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-19_3rdparty_8xb64_in1k_20210928-7f1478d5.pth", + "t2t_vit_t_24": "https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-24_3rdparty_8xb64_in1k_20210928-fe95a61b.pth", + "tnt_small": "https://download.openmmlab.com/mmclassification/v0/tnt/tnt-small-p16_3rdparty_in1k_20210903-c56ee7df.pth", + "vit_base_p16": "https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-98e8652b.pth", + "vit_base_p32": "https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p32_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-9cea8599.pth", + "vit_large_p16": "https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-large-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-b20ba619.pth" +} + +torchvision_012_model_urls = { + "alexnet": "https://download.pytorch.org/models/alexnet-owt-7be5be79.pth", + "densenet121": "https://download.pytorch.org/models/densenet121-a639ec97.pth", + "densenet169": "https://download.pytorch.org/models/densenet169-b2777c0a.pth", + "densenet201": "https://download.pytorch.org/models/densenet201-c1103571.pth", + "densenet161": "https://download.pytorch.org/models/densenet161-8d451a50.pth", + "efficientnet_b0": "https://download.pytorch.org/models/efficientnet_b0_rwightman-3dd342df.pth", + "efficientnet_b1": "https://download.pytorch.org/models/efficientnet_b1_rwightman-533bc792.pth", + "efficientnet_b2": "https://download.pytorch.org/models/efficientnet_b2_rwightman-bcdf34b7.pth", + "efficientnet_b3": "https://download.pytorch.org/models/efficientnet_b3_rwightman-cf984f9c.pth", + "efficientnet_b4": "https://download.pytorch.org/models/efficientnet_b4_rwightman-7eb33cd5.pth", + "efficientnet_b5": "https://download.pytorch.org/models/efficientnet_b5_lukemelas-b6417697.pth", + "efficientnet_b6": "https://download.pytorch.org/models/efficientnet_b6_lukemelas-c76e70fd.pth", + "efficientnet_b7": "https://download.pytorch.org/models/efficientnet_b7_lukemelas-dcc49843.pth", + "googlenet": "https://download.pytorch.org/models/googlenet-1378be20.pth", + "inception_v3_google": "https://download.pytorch.org/models/inception_v3_google-0cc3c7bd.pth", + "mobilenet_v2": "https://download.pytorch.org/models/mobilenet_v2-b0353104.pth", + "mobilenet_v3_large": "https://download.pytorch.org/models/mobilenet_v3_large-8738ca79.pth", + "mobilenet_v3_small": "https://download.pytorch.org/models/mobilenet_v3_small-047dcff4.pth", + "regnet_y_400mf": "https://download.pytorch.org/models/regnet_y_400mf-c65dace8.pth", + "regnet_y_800mf": "https://download.pytorch.org/models/regnet_y_800mf-1b27b58c.pth", + "regnet_y_1_6gf": "https://download.pytorch.org/models/regnet_y_1_6gf-b11a554e.pth", + "regnet_y_3_2gf": "https://download.pytorch.org/models/regnet_y_3_2gf-b5a9779c.pth", + "regnet_y_8gf": "https://download.pytorch.org/models/regnet_y_8gf-d0d0e4a8.pth", + "regnet_y_16gf": "https://download.pytorch.org/models/regnet_y_16gf-9e6ed7dd.pth", + "regnet_y_32gf": "https://download.pytorch.org/models/regnet_y_32gf-4dee3f7a.pth", + "regnet_x_400mf": "https://download.pytorch.org/models/regnet_x_400mf-adf1edd5.pth", + "regnet_x_800mf": "https://download.pytorch.org/models/regnet_x_800mf-ad17e45c.pth", + "regnet_x_1_6gf": "https://download.pytorch.org/models/regnet_x_1_6gf-e3633e7f.pth", + "regnet_x_3_2gf": "https://download.pytorch.org/models/regnet_x_3_2gf-f342aeae.pth", + "regnet_x_8gf": "https://download.pytorch.org/models/regnet_x_8gf-03ceed89.pth", + "regnet_x_16gf": "https://download.pytorch.org/models/regnet_x_16gf-2007eb11.pth", + "regnet_x_32gf": "https://download.pytorch.org/models/regnet_x_32gf-9d47f8d0.pth", + "resnet18": "https://download.pytorch.org/models/resnet18-f37072fd.pth", + "resnet34": "https://download.pytorch.org/models/resnet34-b627a593.pth", + "resnet50": "https://download.pytorch.org/models/resnet50-0676ba61.pth", + "resnet101": "https://download.pytorch.org/models/resnet101-63fe2227.pth", + "resnet152": "https://download.pytorch.org/models/resnet152-394f9c45.pth", + "resnext50_32x4d": "https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth", + "resnext101_32x8d": "https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth", + "wide_resnet50_2": "https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth", + "wide_resnet101_2": "https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth", + "shufflenetv2_x0.5": "https://download.pytorch.org/models/shufflenetv2_x0.5-f707e7126e.pth", + "shufflenetv2_x1.0": "https://download.pytorch.org/models/shufflenetv2_x1-5666bf0f80.pth", + "shufflenetv2_x1.5": None, + "shufflenetv2_x2.0": None, + "squeezenet1_0": "https://download.pytorch.org/models/squeezenet1_0-b66bff10.pth", + "squeezenet1_1": "https://download.pytorch.org/models/squeezenet1_1-b8a52dc0.pth", + "vgg11": "https://download.pytorch.org/models/vgg11-8a719046.pth", + "vgg13": "https://download.pytorch.org/models/vgg13-19584684.pth", + "vgg16": "https://download.pytorch.org/models/vgg16-397923af.pth", + "vgg19": "https://download.pytorch.org/models/vgg19-dcbb9e9d.pth", + "vgg11_bn": "https://download.pytorch.org/models/vgg11_bn-6002323d.pth", + "vgg13_bn": "https://download.pytorch.org/models/vgg13_bn-abd245e5.pth", + "vgg16_bn": "https://download.pytorch.org/models/vgg16_bn-6c64b313.pth", + "vgg19_bn": "https://download.pytorch.org/models/vgg19_bn-c79401a0.pth" +} + +def calculate_file_hash(file_path, hash_algorithm='md5'): + """计算文件的哈希值,默认使用 MD5""" + hash_func = hashlib.new(hash_algorithm) + with open(file_path, 'rb') as f: + while chunk := f.read(8192): + hash_func.update(chunk) + return hash_func.hexdigest() + +def download_file(url, output_path): + """下载并保存文件,显示下载进度条""" + response = requests.get(url, stream=True) + + if response.status_code == 200: + # 获取文件的总大小,以便确定进度条的总长度 + total_size = int(response.headers.get('Content-Length', 0)) + + # 初始化 tqdm 进度条 + with tqdm(total=total_size, unit='B', unit_scale=True, desc=output_path, ncols=100) as pbar: + with open(output_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=1024): + if chunk: + f.write(chunk) + pbar.update(len(chunk)) # 更新进度条 + + print(f"Downloaded {output_path}") + else: + print(f"Failed to download {url}") + +def file_exists_and_same(url, output_path): + """检查文件是否已存在并且相同""" + if not os.path.exists(output_path): + return False + + # 计算远程文件的大小 + response = requests.head(url) + remote_file_size = int(response.headers.get('Content-Length', 0)) + + # 获取本地文件的大小 + local_file_size = os.path.getsize(output_path) + + # 如果文件大小不同,返回 False + if local_file_size != remote_file_size: + return False + else: + return True + + # # 如果大小相同,比较文件哈希值 + # remote_hash = requests.get(url + ".md5").text.strip() if url.endswith(".pth") else None + # local_hash = calculate_file_hash(output_path) if remote_hash else None + # print(remote_hash, local_hash) + # # 返回 True 如果哈希值相同 + # return local_hash == remote_hash if remote_hash else False + +def download_all_models(model_urls, output_dir): + """遍历JSON并下载文件""" + for model_name, url in model_urls.items(): + # 如果url为空则继续 + if url == None: + print(" ", end='') + print(f"\033[91m{model_name}后URL为空,跳过下载!\033[0m") + continue + # 创建保存路径 + file_name = f"{model_name}.pth" # 将文件名按 key 进行命名 + output_path = os.path.join(output_dir, file_name) + + # 获取 output_path 中的文件夹路径,并确保该路径存在 + output_folder = os.path.dirname(output_path) + os.makedirs(output_folder, exist_ok=True) # 如果文件夹不存在则创建 + + # 检查文件是否已存在并且相同 + if file_exists_and_same(url, output_path): + print(" ", end='') + print(f"\033[93mFile {output_path} already exists and the size is same, skipping download.\033[0m") + else: + print(" ", end='') + print(f"正在下载 {model_name} : {output_path} 中...") + # 下载文件 + download_file(url, output_path) + +if __name__ == '__main__': + ### 1.下载openmmlab数据集 ### + # 创建存储下载内容的文件夹 + output_open_mmlab_dir = './My_Local_Model/open_mmlab' + os.makedirs(output_open_mmlab_dir, exist_ok=True) + # 执行下载 + print(f"\033[32m下载open_mmlab数据中...\033[0m") + download_all_models(open_mmlab_model_urls, output_open_mmlab_dir) + + # ### 2.下载deprecated数据集 ### + # # 创建存储下载内容的文件夹 + # output_deprecated_dir = './My_Local_Model/deprecated' + # os.makedirs(output_deprecated_dir, exist_ok=True) + # # 执行下载 + # download_all_models(deprecated_model_urls, output_deprecated_dir) + + ### 3.下载mmcls数据集 ### + # 创建存储下载内容的文件夹 + output_mmcls_dir = './My_Local_Model/mmcls' + os.makedirs(output_mmcls_dir, exist_ok=True) + # 执行下载 + download_all_models(mmcls_model_urls, output_mmcls_dir) + + ### 4.下载torchvision_012数据集 ### + # 创建存储下载内容的文件夹 + output_torchvision_012_dir = './My_Local_Model/torchvision_012' + os.makedirs(output_torchvision_012_dir, exist_ok=True) + # 执行下载 + download_all_models(torchvision_012_model_urls, output_torchvision_012_dir) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/All_Data_Record.json b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/All_Data_Record.json new file mode 100644 index 0000000..3f6daed --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/All_Data_Record.json @@ -0,0 +1,628 @@ +{ + "publicdataset_cholecseg8k": { + "train_imgs_num": 6464, + "classes": [ + "背景", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], + "palette": [ + [ + 0, + 0, + 0 + ], + [ + 255, + 91, + 0 + ], + [ + 255, + 234, + 0 + ], + [ + 85, + 111, + 181 + ], + [ + 181, + 227, + 14 + ], + [ + 72, + 0, + 255 + ], + [ + 0, + 155, + 33 + ], + [ + 255, + 0, + 255 + ], + [ + 29, + 32, + 136 + ], + [ + 160, + 15, + 95 + ], + [ + 0, + 160, + 233 + ], + [ + 52, + 184, + 178 + ], + [ + 90, + 120, + 41 + ] + ], + "palette_num": 13, + "mean": [ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535 + ], + "std": [ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775 + ], + "imgs_num": 6464 + }, + "my_dataset_model": { + "train_imgs_num": 631, + "classes": [ + "背景", + "肝脏", + "胆囊", + "分离钳", + "止血海绵", + "肝总管", + "胆总管", + "吸引器", + "剪刀", + "止血纱布", + "生物夹", + "无损伤钳", + "喷洒", + "胆囊管", + "胆囊动脉", + "电凝", + "标本袋", + "引流管", + "纱布", + "金属钛夹", + "术中超声", + "吻合器", + "乳胶管", + "推结器", + "肝带", + "钳夹", + "超声刀", + "脂肪", + "双极电凝", + "棉球", + "血管阻断夹", + "肿瘤", + "针", + "线", + "韧带", + "胆囊静脉" + ], + "palette": [ + [ + 0, + 0, + 0 + ], + [ + 255, + 91, + 0 + ], + [ + 255, + 234, + 0 + ], + [ + 85, + 111, + 181 + ], + [ + 181, + 227, + 14 + ], + [ + 72, + 0, + 255 + ], + [ + 0, + 155, + 33 + ], + [ + 255, + 0, + 255 + ], + [ + 29, + 32, + 136 + ], + [ + 160, + 15, + 95 + ], + [ + 0, + 160, + 233 + ], + [ + 52, + 184, + 178 + ], + [ + 90, + 120, + 41 + ], + [ + 255, + 0, + 0 + ], + [ + 177, + 0, + 0 + ], + [ + 167, + 24, + 233 + ], + [ + 112, + 113, + 150 + ], + [ + 0, + 255, + 0 + ], + [ + 255, + 255, + 255 + ], + [ + 0, + 255, + 255 + ], + [ + 138, + 251, + 213 + ], + [ + 136, + 162, + 196 + ], + [ + 197, + 83, + 181 + ], + [ + 202, + 202, + 200 + ], + [ + 113, + 102, + 140 + ], + [ + 66, + 115, + 82 + ], + [ + 240, + 16, + 116 + ], + [ + 155, + 132, + 0 + ], + [ + 155, + 62, + 0 + ], + [ + 146, + 175, + 236 + ], + [ + 255, + 172, + 159 + ], + [ + 245, + 161, + 0 + ], + [ + 134, + 124, + 118 + ], + [ + 0, + 157, + 142 + ], + [ + 181, + 85, + 105 + ], + [ + 42, + 8, + 66 + ] + ], + "palette_num": 36, + "mean": [ + 94.94709810464319, + 61.729422339499315, + 75.93763705236911 + ], + "std": [ + 44.00550608113231, + 42.695956669847746, + 44.99354156225513 + ], + "imgs_num": 2000 + }, + "publicdataset_autolaparo": { + "train_imgs_num": 1440, + "classes": [ + "背景", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ], + "palette": [ + [ + 0, + 0, + 0 + ], + [ + 255, + 91, + 0 + ], + [ + 255, + 234, + 0 + ], + [ + 85, + 111, + 181 + ], + [ + 181, + 227, + 14 + ], + [ + 72, + 0, + 255 + ], + [ + 0, + 155, + 33 + ], + [ + 255, + 0, + 255 + ], + [ + 29, + 32, + 136 + ], + [ + 160, + 15, + 95 + ] + ], + "palette_num": 10, + "mean": [ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558 + ], + "std": [ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605 + ] + }, + "publicdataset_endovis_2017": { + "train_imgs_num": 1800, + "classes": [ + "背景", + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ], + "palette": [ + [ + 0, + 0, + 0 + ], + [ + 255, + 91, + 0 + ], + [ + 255, + 234, + 0 + ], + [ + 85, + 111, + 181 + ], + [ + 181, + 227, + 14 + ], + [ + 72, + 0, + 255 + ], + [ + 0, + 155, + 33 + ], + [ + 255, + 0, + 255 + ] + ], + "palette_num": 8, + "mean": [ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716 + ], + "std": [ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446 + ] + }, + "publicdataset_dresden": { + "train_imgs_num": 17363, + "classes": [ + "背景", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10" + ], + "palette": [ + [ + 0, + 0, + 0 + ], + [ + 255, + 91, + 0 + ], + [ + 255, + 234, + 0 + ], + [ + 85, + 111, + 181 + ], + [ + 181, + 227, + 14 + ], + [ + 72, + 0, + 255 + ], + [ + 0, + 155, + 33 + ], + [ + 255, + 0, + 255 + ], + [ + 29, + 32, + 136 + ], + [ + 160, + 15, + 95 + ], + [ + 0, + 160, + 233 + ] + ], + "palette_num": 11, + "mean": [ + 103.172638338208, + 61.44762740851152, + 51.407770213021976 + ], + "std": [ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569 + ] + }, + "publicdataset_endovis_2018": { + "train_imgs_num": 1800, + "classes": [ + "背景", + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ], + "palette": [ + [ + 0, + 0, + 0 + ], + [ + 255, + 91, + 0 + ], + [ + 255, + 234, + 0 + ], + [ + 85, + 111, + 181 + ], + [ + 181, + 227, + 14 + ], + [ + 72, + 0, + 255 + ], + [ + 0, + 155, + 33 + ], + [ + 255, + 0, + 255 + ] + ], + "palette_num": 8, + "mean": [ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716 + ], + "std": [ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446 + ] + } +} \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/my_dataset_model.json b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/my_dataset_model.json new file mode 100644 index 0000000..9c907c9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/my_dataset_model.json @@ -0,0 +1,50 @@ +{ + "____一、comment_dataset_info": "定义多个数据集文件名和对应类名", + "dataset_info": { + "dataset_file_name": "my_dataset_model", + "dataset_class_name": "MyDataset_model", + "data_root": "/home/wkmgc/Desktop/Seg/Seg_All_In_One_MMSeg/My_Data", + "img_scale_width": 1920, + "img_scale_height": 1080, + "____#####comment_paths#####": "训练、验证、测试集所在文件夹", + "paths": { + "train_img_path": "A_Ori", + "train_seg_map_path": "A_Label_GT_label_fold", + "val_img_path": "A_Ori", + "val_seg_map_path": "A_Label_GT_label_fold", + "test_img_path": "A_Ori", + "test_seg_map_path": "A_Label_GT_label_fold" + } + }, + "____二、comment_label_info": "定义Label图片相关参数", + "label_info": { + "classes": [ + "背景", "肝脏", "胆囊", "分离钳", "止血海绵", "肝总管", "胆总管", "吸引器", "剪刀", "止血纱布", "生物夹", "无损伤钳", "喷洒", + "胆囊管", "胆囊动脉", "电凝", "标本袋", "引流管", "纱布", "金属钛夹", "术中超声", "吻合器", "乳胶管", "推结器", + "肝带", "钳夹", "超声刀", "脂肪", "双极电凝", "棉球", "血管阻断夹", "肿瘤", "针", "线", "韧带", "胆囊静脉" + ], + "palette": [ + [0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], + [29, 32, 136], [160, 15, 95], [0, 160, 233], [52, 184, 178], [90, 120, 41], [255, 0, 0], [177, 0, 0], + [167, 24, 233], [112, 113, 150], [0, 255, 0], [255, 255, 255], [0, 255, 255], [138, 251, 213], [136, 162, 196], + [197, 83, 181], [202, 202, 200], [113, 102, 140], [66, 115, 82], [240, 16, 116], [155, 132, 0], [155, 62, 0], + [146, 175, 236], [255, 172, 159], [245, 161, 0], [134, 124, 118], [0, 157, 142], [181, 85, 105], [42, 8, 66] + ], + "____#####comment#####": "一般不太会变的参数", + "img_suffix": ".png", + "seg_map_suffix": "_gtFine_labelTrainIds.png", + "____#####comment_reduce_zero_label_1#####": "在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;", + "____#####comment_reduce_zero_label_2#####": "在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】", + "reduce_zero_label": false + }, + "____三、comment_training_info": "定义训练相关参数", + "training_info": { + "crop_size_width": 256, + "crop_size_height": 256, + "train_batch_size": 16, + "train_num_workers": 4, + "val_and_test_batch_size": 1, + "val_and_test_num_workers": 4 + } + } + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_autoLaparo.json b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_autoLaparo.json new file mode 100644 index 0000000..2535176 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_autoLaparo.json @@ -0,0 +1,42 @@ +{ + "____一、comment_dataset_info": "定义多个数据集文件名和对应类名", + "dataset_info": { + "dataset_file_name": "publicdataset_autolaparo", + "dataset_class_name": "PublicDataSet_AutoLaparo", + "data_root": "/home/wkmgc/Desktop/Seg/DataSet_Public/2_AutoLaparo-10Type-1920x1080", + "img_scale_width": 1920, + "img_scale_height": 1080, + "____#####comment_paths#####": "训练、验证、测试集所在文件夹", + "paths": { + "train_img_path": "images/train", + "train_seg_map_path": "labels_GT/train", + "val_img_path": "images/val", + "val_seg_map_path": "labels_GT/val", + "test_img_path": "images/val", + "test_seg_map_path": "labels_GT/val" + } + }, + "____二、comment_label_info": "定义Label图片相关参数", + "label_info": { + "classes": ["背景", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + "palette": [ + [0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95] + ], + "____#####comment#####": "一般不太会变的参数", + "img_suffix": ".png", + "seg_map_suffix": ".png", + "____#####comment_reduce_zero_label_1#####": "在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;", + "____#####comment_reduce_zero_label_2#####": "在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】", + "reduce_zero_label": false + }, + "____三、comment_training_info": "定义训练相关参数", + "training_info": { + "crop_size_width": 256, + "crop_size_height": 256, + "train_batch_size": 16, + "train_num_workers": 4, + "val_and_test_batch_size": 1, + "val_and_test_num_workers": 4 + } + } + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_cholecseg8k.json b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_cholecseg8k.json new file mode 100644 index 0000000..51f63e8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_cholecseg8k.json @@ -0,0 +1,42 @@ +{ + "____一、comment_dataset_info": "定义多个数据集文件名和对应类名", + "dataset_info": { + "dataset_file_name": "publicdataset_cholecseg8k", + "dataset_class_name": "PublicDataSet_CholecSeg8k", + "data_root": "/home/wkmgc/Desktop/Seg/DataSet_Public/1_CholecSeg8k-13Type-1920x1080", + "img_scale_width": 1920, + "img_scale_height": 1080, + "____#####comment_paths#####": "训练、验证、测试集所在文件夹", + "paths": { + "train_img_path": "images/train", + "train_seg_map_path": "labels_GT/train", + "val_img_path": "images/val", + "val_seg_map_path": "labels_GT/val", + "test_img_path": "images/val", + "test_seg_map_path": "labels_GT/val" + } + }, + "____二、comment_label_info": "定义Label图片相关参数", + "label_info": { + "classes": ["背景", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"], + "palette": [ + [0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233], [52, 184, 178], [90, 120, 41] + ], + "____#####comment#####": "一般不太会变的参数", + "img_suffix": ".png", + "seg_map_suffix": ".png", + "____#####comment_reduce_zero_label_1#####": "在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;", + "____#####comment_reduce_zero_label_2#####": "在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】", + "reduce_zero_label": false + }, + "____三、comment_training_info": "定义训练相关参数", + "training_info": { + "crop_size_width": 256, + "crop_size_height": 256, + "train_batch_size": 16, + "train_num_workers": 4, + "val_and_test_batch_size": 1, + "val_and_test_num_workers": 4 + } + } + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_dresden.json b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_dresden.json new file mode 100644 index 0000000..f87f148 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_dresden.json @@ -0,0 +1,42 @@ +{ + "____一、comment_dataset_info": "定义多个数据集文件名和对应类名", + "dataset_info": { + "dataset_file_name": "publicdataset_dresden", + "dataset_class_name": "PublicDataSet_Dresden", + "data_root": "/home/wkmgc/Desktop/Seg/DataSet_Public/4_Dresden-11Type-512x512", + "img_scale_width": 512, + "img_scale_height": 512, + "____#####comment_paths#####": "训练、验证、测试集所在文件夹", + "paths": { + "train_img_path": "images/train", + "train_seg_map_path": "labels_GT/train", + "val_img_path": "images/val", + "val_seg_map_path": "labels_GT/val", + "test_img_path": "images/test", + "test_seg_map_path": "labels_GT/test" + } + }, + "____二、comment_label_info": "定义Label图片相关参数", + "label_info": { + "classes": ["背景", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], + "palette": [ + [0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233] + ], + "____#####comment#####": "一般不太会变的参数", + "img_suffix": ".png", + "seg_map_suffix": ".png", + "____#####comment_reduce_zero_label_1#####": "在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;", + "____#####comment_reduce_zero_label_2#####": "在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】", + "reduce_zero_label": false + }, + "____三、comment_training_info": "定义训练相关参数", + "training_info": { + "crop_size_width": 256, + "crop_size_height": 256, + "train_batch_size": 16, + "train_num_workers": 4, + "val_and_test_batch_size": 1, + "val_and_test_num_workers": 4 + } + } + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_endovis_2017.json b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_endovis_2017.json new file mode 100644 index 0000000..33412b3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_endovis_2017.json @@ -0,0 +1,42 @@ +{ + "____一、comment_dataset_info": "定义多个数据集文件名和对应类名", + "dataset_info": { + "dataset_file_name": "publicdataset_endovis_2017", + "dataset_class_name": "PublicDataSet_Endovis_2017", + "data_root": "/home/wkmgc/Desktop/Seg/DataSet_Public/3_1_Endovis_2017-8Type-512x512", + "img_scale_width": 512, + "img_scale_height": 512, + "____#####comment_paths#####": "训练、验证、测试集所在文件夹", + "paths": { + "train_img_path": "images/train", + "train_seg_map_path": "labels_GT/train", + "val_img_path": "images/val", + "val_seg_map_path": "labels_GT/val", + "test_img_path": "images/val", + "test_seg_map_path": "labels_GT/val" + } + }, + "____二、comment_label_info": "定义Label图片相关参数", + "label_info": { + "classes": ["背景", "1", "2", "3", "4", "5", "6", "7"], + "palette": [ + [0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255] + ], + "____#####comment#####": "一般不太会变的参数", + "img_suffix": ".bmp", + "seg_map_suffix": ".bmp", + "____#####comment_reduce_zero_label_1#####": "在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;", + "____#####comment_reduce_zero_label_2#####": "在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】", + "reduce_zero_label": false + }, + "____三、comment_training_info": "定义训练相关参数", + "training_info": { + "crop_size_width": 256, + "crop_size_height": 256, + "train_batch_size": 16, + "train_num_workers": 4, + "val_and_test_batch_size": 1, + "val_and_test_num_workers": 4 + } + } + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_endovis_2018.json b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_endovis_2018.json new file mode 100644 index 0000000..c7d6aaa --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Data_Parameter/public_dataset_endovis_2018.json @@ -0,0 +1,42 @@ +{ + "____一、comment_dataset_info": "定义多个数据集文件名和对应类名", + "dataset_info": { + "dataset_file_name": "publicdataset_endovis_2018", + "dataset_class_name": "PublicDataSet_Endovis_2018", + "data_root": "/home/wkmgc/Desktop/Seg/DataSet_Public/3_2_Endovis_2018-8Type-512x512", + "img_scale_width": 512, + "img_scale_height": 512, + "____#####comment_paths#####": "训练、验证、测试集所在文件夹", + "paths": { + "train_img_path": "images/train", + "train_seg_map_path": "labels_GT/train", + "val_img_path": "images/val", + "val_seg_map_path": "labels_GT/val", + "test_img_path": "images/val", + "test_seg_map_path": "labels_GT/val" + } + }, + "____二、comment_label_info": "定义Label图片相关参数", + "label_info": { + "classes": ["背景", "1", "2", "3", "4", "5", "6", "7"], + "palette": [ + [0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255] + ], + "____#####comment#####": "一般不太会变的参数", + "img_suffix": ".bmp", + "seg_map_suffix": ".bmp", + "____#####comment_reduce_zero_label_1#####": "在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;", + "____#####comment_reduce_zero_label_2#####": "在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】", + "reduce_zero_label": false + }, + "____三、comment_training_info": "定义训练相关参数", + "training_info": { + "crop_size_width": 256, + "crop_size_height": 256, + "train_batch_size": 16, + "train_num_workers": 4, + "val_and_test_batch_size": 1, + "val_and_test_num_workers": 4 + } + } + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All-ori.py b/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All-ori.py new file mode 100644 index 0000000..f1b88ca --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All-ori.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import os +from Initial_Data_Program.Initial_Data_Gen_configs_base_datasets_my_dataset import generate_configs_base_datasets_my_dataset_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_utils_class_names import generate_mmseg_utils_class_names_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_datasets_my_dataset import generate_mmseg_datasets_my_dataset_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_datasets_init_ import generate_mmseg_datasets_init_file + +if __name__ == '__main__': + ########### 1.1.定义各数据集相关参数 ########### + # 可以定义多个数据集文件名 和 对应类名 + dataset_file_names = ["my_dataset_model"] # =['my_dataset', 'my_dataset_2'] # =["my_dataset"] + dataset_class_names = ["MyDataset_model"] # =['MyDataset', 'MyDataset2'] # =["MyDataset"] + dataset_file_name='my_dataset_model' # 数据集 文件名.py TODO + dataset_class_name='MyDataset_model' # 数据集 类名称 TODO + data_root='/home/audience/Desktop/Seg_data/Data' # 数据根目录 + img_scale=(1920, 1080) # 图片大小 + # 训练、验证、测试集所在文件夹 + train_img_path='A_Ori' + train_seg_map_path='A_Label_GT_label_fold' + val_img_path='A_Ori' + val_seg_map_path='A_Label_GT_label_fold' + test_img_path='A_Ori' + test_seg_map_path='A_Label_GT_label_fold' + + ########### 1.2.定义Label图片相关参数 ########### + classes = ['肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉'] + palette = [[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118],[0,157,142],[181,85,105],[42,8,66]] + # 这里的classes一定是经过“Initial_Gen_mmseg_datasets_my_dataset.py”处理的 + classes_all = [ + ['背景','肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉'], + ] + palette_all = [ + [[0,0,0],[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118],[0,157,142],[181,85,105],[42,8,66]], + ] + # 一般不太会变的参数 + img_suffix = ".png" + seg_map_suffix = "_gtFine_labelTrainIds.png" + reduce_zero_label = False # 在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + + ########### 1.3.定义训练相关参数 ########### + # 一般不太会变的参数 + crop_size=(512, 512) # 分割大小 + train_batch_size=4 # 训练batch + train_num_workers=4 # 训练并行运行数量 + val_and_test_batch_size=1 # 验证集和测试集batch + val_and_test_num_workers=4 # 验证集和测试集并行运行数量 + + ########### 2.文件存储位置 ########### + output_configs_base_datasets_my_dataset=f'./configs/_base_/datasets/{dataset_file_name}.py' + output_mmseg_datasets_dataset_file_name = os.path.join(f'./mmseg/datasets/{dataset_file_name}.py') + output_mmseg_datasets_init = os.path.join('./mmseg/datasets/__init__.py') + output_mmseg_utils_class_names = f'./mmseg/utils/class_names.py' + + ########### 3.运行程序生成配置文件 ########### + success = generate_configs_base_datasets_my_dataset_file(output_file=output_configs_base_datasets_my_dataset, dataset_class_name=dataset_class_name , data_root=data_root, img_scale=img_scale, crop_size=crop_size, train_batch_size=train_batch_size, train_num_workers=train_num_workers, val_and_test_batch_size=val_and_test_batch_size, val_and_test_num_workers=val_and_test_num_workers, train_img_path=train_img_path, train_seg_map_path=train_seg_map_path, val_img_path=val_img_path, val_seg_map_path=val_seg_map_path, test_img_path=test_img_path, test_seg_map_path=test_seg_map_path) + success, classes, palette = generate_mmseg_datasets_my_dataset_file(output_file=output_mmseg_datasets_dataset_file_name, dataset_class_name=dataset_class_name, classes=classes, palette=palette, img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, reduce_zero_label=reduce_zero_label) + # 需要用到上一步的classes和palette + success = generate_mmseg_datasets_init_file(output_file=output_mmseg_datasets_init, dataset_file_names=dataset_file_names, dataset_class_names=dataset_class_names) + success = generate_mmseg_utils_class_names_file(output_file=output_mmseg_utils_class_names, dataset_file_names=dataset_file_names, classes_all=classes_all, palette_all=palette_all) + diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V1.py b/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V1.py new file mode 100644 index 0000000..94a9610 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V1.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import os, json +from Initial_Data_Program.Initial_Data_Calculate_std_and_mean import calculate_pic_std_and_mean +from Initial_Data_Program.Initial_Data_Gen_configs_base_datasets_my_dataset import generate_configs_base_datasets_my_dataset_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_utils_class_names import generate_mmseg_utils_class_names_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_datasets_my_dataset import generate_mmseg_datasets_my_dataset_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_datasets_init_ import generate_mmseg_datasets_init_file + +def load_json_files(directory, not_check_list=None): + """ + 读取指定文件夹下的所有 JSON 文件,排除指定的文件。 + + :param directory: 要读取的目录 + :param not_check_list: 要排除的文件名列表,不包含路径。默认为空列表 + :return: 包含所有有效 JSON 数据的列表,每个元素为一个字典 + """ + if not_check_list is None: + not_check_list = [''] # 默认排除文件 + + # 获取所有 .json 文件,并排除在 not_check_list 中的文件 + json_files = [f for f in os.listdir(directory) if f.endswith('.json') and f not in not_check_list] + + data_list = [] + + # 遍历每个 JSON 文件 + for json_file in json_files: + json_path = os.path.join(directory, json_file) + + try: + # 打开并加载 JSON 文件 + with open(json_path, 'r', encoding='utf-8') as f: + data = json.load(f) + # 将文件名(去掉 .json)添加到数据中 + data['file_name_json'] = json_file.rstrip(".json") + data_list.append(data) + + except json.JSONDecodeError: + print(f"\033[91mError decoding JSON file: {json_path}\033[0m") + except Exception as e: + print(f"\033[91mError reading file {json_path}: {str(e)}\033[0m") + + return data_list + + +def process_json_data(json_data): + """处理每个 JSON 数据,生成相应参数""" + # 提取 dataset 信息 + dataset_file_name = json_data["dataset_info"]["dataset_file_name"] + dataset_class_name = json_data["dataset_info"]["dataset_class_name"] + data_root = json_data["dataset_info"]["data_root"] + + # 转换 img_scale 为元组 (img_scale_width, img_scale_height) + img_scale = (json_data["dataset_info"]["img_scale_width"], json_data["dataset_info"]["img_scale_height"]) + + # 提取其他必要信息 + train_img_path = json_data["dataset_info"]["paths"]["train_img_path"] + train_seg_map_path = json_data["dataset_info"]["paths"]["train_seg_map_path"] + val_img_path = json_data["dataset_info"]["paths"]["val_img_path"] + val_seg_map_path = json_data["dataset_info"]["paths"]["val_seg_map_path"] + test_img_path = json_data["dataset_info"]["paths"]["test_img_path"] + test_seg_map_path = json_data["dataset_info"]["paths"]["test_seg_map_path"] + + # 提取 label 相关信息 + classes = json_data["label_info"]["classes"] + palette = json_data["label_info"]["palette"] + img_suffix = json_data["label_info"]["img_suffix"] + seg_map_suffix = json_data["label_info"]["seg_map_suffix"] + reduce_zero_label = json_data["label_info"]["reduce_zero_label"] + + # 提取训练相关参数 + # 转换 crop_size 为元组 (crop_size_width, crop_size_height) + crop_size = (json_data["training_info"]["crop_size_width"], json_data["training_info"]["crop_size_height"]) + train_batch_size = json_data["training_info"]["train_batch_size"] + train_num_workers = json_data["training_info"]["train_num_workers"] + val_and_test_batch_size = json_data["training_info"]["val_and_test_batch_size"] + val_and_test_num_workers = json_data["training_info"]["val_and_test_num_workers"] + + return (dataset_file_name, dataset_class_name, data_root, img_scale, train_img_path, train_seg_map_path, val_img_path, val_seg_map_path, test_img_path, test_seg_map_path, + classes, palette, img_suffix, seg_map_suffix, reduce_zero_label, crop_size, train_batch_size, train_num_workers, val_and_test_batch_size, val_and_test_num_workers,) + + +def save_all_record_to_json(output_file, dataset_file_names, classes_all, palette_all, palette_num_all, mean_all, std_all): + """构建一个 JSON 文件,用于存储每个数据集的信息""" + # 构建一个字典,用于存储每个数据集的信息 + data_record = {} + + # 假设所有列表长度相同,遍历每个 dataset_file_name + for i in range(len(dataset_file_names)): + data_record[dataset_file_names[i]] = { + "classes": classes_all[i], + "palette": palette_all[i], + "palette_num": palette_num_all[i], + "mean": list(mean_all[i]), + "std": list(std_all[i]) + } + + # 将字典写入 JSON 文件 + with open(output_file, 'w', encoding='utf-8') as json_file: + json.dump(data_record, json_file, ensure_ascii=False, indent=6) + + print(f"\033[93m相关数据汇总到 {output_file} successfully!\033[0m") + return True + +if __name__ == '__main__': + ########### 1.超参数-定义训练文件夹路径 ########### + train_parameter_dir = './My_All_In_One/1_Data_Parameter' + all_data_record_json = "All_Data_Record.json" # 记录所有数据 + + ########### 2.1.遍历所有配置文件,并生成对应数据集 ########### + # 定义保存信息的列表 + dataset_file_names = [] + dataset_class_names = [] + classes_all = [] + palette_all = [] + palette_num_all = [] + mean_all = [] + std_all = [] + + # 2.1.1. 从 ./1_Data_Parameter 文件夹读取所有 JSON 文件 + json_data_list = load_json_files(train_parameter_dir, not_check_list = [all_data_record_json]) + + # 2.1.2. 遍历每个 JSON 数据文件并生成对应配置文件 + for json_data in json_data_list: + # A. 输出当前处理文件 + print(f"\033[32m正在处理{json_data['file_name_json']}.json文件\033[0m") + + # B. 处理 JSON 数据并提取参数 + (dataset_file_name, dataset_class_name, data_root, img_scale, train_img_path, train_seg_map_path, val_img_path, val_seg_map_path, test_img_path, test_seg_map_path, + classes, palette, img_suffix, seg_map_suffix, reduce_zero_label, crop_size, train_batch_size, train_num_workers, val_and_test_batch_size, val_and_test_num_workers, + ) = process_json_data(json_data) + + # 保存文件名和类别名 + dataset_file_names.append(dataset_file_name) + dataset_class_names.append(dataset_class_name) + + # C. 文件存储位置 + output_configs_base_datasets_my_dataset = f'./configs/_base_/datasets/{dataset_file_name}.py' + output_mmseg_datasets_dataset_file_name = os.path.join(f'./mmseg/datasets/{dataset_file_name}.py') + output_mmseg_datasets_init = os.path.join('./mmseg/datasets/__init__.py') + output_mmseg_utils_class_names = f'./mmseg/utils/class_names.py' + + # D. 运行程序生成配置文件 + # 生成 ./configs/_base_/datasets/{dataset_file_name}.py + print(" ",end='') + success = generate_configs_base_datasets_my_dataset_file( + output_file=output_configs_base_datasets_my_dataset, + dataset_class_name=dataset_class_name, + data_root=data_root, + img_scale=img_scale, + crop_size=crop_size, + train_batch_size=train_batch_size, + train_num_workers=train_num_workers, + val_and_test_batch_size=val_and_test_batch_size, + val_and_test_num_workers=val_and_test_num_workers, + train_img_path=train_img_path, + train_seg_map_path=train_seg_map_path, + val_img_path=val_img_path, + val_seg_map_path=val_seg_map_path, + test_img_path=test_img_path, + test_seg_map_path=test_seg_map_path + ) + + # 生成 ./mmseg/datasets/{dataset_file_name}.py + print(" ",end='') + success, classes, palette = generate_mmseg_datasets_my_dataset_file( + output_file=output_mmseg_datasets_dataset_file_name, + dataset_class_name=dataset_class_name, + classes=classes, + palette=palette, + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label + ) + + mean, std = calculate_pic_std_and_mean( + dataset_dir = os.path.join(data_root, train_img_path) + ) + + # 保存标注类名和颜色 + classes_all.append(classes) + palette_all.append(palette) + palette_num_all.append(len(classes)) + mean_all.append(mean) + std_all.append(std) + + ########### 2.2.汇总所有信息运行生成 init 和 class_names 文件 ########### + # 生成 ./mmseg/datasets/__init__.py + print(" ",end='') + success = generate_mmseg_datasets_init_file( + output_file=output_mmseg_datasets_init, + dataset_file_names=dataset_file_names, + dataset_class_names=dataset_class_names + ) + + # 生成 ./mmseg/utils/class_names.py + print(" ",end='') + success = generate_mmseg_utils_class_names_file( + output_file=output_mmseg_utils_class_names, + dataset_file_names=dataset_file_names, + classes_all=classes_all, + palette_all=palette_all + ) + + ########### 2.2.汇总dataset_file_names、classes_all、palette_all、palette_num_all所有信息到My_All_In_One/1_Data_Parameter/All_Data_Record.json文件 ########### + output_all_data_record = os.path.join(train_parameter_dir, all_data_record_json) + # 调用函数保存数据 + success = save_all_record_to_json(output_file=output_all_data_record, dataset_file_names=dataset_file_names, classes_all=classes_all, palette_all=palette_all, palette_num_all=palette_num_all, mean_all=mean_all, std_all=std_all) + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V2.py b/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V2.py new file mode 100644 index 0000000..20a4c08 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/1_Initial_Data_All_data_from_1_Data_Parameter-V2.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import os, json +from Initial_Data_Program.Initial_Data_Calculate_std_and_mean import calculate_pic_std_and_mean +from Initial_Data_Program.Initial_Data_Gen_configs_base_datasets_my_dataset import generate_configs_base_datasets_my_dataset_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_utils_class_names import generate_mmseg_utils_class_names_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_datasets_my_dataset import generate_mmseg_datasets_my_dataset_file +from Initial_Data_Program.Initial_Data_Gen_mmseg_datasets_init_ import generate_mmseg_datasets_init_file + +def load_json_files(directory, not_check_list=None): + """ + 读取指定文件夹下的所有 JSON 文件,排除指定的文件。 + + :param directory: 要读取的目录 + :param not_check_list: 要排除的文件名列表,不包含路径。默认为空列表 + :return: 包含所有有效 JSON 数据的列表,每个元素为一个字典 + """ + if not_check_list is None: + not_check_list = [''] # 默认排除文件 + + # 获取所有 .json 文件,并排除在 not_check_list 中的文件 + json_files = [f for f in os.listdir(directory) if f.endswith('.json') and f not in not_check_list] + + data_list = [] + + # 遍历每个 JSON 文件 + for json_file in json_files: + json_path = os.path.join(directory, json_file) + + try: + # 打开并加载 JSON 文件 + with open(json_path, 'r', encoding='utf-8') as f: + data = json.load(f) + # 将文件名(去掉 .json)添加到数据中 + data['file_name_json'] = json_file.rstrip(".json") + data_list.append(data) + + except json.JSONDecodeError: + print(f"\033[91mError decoding JSON file: {json_path}\033[0m") + except Exception as e: + print(f"\033[91mError reading file {json_path}: {str(e)}\033[0m") + + return data_list + + +def process_json_data(json_data): + """处理每个 JSON 数据,生成相应参数""" + # 提取 dataset 信息 + dataset_file_name = json_data["dataset_info"]["dataset_file_name"] + dataset_class_name = json_data["dataset_info"]["dataset_class_name"] + data_root = json_data["dataset_info"]["data_root"] + + # 转换 img_scale 为元组 (img_scale_width, img_scale_height) + img_scale = (json_data["dataset_info"]["img_scale_width"], json_data["dataset_info"]["img_scale_height"]) + + # 提取其他必要信息 + train_img_path = json_data["dataset_info"]["paths"]["train_img_path"] + train_seg_map_path = json_data["dataset_info"]["paths"]["train_seg_map_path"] + val_img_path = json_data["dataset_info"]["paths"]["val_img_path"] + val_seg_map_path = json_data["dataset_info"]["paths"]["val_seg_map_path"] + test_img_path = json_data["dataset_info"]["paths"]["test_img_path"] + test_seg_map_path = json_data["dataset_info"]["paths"]["test_seg_map_path"] + + # 提取 label 相关信息 + classes = json_data["label_info"]["classes"] + palette = json_data["label_info"]["palette"] + img_suffix = json_data["label_info"]["img_suffix"] + seg_map_suffix = json_data["label_info"]["seg_map_suffix"] + reduce_zero_label = json_data["label_info"]["reduce_zero_label"] + + # 提取训练相关参数 + # 转换 crop_size 为元组 (crop_size_width, crop_size_height) + crop_size = (json_data["training_info"]["crop_size_width"], json_data["training_info"]["crop_size_height"]) + train_batch_size = json_data["training_info"]["train_batch_size"] + train_num_workers = json_data["training_info"]["train_num_workers"] + val_and_test_batch_size = json_data["training_info"]["val_and_test_batch_size"] + val_and_test_num_workers = json_data["training_info"]["val_and_test_num_workers"] + + return (dataset_file_name, dataset_class_name, data_root, img_scale, train_img_path, train_seg_map_path, val_img_path, val_seg_map_path, test_img_path, test_seg_map_path, + classes, palette, img_suffix, seg_map_suffix, reduce_zero_label, crop_size, train_batch_size, train_num_workers, val_and_test_batch_size, val_and_test_num_workers,) + + +def save_all_record_to_json(output_file, dataset_file_names, classes_all, palette_all, palette_num_all, mean_all, std_all, train_imgs_num): + """构建一个 JSON 文件,用于存储每个数据集的信息""" + # 构建一个字典,用于存储每个数据集的信息 + data_record = {} + + # 假设所有列表长度相同,遍历每个 dataset_file_name + for i in range(len(dataset_file_names)): + data_record[dataset_file_names[i]] = { + "classes": classes_all[i], + "palette": palette_all[i], + "palette_num": palette_num_all[i], + "mean": list(mean_all[i]), + "std": list(std_all[i]), + "train_imgs_num": train_imgs_num[i] + } + + # 将字典写入 JSON 文件 + with open(output_file, 'w', encoding='utf-8') as json_file: + json.dump(data_record, json_file, ensure_ascii=False, indent=6) + + print(f"\033[93m相关数据汇总到 {output_file} successfully!\033[0m") + return True + +if __name__ == '__main__': + ########### 1.超参数-定义训练文件夹路径 ########### + train_parameter_dir = './My_All_In_One/1_Data_Parameter' + all_data_record_json = "All_Data_Record.json" # 记录所有数据 + + ########### 2.1.遍历所有配置文件,并生成对应数据集 ########### + # 定义保存信息的列表 + dataset_file_names = [] + dataset_class_names = [] + classes_all = [] + palette_all = [] + palette_num_all = [] + mean_all = [] + std_all = [] + train_imgs_num = [] + + # 2.1.1. 从 ./1_Data_Parameter 文件夹读取所有 JSON 文件 + json_data_list = load_json_files(train_parameter_dir, not_check_list = [all_data_record_json]) + + # 2.1.2. 遍历每个 JSON 数据文件并生成对应配置文件 + for json_data in json_data_list: + # A. 输出当前处理文件 + print(f"\033[32m正在处理{json_data['file_name_json']}.json文件\033[0m") + + # B. 处理 JSON 数据并提取参数 + (dataset_file_name, dataset_class_name, data_root, img_scale, train_img_path, train_seg_map_path, val_img_path, val_seg_map_path, test_img_path, test_seg_map_path, + classes, palette, img_suffix, seg_map_suffix, reduce_zero_label, crop_size, train_batch_size, train_num_workers, val_and_test_batch_size, val_and_test_num_workers, + ) = process_json_data(json_data) + + # 保存文件名和类别名 + dataset_file_names.append(dataset_file_name) + dataset_class_names.append(dataset_class_name) + + # C. 文件存储位置 + output_configs_base_datasets_my_dataset = f'./configs/_base_/datasets/{dataset_file_name}.py' + output_mmseg_datasets_dataset_file_name = os.path.join(f'./mmseg/datasets/{dataset_file_name}.py') + output_mmseg_datasets_init = os.path.join('./mmseg/datasets/__init__.py') + output_mmseg_utils_class_names = f'./mmseg/utils/class_names.py' + + # D. 运行程序生成配置文件 + # 生成 ./configs/_base_/datasets/{dataset_file_name}.py + print(" ",end='') + success = generate_configs_base_datasets_my_dataset_file( + output_file=output_configs_base_datasets_my_dataset, + dataset_class_name=dataset_class_name, + data_root=data_root, + img_scale=img_scale, + crop_size=crop_size, + train_batch_size=train_batch_size, + train_num_workers=train_num_workers, + val_and_test_batch_size=val_and_test_batch_size, + val_and_test_num_workers=val_and_test_num_workers, + train_img_path=train_img_path, + train_seg_map_path=train_seg_map_path, + val_img_path=val_img_path, + val_seg_map_path=val_seg_map_path, + test_img_path=test_img_path, + test_seg_map_path=test_seg_map_path + ) + + # 生成 ./mmseg/datasets/{dataset_file_name}.py + print(" ",end='') + success, classes, palette = generate_mmseg_datasets_my_dataset_file( + output_file=output_mmseg_datasets_dataset_file_name, + dataset_class_name=dataset_class_name, + classes=classes, + palette=palette, + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label + ) + + mean, std = calculate_pic_std_and_mean( + dataset_dir = os.path.join(data_root, train_img_path) + ) + + # 保存标注类名和颜色 + classes_all.append(classes) + palette_all.append(palette) + palette_num_all.append(len(classes)) + mean_all.append(mean) + std_all.append(std) + train_imgs_num.append(len(os.listdir(os.path.join(data_root, train_img_path)))) + + ########### 2.2.汇总所有信息运行生成 init 和 class_names 文件 ########### + # 生成 ./mmseg/datasets/__init__.py + print(" ",end='') + success = generate_mmseg_datasets_init_file( + output_file=output_mmseg_datasets_init, + dataset_file_names=dataset_file_names, + dataset_class_names=dataset_class_names + ) + + # 生成 ./mmseg/utils/class_names.py + print(" ",end='') + success = generate_mmseg_utils_class_names_file( + output_file=output_mmseg_utils_class_names, + dataset_file_names=dataset_file_names, + classes_all=classes_all, + palette_all=palette_all + ) + + ########### 2.2.汇总dataset_file_names、classes_all、palette_all、palette_num_all所有信息到My_All_In_One/1_Data_Parameter/All_Data_Record.json文件 ########### + output_all_data_record = os.path.join(train_parameter_dir, all_data_record_json) + # 调用函数保存数据 + success = save_all_record_to_json(output_file=output_all_data_record, dataset_file_names=dataset_file_names, classes_all=classes_all, palette_all=palette_all, palette_num_all=palette_num_all, mean_all=mean_all, std_all=std_all, train_imgs_num=train_imgs_num) + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ann_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ann_r50.py new file mode 100644 index 0000000..fc6231d --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ann_r50.py @@ -0,0 +1,101 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'ann_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + ########### 4.程序写入 ########### + # 算法名称解析:ann【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/ann/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_apcnet_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_apcnet_r50.py new file mode 100644 index 0000000..b6f7c7e --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_apcnet_r50.py @@ -0,0 +1,102 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'apcnet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:apcnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/apcnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_beit_upernet.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_beit_upernet.py new file mode 100644 index 0000000..fbc59c4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_beit_upernet.py @@ -0,0 +1,176 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler +from Initial_Alg_Program.Initial_Alg_Gen_5_train_dataloader import generate_train_dataloader + +# 交互式选择 decode_head 的函数 +def select_decode_head(decode_head_choose): + print("可用的 decode head 选项:") + for i, key in enumerate(decode_head_choose.keys()): + print(f"{i + 1}. {key}") + + while True: + try: + # 提示用户输入选择 + choice = int(input("请选择需要的 decode head(输入编号):")) + # 检查输入是否在有效范围内 + if 1 <= choice <= len(decode_head_choose): + selected_key = list(decode_head_choose.keys())[choice - 1] + print(f"你选择了: {selected_key}") + return decode_head_choose[selected_key] + else: + print("输入的编号不正确,请重新输入。") + except ValueError: + print("输入无效,请输入有效的编号。") + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'upernet_beit' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(640, 640)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + # 获取backbone模型、是否需要预训练 + model_list = ['pretrain/beit_base_patch16_224_pt22k_ft22k.pth', 'pretrain/beit_large_patch16_224_pt22k_ft22k.pth'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) # 需要选择是否用预训练模型 + + backbone = create_dict_by_kwargs(type='BEiT', img_size=crop_size,) + + decode_head_loss_decode=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # Way Ori + decode_head_loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # Way 1 + + auxiliary_head_loss_decode=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # Way Ori + auxiliary_head_loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # Way 1 + + if selected_model_name == 'pretrain/beit_base_patch16_224_pt22k_ft22k.pth': + backbone_new=dict( + # patch_size=16, + # in_channels=3, + embed_dims=768, + num_layers=12, + num_heads=12, + # mlp_ratio=4, + out_indices=(3, 5, 7, 11), + # qv_bias=True, + # attn_drop_rate=0.0, + drop_path_rate=0.1, + # norm_cfg=dict(type='LN', eps=1e-6), + # act_cfg=dict(type='GELU'), + # norm_eval=False, + init_values=0.1) + neck = dict(type='Feature2Pyramid', embed_dim=768, rescales=[4, 2, 1, 0.5]) + decode_head=dict(in_channels=[768, 768, 768, 768], num_classes=num_classes, channels=768, norm_cfg=norm_cfg, loss_decode=decode_head_loss_decode) + auxiliary_head=dict(norm_cfg=norm_cfg, in_channels=768, num_classes=num_classes, loss_decode = auxiliary_head_loss_decode) + + optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', # type='OptimWrapper', # TODO Ori + optimizer=dict( + type='AdamW', lr=3e-5, betas=(0.9, 0.999), weight_decay=0.05), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict(num_layers=12, layer_decay_rate=0.9)) + + model_size = 'base' + + train_dataloader, batch_size = generate_train_dataloader(2) + + elif selected_model_name == 'pretrain/beit_large_patch16_224_pt22k_ft22k.pth': + backbone_new=dict( + embed_dims=1024, + num_layers=24, + num_heads=16, + # mlp_ratio=4, + # qv_bias=True, + init_values=1e-6, + drop_path_rate=0.2, + out_indices=[7, 11, 15, 23]) + neck=dict(type='Feature2Pyramid', embed_dim=1024, rescales=[4, 2, 1, 0.5]) + decode_head=dict(in_channels=[1024, 1024, 1024, 1024], num_classes=num_classes, channels=1024, norm_cfg=norm_cfg, loss_decode=decode_head_loss_decode) + auxiliary_head=dict(norm_cfg=norm_cfg, in_channels=1024, num_classes=num_classes, loss_decode = auxiliary_head_loss_decode) + + optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=2e-5, betas=(0.9, 0.999), weight_decay=0.05), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict(num_layers=24, layer_decay_rate=0.95), + accumulative_counts=2) + + model_size = 'large' + + train_dataloader, batch_size = generate_train_dataloader(1) + + # 更新backbone + backbone.update(backbone_new) + + model = dict( + backbone = backbone, + data_preprocessor = model_data_preprocessor, + pretrained = pretrained_pth, + neck = neck, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = optim_wrapper + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:beit【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}-{model_size}_b{batch_size}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/beit/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler, train_dataloader = train_dataloader) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_bisenetv1_r18.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_bisenetv1_r18.py new file mode 100644 index 0000000..95b5d57 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_bisenetv1_r18.py @@ -0,0 +1,133 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'bisenetv1_r18-d32' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(512, 512), (1024, 1024)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + # 获取backbone模型、是否需要预训练 + model_list = ['openmmlab/resnet18_v1c', 'openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list, need_select_pretrained = True) # 需要选择是否用预训练模型 + depth = selected_model_info['depth'] + + if select_pretrained == True: + pretrained_txt = 'Pre' + else: + pretrained_txt = 'NoPre' + + # 模型信息 + if selected_model_name == 'openmmlab/resnet18_v1c' : + backbone_context_channels = (128, 256, 512) + backbone_spatial_channels = (64, 64, 64, 128) + backbone_out_channels = 256 + decode_head_in_channels = 256 + decode_head_channels = 256 + auxiliary_head_in_channels = 128 + auxiliary_head_channels = 64 + + elif selected_model_name == 'openmmlab/resnet50_v1c' or selected_model_name == 'openmmlab/resnet101_v1c' : + backbone_context_channels = (512, 1024, 2048) + backbone_spatial_channels = (256, 256, 256, 512) + backbone_out_channels = 1024 + decode_head_in_channels = 1024 + decode_head_channels = 1024 + auxiliary_head_in_channels = 512 + auxiliary_head_channels = 256 + + # 需要选择预训练模型 + if select_pretrained == True: + backbone_backbone_cfg = create_dict_by_kwargs(type='ResNet', norm_cfg=norm_cfg, depth=depth, init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + else: + backbone_backbone_cfg = create_dict_by_kwargs(type='ResNet', norm_cfg=norm_cfg, depth=depth) + + backbone = create_dict_by_kwargs(context_channels=backbone_context_channels, norm_cfg=norm_cfg, spatial_channels=backbone_spatial_channels, out_channels=backbone_out_channels, backbone_cfg=backbone_backbone_cfg) + + # decode、auxiliary损失下载方式 TODO + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + decode_head = create_dict_by_kwargs(type='FCNHead',in_channels=decode_head_in_channels, channels=decode_head_channels, num_classes=num_classes) + + # auxiliary损失下载方式 TODO # 可更改 + # auxiliary_head_loss_decode_dict = dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO # 可更改 + auxiliary_head_loss_decode_dict = dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) + auxiliary_head = [create_dict_by_kwargs(type='FCNHead', norm_cfg=norm_cfg, in_channels=auxiliary_head_in_channels, channels=auxiliary_head_channels, num_classes=num_classes, align_corners=False, loss_decode=auxiliary_head_loss_decode_dict), + create_dict_by_kwargs(type='FCNHead', norm_cfg=norm_cfg, in_channels=auxiliary_head_in_channels, channels=auxiliary_head_channels, num_classes=num_classes, align_corners=False, loss_decode=auxiliary_head_loss_decode_dict)] + # 获取原始auxiliary_head + auxiliary_head = get_var_from_py_file(alg_file_pth, var_name="model")["auxiliary_head"] + # 新的auxiliary_head + auxiliary_head_new = [create_dict_by_kwargs(type='FCNHead', norm_cfg=norm_cfg, in_channels=auxiliary_head_in_channels, channels=auxiliary_head_channels, num_classes=num_classes, align_corners=False, loss_decode=auxiliary_head_loss_decode_dict), + create_dict_by_kwargs(type='FCNHead', norm_cfg=norm_cfg, in_channels=auxiliary_head_in_channels, channels=auxiliary_head_channels, num_classes=num_classes, align_corners=False, loss_decode=auxiliary_head_loss_decode_dict)] + # 更新auxiliary_head + auxiliary_head = update_list_dict_var(auxiliary_head, auxiliary_head_new) + + # 综合model + model = dict( + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:bisenetv1【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/bisenetv1/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_bisenetv2.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_bisenetv2.py new file mode 100644 index 0000000..2901cff --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_bisenetv2.py @@ -0,0 +1,111 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'bisenetv2' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(512, 512)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + # 3.3.1. 预处理data_preprocessor + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + + # 3.3.2. 采样函数sampler + selected_sampler_name, use_sampler, selected_sampler_info = select_sampler(sampler_list=['OHEMPixelSampler']) + if use_sampler == True: + decode_head_sampler=selected_sampler_info + auxiliary_head_sampler=selected_sampler_info + use_sampler_txt = 'ohempSampler' # 标记 + else: + decode_head_sampler=None + auxiliary_head_sampler=None + use_sampler_txt = '' + + # 3.3.3. 解码器decode_head + # decode、auxiliary损失下载方式 TODO + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) + decode_head = create_dict_by_kwargs(type='FCNHead', loss_decode=decode_head_loss_decode_dict, sampler=decode_head_sampler, num_classes=num_classes) + + # 3.3.3. 辅助部分auxiliary_head + # auxiliary损失下载方式 TODO # 可更改 + auxiliary_head_loss_decode_dict = dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4, reduction="none") # DiceLoss损失函数 # TODO # 可更改 + # auxiliary_head_loss_decode_dict = dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) + # 获取原始auxiliary_head + auxiliary_head = get_var_from_py_file(alg_file_pth, var_name="model")["auxiliary_head"] + # 新的auxiliary_head + auxiliary_head_new = [create_dict_by_kwargs(type='FCNHead', sampler=auxiliary_head_sampler, norm_cfg=norm_cfg, align_corners=False, loss_decode=auxiliary_head_loss_decode_dict, num_classes=num_classes,), + create_dict_by_kwargs(type='FCNHead', sampler=auxiliary_head_sampler, norm_cfg=norm_cfg, align_corners=False, loss_decode=auxiliary_head_loss_decode_dict, num_classes=num_classes,), + create_dict_by_kwargs(type='FCNHead', sampler=auxiliary_head_sampler, norm_cfg=norm_cfg, align_corners=False, loss_decode=auxiliary_head_loss_decode_dict, num_classes=num_classes,), + create_dict_by_kwargs(type='FCNHead', sampler=auxiliary_head_sampler, norm_cfg=norm_cfg, align_corners=False, loss_decode=auxiliary_head_loss_decode_dict, num_classes=num_classes,)] + # 更新auxiliary_head + auxiliary_head = update_list_dict_var(auxiliary_head, auxiliary_head_new) + + # 3.3.4. 综合model + model = dict( + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 4.1. 算法名称解析:bisenetv2【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_fcn_{use_sampler_txt+'_' if use_sampler_txt != '' else ''}g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/bisenetv2/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 4.2. 将信息临时写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ccnet_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ccnet_r50.py new file mode 100644 index 0000000..d351014 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ccnet_r50.py @@ -0,0 +1,102 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'ccnet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:ccnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/ccnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_cgnet_fcn.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_cgnet_fcn.py new file mode 100644 index 0000000..8509c59 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_cgnet_fcn.py @@ -0,0 +1,95 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'cgnet' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(680, 680), (512, 1024)]) # 选择切割大小 + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head = get_var_from_py_file(os.path.join('./configs/_base_/models', alg_file_name+'.py'), 'model')['decode_head'] + + # Way Ori: loss_decode + decode_head_loss_decode_dict = dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + class_weight=[ + 2.5959933, 6.7415504, 3.5354059, 9.8663225, 9.690899, 9.369352, + 10.289121, 9.953208, 4.3097677, 9.490387, 7.674431, 9.396905, + 10.347791, 6.3927646, 10.226669, 10.241062, 10.280587, + 10.396974, 10.055647 + ] + ) + # Way 1: loss_decode + decode_head_loss_decode_dict = dict(_delete_=True, type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + decode_head['_delete_'] = True + decode_head['loss_decode'] = decode_head_loss_decode_dict + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + # 综合model + model = dict(data_preprocessor = model_data_preprocessor, decode_head=decode_head) + + # test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:cgnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_fcn_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/cgnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_danet_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_danet_r50.py new file mode 100644 index 0000000..06b9998 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_danet_r50.py @@ -0,0 +1,102 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'danet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:danet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/danet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ddrnet.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ddrnet.py new file mode 100644 index 0000000..09edf29 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_ddrnet.py @@ -0,0 +1,95 @@ +import os, sys, argparse, json +import importlib.util +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'ddrnet' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(512, 512)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + # 3.3.1. 预处理data_preprocessor + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + + # # 3.3.2. 骨架backbone、解码器decode_head + model_list = ["openmmlab/ddrnet23-s", "openmmlab/ddrnet23"] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + if selected_model_name == "openmmlab/ddrnet23-s": + backbone = dict(channels=32, init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + decode_head = dict(in_channels=32 * 4, channels=64, num_classes=num_classes) + model_size = 'small' + elif selected_model_name == "openmmlab/ddrnet23": + backbone = dict(channels=64, init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + decode_head = dict(in_channels=64 * 4, channels=128, num_classes=num_classes) + model_size = 'normal' + else: + quit("Error: 未知的模型名称") + + # 3.3.4. 综合model + model = dict( + data_preprocessor = model_data_preprocessor, + backbone = backbone, + decode_head = decode_head, + ) + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 4.1. 算法名称解析:bisenetv2【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_{model_size}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/ddrnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 4.2. 将信息临时写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_deeplabv3_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_deeplabv3_r50.py new file mode 100644 index 0000000..37d2f88 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_deeplabv3_r50.py @@ -0,0 +1,135 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'deeplabv3_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769),(1280,1280)]) # 选择切割大小 + model_list = ['openmmlab/resnet18_v1c', 'openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c', 'torchvision://resnet18', 'torchvision://resnet50', 'torchvision://resnet101', ] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + type_model = selected_model_info['type'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + # 3.3.2. 采样函数sampler + selected_sampler_name, use_sampler, selected_sampler_info = select_sampler(sampler_list=['OHEMPixelSampler']) + if use_sampler == True: + decode_head_sampler=selected_sampler_info + auxiliary_head_sampler=selected_sampler_info + use_sampler_txt = 'ohempSampler' # 标记 + backbone_dilations=(1, 1, 1, 2) + backbone_strides=(1, 2, 2, 1) + backbone_multi_grid=(1, 2, 4) + decode_head_dilations=(1, 6, 12, 18) + else: + decode_head_sampler=None + auxiliary_head_sampler=None + use_sampler_txt = '' + backbone_dilations=None # (1, 1, 2, 4), + backbone_strides=None # (1, 2, 1, 1) + backbone_multi_grid=None # 没有 + decode_head_dilations=None # (1, 12, 24, 36), + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + + backbone = create_dict_by_kwargs(depth=depth, type=type_model, dilations=backbone_dilations, strides=backbone_strides, multi_grid=backbone_multi_grid) # generate_model_backbone(depth=depth,) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + if depth == 18: + decode_head = create_dict_by_kwargs(sampler=decode_head_sampler, dilations=decode_head_dilations, in_channels=512, channels=128, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + elif depth == 101 or depth == 50: + decode_head = create_dict_by_kwargs(sampler=decode_head_sampler, dilations=decode_head_dilations, in_channels=2048, channels=512, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + + if depth == 18: + auxiliary_head = create_dict_by_kwargs(in_channels=256, channels=64, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + elif depth == 101 or depth == 50: + auxiliary_head = create_dict_by_kwargs(in_channels=1024, channels=256, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:deeplabv3【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_{use_sampler_txt+'_' if use_sampler_txt != '' else ''}g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/deeplabv3/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_deeplabv3plus_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_deeplabv3plus_r50.py new file mode 100644 index 0000000..0cea160 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_deeplabv3plus_r50.py @@ -0,0 +1,135 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'deeplabv3plus_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769),(1280,1280)]) # 选择切割大小 + model_list = ['openmmlab/resnet18_v1c', 'openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c', 'torchvision://resnet18', 'torchvision://resnet50', 'torchvision://resnet101', ] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + type_model = selected_model_info['type'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + # 3.3.2. 采样函数sampler + selected_sampler_name, use_sampler, selected_sampler_info = select_sampler(sampler_list=['OHEMPixelSampler']) + if use_sampler == True: + decode_head_sampler=selected_sampler_info + auxiliary_head_sampler=selected_sampler_info + use_sampler_txt = 'ohempSampler' # 标记 + backbone_dilations=(1, 1, 1, 2) + backbone_strides=(1, 2, 2, 1) + backbone_multi_grid=(1, 2, 4) + decode_head_dilations=(1, 6, 12, 18) + else: + decode_head_sampler=None + auxiliary_head_sampler=None + use_sampler_txt = '' + backbone_dilations=None # (1, 1, 2, 4), + backbone_strides=None # (1, 2, 1, 1) + backbone_multi_grid=None # 没有 + decode_head_dilations=None # (1, 12, 24, 36), + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + + backbone = create_dict_by_kwargs(depth=depth, type=type_model, dilations=backbone_dilations, strides=backbone_strides, multi_grid=backbone_multi_grid) # generate_model_backbone(depth=depth,) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + if depth == 18: + decode_head = create_dict_by_kwargs(sampler=decode_head_sampler, dilations=decode_head_dilations, c1_in_channels=64, c1_channels=12, in_channels=512, channels=128, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + elif depth == 101 or depth == 50: + decode_head = create_dict_by_kwargs(sampler=decode_head_sampler, dilations=decode_head_dilations, c1_in_channels=256, c1_channels=48, in_channels=2048, channels=512, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + + if depth == 18: + auxiliary_head = create_dict_by_kwargs(in_channels=256, channels=64, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + elif depth == 101 or depth == 50: + auxiliary_head = create_dict_by_kwargs(in_channels=1024, channels=256, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:deeplabv3plus【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_{use_sampler_txt+'_' if use_sampler_txt != '' else ''}g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/deeplabv3plus/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_dnlnet_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_dnlnet_r50.py new file mode 100644 index 0000000..80e29d8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_dnlnet_r50.py @@ -0,0 +1,102 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'dnl_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:dnlnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/dnlnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_dpt_vit.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_dpt_vit.py new file mode 100644 index 0000000..d292698 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_dpt_vit.py @@ -0,0 +1,100 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler +from Initial_Alg_Program.Initial_Alg_Gen_5_train_dataloader import generate_train_dataloader + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'dpt_vit-b16' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(1024, 1024)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + # 3.3.1. 预处理data_preprocessor + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + + model_list = ['pretrain/vit-b16_p16_224-80ecf9dd.pth'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + model_data_preprocessor = data_preprocessor + + # 3.3.2. 解码器decode_head + # decode、auxiliary损失下载方式 TODO + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # 默认 TODO + decode_head = create_dict_by_kwargs(loss_decode=decode_head_loss_decode_dict, num_classes=num_classes) + + # 3.3.3. 辅助部分auxiliary_head [空] + auxiliary_head = None + + # 3.3.4. 综合model + model = dict( + data_preprocessor = model_data_preprocessor, + pretrained = pretrained, + decode_head = decode_head, + # auxiliary_head = auxiliary_head, + ) + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = generate_optim_wrapper(type_of_back_bone = "Vit") + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 3.5. 生成train_dataloader部分[1个batch_size就要20G] ########### + train_dataloader, batch_size = generate_train_dataloader(batch_size_default=1) + + ########### 4.程序写入 ########### + # 4.1. 算法名称解析:dpt_vit【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_g{GPU_num}_b{batch_size}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/dpt/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler, train_dataloader=train_dataloader) + + # 4.2. 将信息临时写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_emanet_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_emanet_r50.py new file mode 100644 index 0000000..eda830a --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_emanet_r50.py @@ -0,0 +1,106 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'emanet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + # test_cfg_mode = None + # test_cfg_crop_div_stride = crop_size + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + # auxiliary_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:emanet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/emanet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_encnet_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_encnet_r50.py new file mode 100644 index 0000000..4ceb808 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_encnet_r50.py @@ -0,0 +1,106 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'encnet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + # test_cfg_mode = None + # test_cfg_crop_div_stride = crop_size + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + # auxiliary_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:encnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/encnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_erfnet_fcn.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_erfnet_fcn.py new file mode 100644 index 0000000..85e1ecc --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_erfnet_fcn.py @@ -0,0 +1,83 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'erfnet_fcn' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512,1024), (512, 512)]) # 选择切割大小 + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + # decode_head_loss_decode_dict = dict(type='CrossEntropyLoss', + # use_sigmoid=False, + # loss_weight=1.0) + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + decode_head=dict(loss_decode=decode_head_loss_decode_dict) + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + # 综合model + model = dict(data_preprocessor = model_data_preprocessor, decode_head=decode_head) + + # test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:erfnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_fcn_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/erfnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fast_scnn.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fast_scnn.py new file mode 100644 index 0000000..0db6584 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fast_scnn.py @@ -0,0 +1,89 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'fast_scnn' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512,1024), (512, 512)]) # 选择切割大小 + + ########### 3.生成各个部分 ########### + # 3.1. base、norm_cfg、data_preprocessor部分 + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + # 3.2. model部分 + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + # decode损失 + decode_head_loss_decode_dict = dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + decode_head=dict(loss_decode=decode_head_loss_decode_dict) + + # auxiliary损失 + auxiliary_head = get_var_from_py_file(os.path.join('./configs/_base_/models', alg_file_name+'.py'), 'model')['auxiliary_head'] + for i in range(len(auxiliary_head)): + auxiliary_head[i]['loss_decode']['use_sigmoid']=False + auxiliary_head[i]['loss_decode']['type']='DiceLoss' + auxiliary_head[i]['num_classes']=num_classes + auxiliary_head[i]['norm_cfg']=norm_cfg + + # 综合model + model = dict(data_preprocessor = model_data_preprocessor, decode_head = decode_head, auxiliary_head=auxiliary_head) + + # 3.3. optim_wrapper、param_scheduler + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:fastscnn【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/fastscnn/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fastfcn_r50_neck.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fastfcn_r50_neck.py new file mode 100644 index 0000000..9581c68 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fastfcn_r50_neck.py @@ -0,0 +1,189 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +# 交互式选择 decode_head 的函数 +def select_decode_head(decode_head_choose): + print("可用的 decode head 选项:") + for i, key in enumerate(decode_head_choose.keys()): + print(f"{i + 1}. {key}") + + while True: + try: + # 提示用户输入选择 + choice = int(input("请选择需要的 decode head(输入编号):")) + # 检查输入是否在有效范围内 + if 1 <= choice <= len(decode_head_choose): + selected_key = list(decode_head_choose.keys())[choice - 1] + print(f"你选择了: {selected_key}") + return decode_head_choose[selected_key] + else: + print("输入的编号不正确,请重新输入。") + except ValueError: + print("输入无效,请输入有效的编号。") + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'fastfcn_r50-d32_jpu_psp' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(512, 512), (1024, 1024)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + # 获取backbone模型、是否需要预训练 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) # 需要选择是否用预训练模型 + depth = selected_model_info['depth'] + + backbone = create_dict_by_kwargs(norm_cfg=norm_cfg, depth=depth) + + # 定义 decode_head 的选项字典 + decode_head_choose = { + 'psp-PSPHead': { + 'decode_head':dict( + type='PSPHead', + in_channels=2048, + in_index=2, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, # 假设 norm_cfg 预定义 + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) + ), + 'decode_head_name':'aspp' + }, + 'enc-EncHead': { + 'decode_head':dict( + _delete_=True, + type='EncHead', + in_channels=[512, 1024, 2048], + in_index=(0, 1, 2), + channels=512, + num_codes=32, + use_se_loss=True, + add_lateral=False, + dropout_ratio=0.1, + num_classes=num_classes, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_se_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.2) + ), + 'decode_head_name':'aspp' + }, + 'aspp-ASPPHead': { + 'decode_head':dict( + _delete_=True, + type='ASPPHead', + in_channels=2048, + in_index=2, + channels=512, + dilations=(1, 12, 24, 36), + dropout_ratio=0.1, + num_classes=num_classes, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) + ), + 'decode_head_name':'aspp' + } + } + + decode_head_dict = select_decode_head(decode_head_choose=decode_head_choose) + decode_head = decode_head_dict['decode_head'] + decode_head_name = decode_head_dict['decode_head_name'] + + decode_head_loss_decode_type = 'DiceLoss' # Way 1: 更改 Loss + # decode_head_loss_decode_type = 'CrossEntropyLoss' # Way ori: 不更改 + + # 修改decode_head中loss_decode type + if 'loss_se_decode' in decode_head.keys(): + decode_head['loss_se_decode']['type'] = decode_head_loss_decode_type + if 'loss_decode' in decode_head.keys(): + decode_head['loss_decode']['type'] = decode_head_loss_decode_type + + # auxiliary 为 None + auxiliary_head_loss_decode_dict = dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) + auxiliary_head = dict(loss_decode=auxiliary_head_loss_decode_dict, norm_cfg=norm_cfg, num_classes=num_classes) # Way 1: 更改 Loss + # auxiliary_head = dict(norm_cfg=norm_cfg, num_classes=num_classes) # Way ori: 不更改 + + # 综合model + if auxiliary_head == None: + model = dict( + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head + ) + else: + model = dict( + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head + ) + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:fastfcn【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_jpu_{decode_head_name}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/fastfcn/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fcn_r18.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fcn_r18.py new file mode 100644 index 0000000..7c318d3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_fcn_r18.py @@ -0,0 +1,132 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'fcn_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + # 如果crop_size更大则调整dilations和strides + if crop_size[0]*crop_size[1] > 512*512: + backbone_dilations = (1, 1, 1, 2) # 每一步更精细信息 + backbone_strides = (1, 2, 2, 1) # 步子走的大 + decode_head_dilation = 6 # 每一步更多信息 + auxiliary_head_dilation = 6 # 每一步更多信息 + else: + backbone_dilations = (1, 1, 2, 4) # 每一步更多信息 + backbone_strides = (1, 2, 1, 1) # 步子走的小 + decode_head_dilation = 1 # 每一步更精细信息 + auxiliary_head_dilation = 1 # 每一步更精细信息 + + model_list = ['torchvision://resnet18', 'torchvision://resnet50', 'torchvision://resnet101', 'open-mmlab/resnet18_v1c', 'openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + model_type = selected_model_info['type'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 模型信息 + if str(depth) == '18' : + backbone_out_channels = 256 + decode_head_in_channels = 512 # + decode_head_channels = 128 # + auxiliary_head_in_channels = 256 # + auxiliary_head_channels = 64 # + + elif str(depth) == '50' or str(depth) == '101': + backbone_out_channels = 1024 + decode_head_in_channels = 2048 # + decode_head_channels = 512 # + auxiliary_head_in_channels = 1024 # + auxiliary_head_channels = 256 # + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + # auxiliary_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = create_dict_by_kwargs(type=model_type, depth=depth, strides = backbone_strides, dilations=backbone_dilations) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = create_dict_by_kwargs(dilation=decode_head_dilation, channels=decode_head_channels, in_channels=decode_head_in_channels, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = create_dict_by_kwargs(dilation=auxiliary_head_dilation, channels=auxiliary_head_channels, in_channels=auxiliary_head_in_channels, num_classes=num_classes, loss_decode=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:fcn【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/fcn/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_gcnet_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_gcnet_r50.py new file mode 100644 index 0000000..fee2399 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_gcnet_r50.py @@ -0,0 +1,106 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'gcnet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + # test_cfg_mode = None + # test_cfg_crop_div_stride = crop_size + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + # auxiliary_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:gcnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/gcnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_hrnet_fcn.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_hrnet_fcn.py new file mode 100644 index 0000000..c9f69ed --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_hrnet_fcn.py @@ -0,0 +1,144 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'fcn_hr18' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + # # 如果crop_size更大则调整dilations和strides + # if crop_size[0]*crop_size[1] > 512*512: + # backbone_dilations = (1, 1, 1, 2) # 每一步更精细信息 + # backbone_strides = (1, 2, 2, 1) # 步子走的大 + # decode_head_dilation = 6 # 每一步更多信息 + # auxiliary_head_dilation = 6 # 每一步更多信息 + # else: + # backbone_dilations = (1, 1, 2, 4) # 每一步更多信息 + # backbone_strides = (1, 2, 1, 1) # 步子走的小 + # decode_head_dilation = 1 # 每一步更精细信息 + # auxiliary_head_dilation = 1 # 每一步更精细信息 + + model_list = ['open-mmlab://msra/hrnetv2_w18', 'open-mmlab://msra/hrnetv2_w18_small', 'open-mmlab://msra/hrnetv2_w48'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 模型信息 + if selected_model_name == 'open-mmlab://msra/hrnetv2_w18': + backbone_extra=dict( + stage1=dict(num_blocks=(4, ), num_channels=(64, )), + stage2=dict(num_blocks=(4, 4), num_channels=(18, 36)), + stage3=dict(num_modules=4, num_blocks=(4, 4, 4), num_channels=(18, 36, 72)), + stage4=dict(num_modules=3, num_blocks=(4, 4, 4, 4), num_channels=(18, 36, 72, 144))) + + decode_head_channels = dict(in_channels=[18, 36, 72, 144], channels=sum([18, 36, 72, 144])) + alg_text = 'w18' + elif selected_model_name == 'open-mmlab://msra/hrnetv2_w18_small': + backbone_extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))) + + decode_head_channels = dict(in_channels=[18, 36, 72, 144], channels=sum([18, 36, 72, 144])) # 同上 + alg_text = 'w18-small' + elif selected_model_name == 'open-mmlab://msra/hrnetv2_w48': + # 改变channel + backbone_extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384))) + + decode_head_channels = dict(in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384])) + alg_text = 'w48' + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # Way: Ori TODO + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # Way: 1 TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = create_dict_by_kwargs(extra = backbone_extra) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = create_dict_by_kwargs(num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners) + decode_head.update(decode_head_channels) # 加入channels相关信息 + + auxiliary_head = None + + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + # auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:hrnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_{alg_text}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/hrnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_icnet_r18.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_icnet_r18.py new file mode 100644 index 0000000..da8d152 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_icnet_r18.py @@ -0,0 +1,124 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'icnet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(832, 832), (512, 512)]) # 选择切割大小 + + model_list = ['openmmlab/resnet18_v1c', 'openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list, need_select_pretrained=True) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 需要预训练模型 + if select_pretrained == True: + backbone_backbone_cfg_init_cfg = dict(type='Pretrained', checkpoint=pretrained_pth) + else: + backbone_cfg_init_cfg = None + backbone_backbone_cfg = create_dict_by_kwargs(depth=depth, init_cfg=backbone_backbone_cfg_init_cfg) + + # 模型信息 + if str(depth) == '18' : + backbone_layer_channels = (128, 512) + elif str(depth) == '50' or str(depth) == '101': + backbone_layer_channels = (512, 2048) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # Way 1: decode损失函数 # TODO + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # Way Ori: decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # Way 1: DiceLoss损失函数 # TODO + # auxiliary_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # Way Ori: DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + backbone = create_dict_by_kwargs(backbone_cfg = backbone_backbone_cfg, layer_channels=backbone_layer_channels, norm_cfg=norm_cfg, align_corners=align_corners) + + neck = create_dict_by_kwargs(norm_cfg=norm_cfg, align_corners=align_corners) + + decode_head = create_dict_by_kwargs(num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners) + + auxiliary_head = get_var_from_py_file(os.path.join('./configs/_base_/models', alg_file_name+'.py'), 'model')['auxiliary_head'] + for i in range(len(auxiliary_head)): + auxiliary_head[i].update(num_classes=num_classes, loss_decode=auxiliary_head_loss_decode_dict, align_corners=align_corners) + + # 综合model + model = dict( + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:icnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/icnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_isanet_r50.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_isanet_r50.py new file mode 100644 index 0000000..e797c6b --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_isanet_r50.py @@ -0,0 +1,106 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'isanet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]) # 选择切割大小 + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + # test_cfg_mode = None + # test_cfg_crop_div_stride = crop_size + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + # auxiliary_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + backbone = generate_model_backbone(depth=depth) + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:isanet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/isanet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_knet.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_knet.py new file mode 100644 index 0000000..35b7033 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_knet.py @@ -0,0 +1,276 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler +from Initial_Alg_Program.Initial_Alg_Gen_5_train_dataloader import generate_train_dataloader + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'knet_r50-d8_my' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) # _base_无算法 + + crop_size = select_crop_size(predefined_options=[(512, 512), (640,640)]) # 选择切割大小 + + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + # 选择对应的模型 + config_dict = { + 'DeepLabV3': { + 'decode_head_kernel_generate_head_dict': dict( + _delete_ = True, + type='ASPPHead', + in_channels=2048, + in_index=3, + channels=512, + dilations=(1, 12, 24, 36), + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False), + 'backbone_dilations': (1, 1, 2, 4), + 'backbone_strides': (1, 2, 1, 1), + 'auxiliary_head_in_channels':1024 + }, + 'PSPNet': { + 'decode_head_kernel_generate_head_dict': dict( + _delete_ = True, + type='PSPHead', + in_channels=2048, + in_index=3, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False), + 'backbone_dilations': (1, 1, 2, 4), + 'backbone_strides': (1, 2, 1, 1), + 'auxiliary_head_in_channels':1024 + }, + 'FCN': { + 'decode_head_kernel_generate_head_dict': dict( + _delete_ = True, + type='FCNHead', + in_channels=2048, + in_index=3, + channels=512, + num_convs=2, + concat_input=True, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False), + 'backbone_dilations': (1, 1, 2, 4), + 'backbone_strides': (1, 2, 1, 1), + 'auxiliary_head_in_channels':1024 + }, + 'UPerNet': { + 'decode_head_kernel_generate_head_dict': dict( + type='UPerHead', + in_channels=[256, 512, 1024, 2048], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False), + 'backbone_dilations': (1, 1, 1, 1), + 'backbone_strides': (1, 2, 2, 2), + 'auxiliary_head_in_channels':1024 + } + } + + models = ['DeepLabV3', 'PSPNet', 'FCN', 'UPerNet'] + print("请你选择对应的模型:") + while True: + for index, model in enumerate(models, 1): + print(f"{index}. {model}") + try: + user_input = int(input("Enter your choice (1-4): ")) + if 1 <= user_input <= 4: + model_choice = models[user_input - 1] + break + else: + print("Invalid input, please enter a number between 1 and 4.") + except ValueError: + print("Invalid input, please enter a valid number.") + + decode_head_kernel_generate_head_dict = config_dict[model_choice]['decode_head_kernel_generate_head_dict'] + backbone_dilations = config_dict[model_choice]['backbone_dilations'] + backbone_strides = config_dict[model_choice]['backbone_strides'] + auxiliary_head_in_channels = config_dict[model_choice]['auxiliary_head_in_channels'] + + model_list = ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c', "pretrain/swin_tiny-f41b89d3.pth", "pretrain/swin_large-d5bdebaf.pth"] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + if selected_model_info['type'] == 'ResNetV1c': + depth = selected_model_info['depth'] + backbone = create_dict_by_kwargs(depth = depth, norm_cfg=norm_cfg, dilations=backbone_dilations, strides=backbone_strides) + elif selected_model_info['type'] == 'swin': + if selected_model_name == 'pretrain/swin_tiny-f41b89d3.pth': + backbone = dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, # + depths=[2, 2, 6, 2], # + num_heads=[3, 6, 12, 24], # + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, # + use_abs_pos_embed=False, + patch_norm=True, + out_indices=(0, 1, 2, 3)) + if selected_model_name == 'pretrain/swin_large-d5bdebaf.pth': + backbone = dict( + _delete_=True, + type='SwinTransformer', + embed_dims=192, # + depths=[2, 2, 18, 2], # + num_heads=[6, 12, 24, 48], # + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.4, # + use_abs_pos_embed=False, + patch_norm=True, + out_indices=(0, 1, 2, 3)) + backbone.update(create_dict_by_kwargs(norm_cfg=dict(type='LN'))) # TODO Transformer一类的内容norm_cfg都设置为LN TODO # 没有 dilations 和 strides + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # 如果使用slide模式,则开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + else: + align_corners = False + test_slide = 'no_testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + + # 3.2. decode_head + decode_head = get_var_from_py_file(os.path.join('./configs/_base_/models', alg_file_name+'.py'), 'model')['decode_head'] + + decode_head_kernel_update_head_list = decode_head['kernel_update_head'] + for i in range(len(decode_head_kernel_update_head_list)): + decode_head_kernel_update_head_list[i]['num_classes'] = num_classes + decode_head['kernel_update_head'] = decode_head_kernel_update_head_list + + decode_head_kernel_generate_head_dict['num_classes'] = num_classes + if model_choice == 'UPerNet': + if selected_model_name == 'pretrain/swin_tiny-f41b89d3.pth': + decode_head_kernel_generate_head_dict['in_channels'] = [96, 192, 384, 768] + auxiliary_head_in_channels = 384 + elif selected_model_name == 'pretrain/swin_large-d5bdebaf.pth': + decode_head_kernel_generate_head_dict['in_channels'] = [192, 384, 768, 1536] + auxiliary_head_in_channels = 768 + elif model_choice in ['DeepLabV3', 'PSPNet', 'FCN']: + if selected_model_name == 'pretrain/swin_tiny-f41b89d3.pth': + decode_head_kernel_generate_head_dict['in_channels'] = 768 + auxiliary_head_in_channels = 384 + elif selected_model_name == 'pretrain/swin_large-d5bdebaf.pth': + decode_head_kernel_generate_head_dict['in_channels'] = 1536 + auxiliary_head_in_channels = 768 + + + decode_head_kernel_generate_head_dict_loss_decode = dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # Way Ori + decode_head_kernel_generate_head_dict_loss_decode = dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # Way 1 + decode_head_kernel_generate_head_dict['loss_decode'] = decode_head_kernel_generate_head_dict_loss_decode + + decode_head['kernel_generate_head'] = decode_head_kernel_generate_head_dict + + decode_head['kernel_generate_head']['align_corners'] = align_corners + + # 3.2. auxiliary_head + auxiliary_head = get_var_from_py_file(os.path.join('./configs/_base_/models', alg_file_name+'.py'), 'model')['auxiliary_head'] + auxiliary_head_loss_decode = dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # Way Ori + auxiliary_head_loss_decode = dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # Way 1 + auxiliary_head['loss_decode'] = auxiliary_head_loss_decode + auxiliary_head['align_corners'] = align_corners + auxiliary_head['in_channels'] = auxiliary_head_in_channels + + # 综合model + model = dict( + backbone = backbone, + pretrained = pretrained_pth, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + # 4. train_dataloader + train_dataloader, batch_size, num_workers = generate_train_dataloader(batch_size_default=2, num_workers_default=2) + if selected_model_info['type'] == 'ResNetV1c': + optim_wrapper = generate_optim_wrapper() + elif selected_model_info['type'] == 'swin': + optim_wrapper = generate_optim_wrapper('swin') + + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + + ########### 4.程序写入 ########### + # 算法名称解析:knet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + if selected_model_info['type'] == 'ResNetV1c': + alg_file_name = f"{alg_name}_r{depth}_{model_choice}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + elif selected_model_info['type'] == 'swin': + alg_file_name = f"{alg_name}_swin_{selected_model_info['size']}_{model_choice}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/knet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, train_dataloader=train_dataloader, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_mae_upernet.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_mae_upernet.py new file mode 100644 index 0000000..df44b16 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_mae_upernet.py @@ -0,0 +1,136 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler +from Initial_Alg_Program.Initial_Alg_Gen_5_train_dataloader import generate_train_dataloader + +# 交互式选择 decode_head 的函数 +def select_decode_head(decode_head_choose): + print("可用的 decode head 选项:") + for i, key in enumerate(decode_head_choose.keys()): + print(f"{i + 1}. {key}") + + while True: + try: + # 提示用户输入选择 + choice = int(input("请选择需要的 decode head(输入编号):")) + # 检查输入是否在有效范围内 + if 1 <= choice <= len(decode_head_choose): + selected_key = list(decode_head_choose.keys())[choice - 1] + print(f"你选择了: {selected_key}") + return decode_head_choose[selected_key] + else: + print("输入的编号不正确,请重新输入。") + except ValueError: + print("输入无效,请输入有效的编号。") + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'upernet_mae' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(512, 512)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + # 获取backbone模型、是否需要预训练 + model_list = ["mae_pretrain_vit_base_mmcls.pth"] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) # 需要选择是否用预训练模型 + + backbone = create_dict_by_kwargs(type='MAE', img_size=crop_size, init_values=1.0) + + decode_head_loss_decode=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # Way Ori + decode_head_loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # Way 1 + + auxiliary_head_loss_decode=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # Way Ori + auxiliary_head_loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # Way 1 + + neck=dict(embed_dim=768, rescales=[4, 2, 1, 0.5]) + decode_head=dict(in_channels=[768, 768, 768, 768], channels=768, num_classes=num_classes, norm_cfg=norm_cfg, loss_decode=decode_head_loss_decode) + auxiliary_head=dict(in_channels=768, norm_cfg=norm_cfg, num_classes=num_classes, loss_decode = auxiliary_head_loss_decode) + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size, select_slide=True) + test_cfg = generate_model_test_cfg(test_cfg_mode='slide', crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + + optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', # type='OptimWrapper', # TODO Ori + optimizer=dict( + type='AdamW', lr=3e-5, betas=(0.9, 0.999), weight_decay=0.05), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict(num_layers=12, layer_decay_rate=0.9)) + + model_size = 'base' + + train_dataloader, batch_size = generate_train_dataloader(4) + + model = dict( + backbone = backbone, + data_preprocessor = model_data_preprocessor, + pretrained = pretrained_pth, + decode_head = decode_head, + neck = neck, + auxiliary_head = auxiliary_head, + test_cfg = test_cfg + ) + + ########### 3.4. 生成fp16部分 ########### + fp16 = dict(loss_scale='dynamic') # mixed precision + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = optim_wrapper + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:beit【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}-{model_size}_b{batch_size}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-testslide.py" + output_configs_alg_my_alg = os.path.join(f'./configs/mae/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, fp16=fp16,optim_wrapper=optim_wrapper, param_scheduler=param_scheduler, train_dataloader = train_dataloader) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_mask2former.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_mask2former.py new file mode 100644 index 0000000..70d8c8a --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_mask2former.py @@ -0,0 +1,284 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler +from Initial_Alg_Program.Initial_Alg_Gen_5_train_dataloader import generate_train_dataloader + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'mask2former_my' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) # _base_无算法 + + crop_size = select_crop_size(predefined_options=[(512, 512), (640, 640), (512, 1024)]) # 选择切割大小 + + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + # 3.1. 选择backbone + model_list = ['torchvision://resnet50', 'torchvision://resnet101', 'pretrain/swin_large-6580f57d.pth', 'pretrain/swin_base-e5c09f74.pth', 'pretrain/swin_small-7ba6d6dd.pth', 'pretrain/swin_tiny-1cdeb081.pth'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + + if selected_model_info['type'] == 'ResNet': + depth = selected_model_info['depth'] + backbone = create_dict_by_kwargs(depth = depth, type=selected_model_info['type'], norm_cfg=norm_cfg, num_stages=4, frozen_stages=-1, style='pytorch', init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + in_channels = [256, 512, 1024, 2048] + elif selected_model_info['type'] == 'swin': + if selected_model_name == 'pretrain/swin_tiny-1cdeb081.pth': + in_channels = [96, 192, 384, 768] + depths=[2, 2, 6, 2] + backbone = dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=224, + embed_dims=96, + depths=depths, + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + # out_indices=(0, 1, 2, 3), + with_cp=False, + # frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + if selected_model_name == 'pretrain/swin_small-7ba6d6dd.pth': + depths=[2, 2, 18, 2] + in_channels = [96, 192, 384, 768] + backbone = dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=224, + embed_dims=96, + depths=depths, + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + # out_indices=(0, 1, 2, 3), + with_cp=False, + # frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + if selected_model_name == 'pretrain/swin_base-e5c09f74.pth': + depths=[2, 2, 18, 2] + in_channels = [128, 256, 512, 1024] + backbone = dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=128, + depths=depths, + num_heads=[4, 8, 16, 32], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + # out_indices=(0, 1, 2, 3), + with_cp=False, + # frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + if selected_model_name == 'pretrain/swin_large-6580f57d.pth': + in_channels = [192, 384, 768, 1536] + depths=[2, 2, 18, 2] + backbone = dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=depths, + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + # out_indices=(0, 1, 2, 3), + with_cp=False, + # frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + # set all layers in backbone to lr_mult=0.1 + # set all norm layers, position_embeding, + # query_embeding, level_embeding to decay_multi=0.0 + backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) + backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) + embed_multi = dict(lr_mult=1.0, decay_mult=0.0) + custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi + } + custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) + }) + custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) + }) + + # 3.2. decode_head + decode_head = dict(num_classes=num_classes, in_channels=in_channels, + loss_cls=dict(class_weight=[1.0] * num_classes + [0.1])) + + # 3.3. optimizer部分 + if selected_model_info['type'] == 'ResNet': + # optimizer + embed_multi = dict(lr_mult=1.0, decay_mult=0.0) + optimizer = dict( + type='AdamW', lr=0.0001, weight_decay=0.05, eps=1e-8, betas=(0.9, 0.999)) + optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi, + }, + norm_decay_mult=0.0)) + train_dataloader, batch_size, num_workers = generate_train_dataloader(batch_size_default=4, num_workers_default=4) + elif selected_model_info['type'] == 'swin': + # set all layers in backbone to lr_mult=0.1 + # set all norm layers, position_embeding, + # query_embeding, level_embeding to decay_multi=0.0 + backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) + backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) + embed_multi = dict(lr_mult=1.0, decay_mult=0.0) + custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi + } + custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) + }) + custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) + }) + # optimizer + optimizer = dict( + type='AdamW', lr=0.0001, weight_decay=0.05, eps=1e-8, betas=(0.9, 0.999)) + optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg={'custom_keys':custom_keys, 'norm_decay_mult':0.0}) + + # 更新train_dataloader + train_dataloader, batch_size, num_workers = generate_train_dataloader(batch_size_default=2, num_workers_default=2) + + # 3.4. train_dataloader部分 + + train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomChoiceResize', + scales=[int(max(crop_size[0], crop_size[1]) * x * 0.1) for x in range(5, 21)], + resize_type='ResizeShortestEdge', + max_size=max(crop_size[0], crop_size[1])*4), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') + ] + train_dataloader.update(dict(dataset=dict(pipeline=train_pipeline))) + + # 3.5. param_scheduler部分 + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 3.生成各个部分 ########### + # 综合model + model = dict( + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head + ) + + ########### 4.程序写入 ########### + # 算法名称解析:mask2former【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + if selected_model_info['type'] == 'ResNet': + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + elif selected_model_info['type'] == 'swin': + alg_file_name = f"{alg_name}_swin_{selected_model_info['size']}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/mask2former/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, train_dataloader=train_dataloader, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_maskformer.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_maskformer.py new file mode 100644 index 0000000..cb09e8c --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_maskformer.py @@ -0,0 +1,246 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler +from Initial_Alg_Program.Initial_Alg_Gen_5_train_dataloader import generate_train_dataloader + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'maskformer_my' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) # _base_无算法 + + crop_size = select_crop_size(predefined_options=[(512, 512), (640, 640), (512, 1024)]) # 选择切割大小 + + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + # 3.1. 选择backbone + model_list = ['torchvision://resnet50', 'torchvision://resnet101', 'pretrain/swin_large-6580f57d.pth', 'pretrain/swin_base-e5c09f74.pth', 'pretrain/swin_small-7ba6d6dd.pth', 'pretrain/swin_tiny-1cdeb081.pth'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + + if selected_model_info['type'] == 'ResNet': + depth = selected_model_info['depth'] + backbone = create_dict_by_kwargs(depth = depth, type=selected_model_info['type'], norm_cfg=norm_cfg, num_stages=4, frozen_stages=-1, style='pytorch', init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + in_channels = [256, 512, 1024, 2048] + elif selected_model_info['type'] == 'swin': + if selected_model_name == 'pretrain/swin_tiny-1cdeb081.pth': + in_channels = [96, 192, 384, 768] + depths=[2, 2, 6, 2] + backbone = dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=224, + embed_dims=96, + depths=depths, + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + act_cfg=dict(type='GELU'), # ADD + use_abs_pos_embed=False, # ADD + # out_indices=(0, 1, 2, 3), + with_cp=False, + init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + if selected_model_name == 'pretrain/swin_small-7ba6d6dd.pth': + depths=[2, 2, 18, 2] + in_channels = [96, 192, 384, 768] + backbone = dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=224, + embed_dims=96, + depths=depths, + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + act_cfg=dict(type='GELU'), # ADD + use_abs_pos_embed=False, # ADD + # out_indices=(0, 1, 2, 3), + with_cp=False, + init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + if selected_model_name == 'pretrain/swin_base-e5c09f74.pth': + depths=[2, 2, 18, 2] + in_channels = [128, 256, 512, 1024] + backbone = dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=128, + depths=depths, + num_heads=[4, 8, 16, 32], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + act_cfg=dict(type='GELU'), # ADD + use_abs_pos_embed=False, # ADD + # out_indices=(0, 1, 2, 3), + with_cp=False, + init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + if selected_model_name == 'pretrain/swin_large-6580f57d.pth': + in_channels = [192, 384, 768, 1536] + depths=[2, 2, 18, 2] + backbone = dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=192, + depths=depths, + num_heads=[6, 12, 24, 48], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + act_cfg=dict(type='GELU'), # ADD + use_abs_pos_embed=False, # ADD + # out_indices=(0, 1, 2, 3), + with_cp=False, + init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + + # 3.2. decode_head + decode_head = dict(num_classes=num_classes, in_channels=in_channels, + loss_cls=dict(class_weight=[1.0] * num_classes + [0.1])) + + # 3.3. optimizer部分 + if selected_model_info['type'] == 'ResNet': + # optimizer + embed_multi = dict(lr_mult=1.0, decay_mult=0.0) + optimizer = dict( + type='AdamW', lr=0.0001, weight_decay=0.05, eps=1e-8, betas=(0.9, 0.999)) + optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi, + }, + norm_decay_mult=0.0)) + train_dataloader, batch_size, num_workers = generate_train_dataloader(batch_size_default=4, num_workers_default=4) + elif selected_model_info['type'] == 'swin': + # set all layers in backbone to lr_mult=0.1 + # set all norm layers, position_embeding, + # query_embeding, level_embeding to decay_multi=0.0 + backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) + backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) + embed_multi = dict(lr_mult=1.0, decay_mult=0.0) + custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi + } + custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) + }) + custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) + }) + # optimizer + optimizer = dict( + type='AdamW', lr=0.0001, weight_decay=0.05, eps=1e-8, betas=(0.9, 0.999)) + optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg={'custom_keys':custom_keys, 'norm_decay_mult':0.0}) + + # 更新train_dataloader + train_dataloader, batch_size, num_workers = generate_train_dataloader(batch_size_default=2, num_workers_default=2) + + # 3.5. param_scheduler部分 + param_scheduler = generate_param_scheduler(train_time_or_epoch_k) + + ########### 3.生成各个部分 ########### + # 综合model + model = dict( + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head + ) + + ########### 4.程序写入 ########### + # 算法名称解析:maskformer【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + if selected_model_info['type'] == 'ResNet': + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + elif selected_model_info['type'] == 'swin': + alg_file_name = f"{alg_name}_swin_{selected_model_info['size']}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/maskformer/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, train_dataloader=train_dataloader, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_pidnet.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_pidnet.py new file mode 100644 index 0000000..c35194f --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_pidnet.py @@ -0,0 +1,152 @@ +import os, sys, argparse, json +import importlib.util +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +def get_train_pipeline_from_config(dataset_file_name: str): + """ + 动态加载指定的配置文件并读取其中的 'train_pipeline' 列表。 + + Args: + dataset_file_name (str): 数据集配置文件的名字 (不包含.py后缀)。 + + Returns: + list | None: 如果成功找到,则返回 train_pipeline 列表; + 如果文件不存在或文件中没有 train_pipeline 变量,则返回 None。 + """ + # 1. 构建配置文件的相对路径 + # 使用 os.path.join 来确保路径在不同操作系统上都是正确的 + config_path = os.path.join('configs/_base_/datasets/', f'{dataset_file_name}.py') + + # 2. 检查文件是否存在 + if not os.path.exists(config_path): + print(f"\033[30错误:配置文件不存在于 '{config_path}'\033[0m") + return None + + try: + # 3. 动态加载 .py 文件作为一个模块 + # 创建一个模块规范 (spec) + # 模块名可以是任意的,这里用文件名以防冲突 + spec = importlib.util.spec_from_file_location(dataset_file_name, config_path) + + # 根据规范创建一个模块对象 + config_module = importlib.util.module_from_spec(spec) + + # 执行模块代码,使其所有变量(如 train_pipeline)都加载到模块对象中 + spec.loader.exec_module(config_module) + + # 4. 从加载的模块中获取 train_pipeline 变量 + # 使用 getattr 来安全地获取,如果不存在,可以设置一个默认值 + train_pipeline = getattr(config_module, 'train_pipeline', None) + + if train_pipeline is None: + print(f"错误:在 '{config_path}' 文件中未找到 'train_pipeline' 变量。") + return None + + return train_pipeline + + except Exception as e: + print(f"读取配置文件时发生未知错误: {e}") + return None + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'pidnet' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.1. 生成train_pipeline部分 ########### + config_path = os.path.join('../_base_/datasets/', f'{dataset_file_name}.py') + + train_pipeline = get_train_pipeline_from_config(dataset_file_name) + train_pipeline.insert(-1, dict(type='GenerateEdge', edge_width=4)) # For pidnet + train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(512, 512)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + # 3.3.1. 预处理data_preprocessor + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + + # # 3.3.2. 骨架backbone、解码器decode_head + model_list = ['openmmlab/pidnet-s', 'openmmlab/pidnet-m', 'openmmlab/pidnet-l'] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + if selected_model_name == 'openmmlab/pidnet-s': + backbone = dict(channels=32, ppm_channels=96, num_stem_blocks=2, num_branch_blocks=3, init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + decode_head = dict(num_classes=num_classes, in_channels=128, channels=128) + model_size = 'small' + elif selected_model_name == 'openmmlab/pidnet-m': + backbone = dict(channels=64, ppm_channels=96, num_stem_blocks=2, num_branch_blocks=3, init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + decode_head = dict(num_classes=num_classes, in_channels=256, channels=128) + model_size = 'middle' + elif selected_model_name == 'openmmlab/pidnet-l': + backbone = dict(channels=64, ppm_channels=112, num_stem_blocks=3, num_branch_blocks=4, init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + decode_head = dict(num_classes=num_classes, in_channels=256, channels=256) + model_size = 'large' + else: + quit("Error: 未知的模型名称") + + # 3.3.4. 综合model + model = dict( + data_preprocessor = model_data_preprocessor, + backbone = backbone, + decode_head = decode_head, + ) + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 4.1. 算法名称解析:bisenetv2【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_{model_size}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/pidnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, train_pipeline = train_pipeline, train_dataloader = train_dataloader, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 4.2. 将信息临时写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_pspnet.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_pspnet.py new file mode 100644 index 0000000..9169ff7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_pspnet.py @@ -0,0 +1,111 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'pspnet_r50-d8' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769),(1280,1280)]) # 选择切割大小 + model_list = ['openmmlab/resnet18_v1c', 'openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c', 'torchvision://resnet18', 'torchvision://resnet50', 'torchvision://resnet101', ] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + depth = selected_model_info['depth'] + type_model = selected_model_info['type'] + + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size) + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + # decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 # TODO + # auxiliary_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 # TODO + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + + backbone = create_dict_by_kwargs(depth=depth, type=type_model) # generate_model_backbone(depth=depth,) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + if depth == 18: + decode_head = create_dict_by_kwargs(in_channels=512, channels=128, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, norm_cfg=norm_cfg,) + elif depth == 101 or depth == 50: + decode_head = create_dict_by_kwargs(in_channels=2048, channels=512, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, norm_cfg=norm_cfg,) + + if depth == 18: + auxiliary_head = create_dict_by_kwargs(in_channels=256, channels=64, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, norm_cfg=norm_cfg,) + elif depth == 101 or depth == 50: + auxiliary_head = create_dict_by_kwargs(in_channels=1024, channels=256, num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, norm_cfg=norm_cfg,) + + # 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + ) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:pspnet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_r{depth}_{'Pre' if select_pretrained else 'NoPre'}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/pspnet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, test_cfg=test_cfg, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_stdc.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_stdc.py new file mode 100644 index 0000000..58e85c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_stdc.py @@ -0,0 +1,139 @@ +import os, sys, argparse, json +import importlib.util +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs, get_var_from_file, update_list_dict_var, get_var_from_py_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +def get_pretrained_model_choice(): + """ + 提示用户选择是否使用预训练模型,并返回他们的选择。 + + 返回: + str: 如果用户选择 '是' 或默认,则返回 '是'。 + 如果用户选择 '否',则返回 '否'。 + """ + while True: + # 如果用户直接按回车,input()返回空字符串,or "1" 使其默认值为 "1" + choice = input("可用的预训练模型选项:\n1. 是\n2. 否\n请选择是否使用预训练模型(默认1): ") or "1" + + if choice == "1": + print("您已选择:1. 是") + return True + elif choice == "2": + print("您已选择:2. 否") + return False + else: + # 如果输入了其他无效内容(如"3"),则提示重新输入 + print("\n无效输入,请输入 1 或 2。\n") + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'stdc' + alg_file_pth = f"configs/_base_/models/{alg_file_name}.py" + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + ########### 3.1. 生成_base_部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + ########### 3.1. 生成norm_cfg部分 ########### + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + ########### 3.2. 生成data_preprocessor部分 ########### + crop_size = select_crop_size(predefined_options=[(512, 512)]) # 选择切割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + ########### 3.3. 生成model部分 ########### + # 3.3.1. 预处理data_preprocessor + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + + # # 3.3.2. 骨架backbone、解码器decode_head + decode_head_loss_decode=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # Way Ori + decode_head_loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # Way 1 + + # auxiliary_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # Way Ori: DiceLoss损失函数 # TODO + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # Way 1: DiceLoss损失函数 # TODO + + auxiliary_head = get_var_from_py_file(alg_file_pth, 'model')['auxiliary_head'] + for i in range(len(auxiliary_head)): + if auxiliary_head[i]['type'] == 'FCNHead': + auxiliary_head[i].update(num_classes=num_classes, loss_decode=auxiliary_head_loss_decode_dict) + + model_list = ["openmmlab/stdc1", "openmmlab/stdc2"] + selected_model_name, select_pretrained, pretrained_pth, selected_model_info = select_pretrained_model(model_list=model_list) + use_pretrained = get_pretrained_model_choice() + if selected_model_name == "openmmlab/stdc1": + if use_pretrained: + backbone = dict(backbone_cfg=dict(stdc_type='STDCNet1'), init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + Pre = "Pre" + else: + backbone = dict(backbone_cfg=dict(stdc_type='STDCNet1')) + Pre = "NoPre" + decode_head = dict(num_classes=num_classes, loss_decode=decode_head_loss_decode) + model_size = 'V1_'+Pre + elif selected_model_name == "openmmlab/stdc2": + if use_pretrained: + backbone = dict(backbone_cfg=dict(stdc_type='STDCNet2'), init_cfg=dict(type='Pretrained', checkpoint=pretrained_pth)) + Pre = "Pre" + else: + backbone = dict(backbone_cfg=dict(stdc_type='STDCNet2')) + Pre = "NoPre" + decode_head = dict(num_classes=num_classes, loss_decode=decode_head_loss_decode) + model_size = 'V2_'+Pre + else: + quit("Error: 未知的模型名称") + + # 3.3.4. 综合model + model = dict( + data_preprocessor = model_data_preprocessor, + backbone = backbone, + decode_head = decode_head, + ) + + ########### 3.4. 生成optim、param_scheduler部分 ########### + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 4.1. 算法名称解析:bisenetv2【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_{model_size}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/stdc/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 4.2. 将信息临时写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_unet_deeplabv3.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_unet_deeplabv3.py new file mode 100644 index 0000000..2f49447 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Alg_Program/my_unet_deeplabv3.py @@ -0,0 +1,105 @@ +import os, sys, argparse, json +# 添加上级目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) + +from Initial_Alg_Program.Initial_Alg_Select_Tool import select_crop_size, select_pretrained_model, select_test_cfg_slide, select_sampler + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, create_dict_by_kwargs +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg +from Initial_Alg_Program.Initial_Alg_Gen_4_optimizer import generate_optim_wrapper, generate_param_scheduler + +if __name__ == '__main__': + ########### 1.超参数-算法默认参数 ########### + alg_file_name = 'deeplabv3_unet_s5-d16' + + ########### 2.参数解析 ########### + parser = argparse.ArgumentParser(description="Run algorithm with specified parameters.") + + # 添加命令行参数 + parser.add_argument('--alg_name', type=str, required=True, help="Algorithm name (e.g., 'ann')") + parser.add_argument('--dataset_file_name', type=str, required=True, help="Dataset file name (e.g., 'my_dataset_model')") + parser.add_argument('--mean', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--std', type=str, required=True, help="Mean of dataset (e.g., [1.2, 1.2, 1.2])") + parser.add_argument('--dataset_num_classes', type=int, required=True, help="Dataset class num (e.g., 36)") + parser.add_argument('--GPU_num', type=int, required=True, help="Num of GPU to train (e.g., 1)") + parser.add_argument('--schedule_file_name', type=str, required=True, help="Schedule file name (e.g., 'schedule_4k_check_400')") + parser.add_argument('--train_type', type=str, required=True, help="Schedule train type (e.g., Epoch or Iteration)") + parser.add_argument('--train_time_or_epoch_k', type=int, required=True, help="Schedule train time k (e.g., 4)") + + # 解析命令行参数 + args = parser.parse_args() + + # 变量赋值 + alg_name = args.alg_name + dataset_file_name = args.dataset_file_name + mean = json.loads(args.mean) # 使用json.loads转换成list + std = json.loads(args.std) # 使用json.loads转换成list + num_classes = args.dataset_num_classes + schedule_file_name = args.schedule_file_name + train_type = args.train_type + train_time_or_epoch_k = args.train_time_or_epoch_k + GPU_num = args.GPU_num + + + ########### 3. 设定参数 ########### + # 3.1. 自选参数 + crop_size = select_crop_size(predefined_options=[(128, 128), (256, 256)]) # 选择切割大小 + test_cfg_mode, test_cfg_crop_div_stride = select_test_cfg_slide(crop_size, select_slide=True) # 默认选择滑动模式 + + # 3.2. 自动生成参数 + # decode、auxiliary损失下载方式 + decode_head_loss_decode_dict=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0) # Way Ori: decode损失函数 # TODO + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # Way 1: decode损失函数 # TODO + decode_head_loss_decode_dict = [ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0)] + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # Way 1: DiceLoss损失函数 # TODO + + # 开启align_corners + if test_cfg_mode == "slide": + align_corners = True + test_slide = 'testslide' # 算法名进行标记 + + + ########### 3.生成各个部分 ########### + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + data_preprocessor = generate_data_preprocessor(crop_size = crop_size, mean = mean, std = std) + + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + # TODO TODO decode_head_loss_decode_dict + decode_head = create_dict_by_kwargs(num_classes=num_classes, loss_decode=decode_head_loss_decode_dict, align_corners=align_corners, norm_cfg=norm_cfg,) + + # auxiliary_head_loss_decode=dict(type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4) + auxiliary_head = create_dict_by_kwargs(num_classes=num_classes, norm_cfg=norm_cfg, loss_decode=auxiliary_head_loss_decode_dict) + + test_cfg = generate_model_test_cfg(test_cfg_mode=test_cfg_mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + + # 综合model + model = dict( + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + test_cfg = test_cfg, + auxiliary_head = auxiliary_head, + ) + + + optim_wrapper = generate_optim_wrapper() + param_scheduler = generate_param_scheduler(train_type, train_time_or_epoch_k) + + ########### 4.程序写入 ########### + # 算法名称解析:unet【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py + alg_file_name = f"{alg_name}_g{GPU_num}-{schedule_file_name.lstrip('schedule_')}_{dataset_file_name}-{crop_size[0]}x{crop_size[1]}-{test_slide}.py" + output_configs_alg_my_alg = os.path.join(f'./configs/unet/', alg_file_name) + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, norm_cfg=norm_cfg, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model, optim_wrapper=optim_wrapper, param_scheduler=param_scheduler) + + # 将信息写入文件 + with open("_temp_.txt", "w") as file: + json.dump({'alg_infos':{'alg_file_name':alg_file_name,'alg_file_pth':output_configs_alg_my_alg}}, file) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All-ori-old.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All-ori-old.py new file mode 100644 index 0000000..256fe44 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All-ori-old.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import os +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg + +if __name__ == '__main__': + ########### 1.1.定义算法基本参数 ########### + # A. generate_base_config + alg_name = 'ann' # ./configs中算法简称 + alg_file_name = 'ann_r50-d8' # 算法根文件 + dataset_file_name = 'my_dataset_model' # 数据文件 + schedule_file_name = 'schedule_4k_check_400' # schedule文件 + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + + # B. generate_norm_cfg + GPU_num = 1 # GPU数量 + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + + # C. generate_data_preprocessor + crop_size = (512,512) # 分割大小 + data_preprocessor = generate_data_preprocessor(crop_size = crop_size) + + # D. generate_model + # D.1. pretrained + pretrained_pth = './My_Local_Model/open_mmlab/resnet50_v1c.pth' # 预训练模型位置 + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + + # D.2. backbone + depth = 50 # 模型深度 + backbone = generate_model_backbone(depth=depth) + + # D.3. data_preprocessor + model_data_preprocessor = data_preprocessor # 修改data_preprocessor + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + # D.4. decode_head、auxiliary_head + num_classes=36 # 分类数 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # decode损失函数 + align_corners=False # 是否需要角对齐 + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # DiceLoss损失函数 + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + + # D.5. train_cfg + # train_cfg = generate_model_train_cfg() + + # D.6. test_cfg + # test_cfg = generate_model_test_cfg() + + # E. 综合model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + # train_cfg = train_cfg, + # test_cfg = test_cfg, + ) + + ########### 2.文件存储 ########### + # output_configs_alg_my_alg = os.path.join(f'my_{alg_name}.py') + output_configs_alg_my_alg = os.path.join(f'./configs/{alg_name}/', f'my_{alg_name}.py') + + write_config_to_file(output_configs_alg_my_alg, _base_=_base_, crop_size=crop_size, data_preprocessor=data_preprocessor, model=model) + diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V1.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V1.py new file mode 100644 index 0000000..069d1b9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V1.py @@ -0,0 +1,301 @@ +import os, json, subprocess +from Initial_Schedule_Program.Initial_Train_Gen_configs_base_schedules_schedule_XXk import generate_times_configs_base_schedules_schedule_file +from Initial_Schedule_Program.Initial_Train_Gen_configs_base_schedules_schedule_XXe import generate_epochs_configs_base_schedules_schedule_file +from datetime import datetime + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, get_gpu_info +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg + +def load_json_file(file_name): + """ + 读取并返回指定 JSON 文件的内容。 + + :param file_name: 要读取的 JSON 文件路径 + :return: JSON 文件的内容作为字典或列表 + """ + try: + # 打开并读取 JSON 文件 + with open(file_name, 'r', encoding='utf-8') as f: + data = json.load(f) + return data + except FileNotFoundError: + print(f"\033[91mError: File {file_name} not found.\033[0m") + return None + except json.JSONDecodeError: + print(f"\033[91mError: Failed to decode JSON from {file_name}.\033[0m") + return None + except Exception as e: + print(f"\033[91mAn error occurred while reading {file_name}: {str(e)}\033[0m") + return None + +def process_data_record_json_data(json_data): + """ + 将 JSON 数据直接存入一个大的字典 all_data_record。 + :param json_data: 从 JSON 文件加载的数据 + :return: all_data_record 字典 + """ + all_data_record = {} + + # 遍历 json_data 中的每个键(即每个数据集) + for dataset_name, dataset_info in json_data.items(): + all_data_record[dataset_name] = { + "classes": dataset_info['classes'], + "palette": dataset_info['palette'], + "palette_num": dataset_info['palette_num'], + "mean": dataset_info['mean'], + "std": dataset_info['std'], + } + + return all_data_record + +# 选择要处理的数据集 +def select_dataset(all_data_record): + """ + 让用户从 all_data_record 中选择数据集,并返回 dataset_file_name 和对应的 palette_num。 + + :param all_data_record: 包含所有数据集信息的字典 + :return: 选定的 dataset_file_name 和 palette_num + """ + # 获取所有数据集的名称 + dataset_names = list(all_data_record.keys()) + + # 显示可用的数据集,并让用户选择 + print("选择可用数据集:") + for i, name in enumerate(dataset_names): + print(f"{i + 1}. {name} - {all_data_record[name]['palette_num']}类") + + # 用户输入选择的数据集编号 + while True: + try: + selection = int(input("请选择数据集编号(输入数字):")) - 1 + if 0 <= selection < len(dataset_names): + dataset_file_name = dataset_names[selection] + break + else: + print(f"输入的编号无效,请输入 1 到 {len(dataset_names)} 之间的数字。") + except ValueError: + print("无效输入,请输入数字。") + + # 获取对应的 palette_num + palette_num = all_data_record[dataset_file_name]['palette_num'] + mean = all_data_record[dataset_file_name]['mean'] + std = all_data_record[dataset_file_name]['std'] + + print(f" 已选择数据集: {dataset_file_name} ,其对应的分类数为: {palette_num}") + + return dataset_file_name, palette_num, mean, std + +# 选择对应的算法 +def select_alg(alg_directory): + """ + 选择一个算法文件并运行它。 + + :return: 选定的算法文件名和相对路径 + """ + # 获取 ./Alg 目录下的所有 Python 文件 + algorithms = [f for f in os.listdir(alg_directory) if f.endswith('.py')] + + # 显示可用的算法文件 + print("选择可用算法:") + for i, alg in enumerate(algorithms): + print(f"{i + 1}. {alg}") + + # 用户选择算法 + while True: + try: + selection = int(input("请选择算法编号(输入数字):")) - 1 + if 0 <= selection < len(algorithms): + selected_alg = algorithms[selection] + break + else: + print(f"输入的编号无效,请输入 1 到 {len(algorithms)} 之间的数字。") + except ValueError: + print("无效输入,请输入数字。") + + # 生成选定算法的相对路径 + relative_alg_path = os.path.join(alg_directory, selected_alg) + print(f" 已选择算法: {selected_alg} {relative_alg_path}") + + return selected_alg, relative_alg_path + +# 选择计算用的GPU +def select_GPU(): + """ + 让用户选择要使用的 GPU 数量,并返回相应的 GPU 列表(以 0,1 格式输出)。 + 如果用户没有输入任何选择,默认为选择一块 GPU,编号为 0。 + :return: 用户选择的 GPU 列表和数量 + """ + # 获取 GPU 信息 + gpu_info = get_gpu_info() + + if not gpu_info: + return None, 0 + + # 显示 GPU 信息 + print("可用的 GPU 列表:") + for idx, mem_free in gpu_info: + print(f"GPU {idx}: 剩余显存 {mem_free} MB") + + # 提供默认选择 GPU 个数和编号 + default_num_gpus = 1 + default_gpu_idx = 0 + + # 用户选择 GPU 个数(默认选择 1) + try: + num_gpus = input(f"请选择使用的 GPU 个数 (1-{len(gpu_info)}, 默认为 1): ") + num_gpus = int(num_gpus) if num_gpus else default_num_gpus + if not (1 <= num_gpus <= len(gpu_info)): + print(f"输入无效,使用默认值 1 个 GPU。") + num_gpus = default_num_gpus + except ValueError: + print(f"无效输入,使用默认值 1 个 GPU。") + num_gpus = default_num_gpus + + # 用户选择 GPU 编号(如果选择多个 GPU,逐个选择编号) + selected_gpus = [] + for i in range(num_gpus): + try: + gpu_idx = input(f"请输入要使用的第 {i + 1} 个 GPU 的编号 (默认为 {default_gpu_idx}): ") + gpu_idx = int(gpu_idx) if gpu_idx else default_gpu_idx + if gpu_idx in [idx for idx, _ in gpu_info] and gpu_idx not in selected_gpus: + selected_gpus.append(gpu_idx) + else: + print(f"无效输入,使用默认 GPU {default_gpu_idx}") + selected_gpus.append(default_gpu_idx) + except ValueError: + print(f"无效输入,使用默认 GPU {default_gpu_idx}") + selected_gpus.append(default_gpu_idx) + + # 返回以 0,1 格式的 GPU 列表和 GPU 数量 + gpu_list_str = ','.join(map(str, selected_gpus)) + print(f"\n已选择 GPU: {gpu_list_str},共 {num_gpus} 块 GPU") + + return gpu_list_str, num_gpus + +# 选择训练批次相关信息 +def select_schedule(): + """ + 选择训练时间、验证次数、日志间隔,并生成训练计划配置文件。 + 提供默认值:train_time_k=40, check_num=10, loggerhook_interval=50 + """ + # 交互式输入 train_time_k,默认值 40 + try: + train_time_k = input("请输入训练次数(k为单位,默认40k):").strip() + train_time_k = int(train_time_k) if train_time_k else 40 + except ValueError: + print("无效输入,使用默认训练时间 40k") + train_time_k = 40 + + # 交互式输入 check_num,默认值 10 + try: + check_num = input("请输入检查点数量(默认10):").strip() + check_num = int(check_num) if check_num else 10 + except ValueError: + print("无效输入,使用默认检查点数量 20") + check_num = 20 + + # 交互式输入 loggerhook_interval,默认值 50 + try: + loggerhook_interval = input("请输入日志间隔(默认50次迭代):").strip() + loggerhook_interval = int(loggerhook_interval) if loggerhook_interval else 50 + except ValueError: + print("无效输入,使用默认日志间隔 50 次迭代") + loggerhook_interval = 50 + + # 计算验证比例和检查点间隔 + val_proportion = 1 / check_num # 验证比例 + checkpoint_interval = int(train_time_k * 1000 * val_proportion) # 计算保存检查点的间隔 + + # 生成文件名和路径 + output_configs_base_schedules_schedules_Timek = os.path.join('./configs/_base_/schedules/', f'schedule_{train_time_k}k_check_{checkpoint_interval}.py') + schedule_file_name = f'schedule_{train_time_k}k_check_{checkpoint_interval}.py' + + # 调用生成配置文件函数 + generate_times_configs_base_schedules_schedule_file( + output_file=output_configs_base_schedules_schedules_Timek, + train_time_k=train_time_k, + val_proportion=val_proportion, + loggerhook_interval=loggerhook_interval + ) + + return schedule_file_name, train_time_k + +# 运行对应的代码去生成算法配置文件 +def run_alg_to_gen_alg(relative_alg_path, mean, std, alg_name, dataset_file_name, num_classes, GPU_num, schedule_file_name, train_time_k): + try: + # 构建命令和参数 + cmd = [ + 'python', relative_alg_path, + '--alg_name', alg_name, + '--dataset_file_name', dataset_file_name, + '--mean', str(mean), + '--std', str(std), + '--dataset_num_classes', str(num_classes), + '--GPU_num', str(GPU_num), + '--schedule_file_name', schedule_file_name, + '--train_time_k', str(train_time_k) + ] + + # 打印当前执行的命令 + print(f"\n正在运行: \033[33m{' '.join(cmd)}\033[0m") + + # 运行 Python 脚本并传递参数 + subprocess.run(cmd, check=True) + + print(f"\033[92m算法生成器 {relative_alg_path} 运行成功。\033[0m") + except subprocess.CalledProcessError as e: + print(f"\033[91m算法生成器 {relative_alg_path} 运行失败,错误信息: {e}\033[0m") + +if __name__ == '__main__': + ########### 1.超参数-定义数据集、算法信息路径 ########### + train_parameter_dir = './My_All_In_One/1_Data_Parameter' + all_data_record_json = "All_Data_Record.json" + all_data_record_file = os.path.join(train_parameter_dir, all_data_record_json) # 数据集信息记录路径 + + alg_directory = './My_All_In_One/2_Alg_Program' # 算法配置生成路径 + + work_dir_base = './work_dirs' # 工作路径 + + ########### 2.获取现有数据集信息 ########### + print(f"\033[36m{'='*10} 一、选择训练数据集 {'='*10}\033[0m") + data_record_json_data = load_json_file(file_name = all_data_record_file) # 加载数据集信息 + all_data_record = process_data_record_json_data(json_data = data_record_json_data) # 分析数据集信息 + dataset_file_name, num_classes, mean, std = select_dataset(all_data_record = all_data_record) # 选择特定数据集 + + ########### 3.获取现有GPU信息 ########### + print(f"\033[36m{'='*10} 二、选择训练GPU {'='*10}\033[0m") + gpu_list_str, GPU_num = select_GPU() # 选择GPU + + ########### 4.获取训练批次信息 ########### + print(f"\033[36m{'='*10} 三、选择训练批次信息 {'='*10}\033[0m") + schedule_file_name, train_time_k = select_schedule() # 选择训练批次 + schedule_file_name = schedule_file_name.rstrip('.py') + + ########### 5.获取现有算法信息 ########### + print(f"\033[36m{'='*10} 四、选择训练算法 {'='*10}\033[0m") + selected_alg, relative_alg_path = select_alg(alg_directory=alg_directory) # 选择算法 + alg_name = selected_alg.rstrip(".py") + + ########### 6.运行选定的算法 ########### + print(f"\033[36m{'='*5} 生成训练算法配置 {'='*5}\033[0m") + run_alg_to_gen_alg(relative_alg_path=relative_alg_path, alg_name=alg_name, dataset_file_name=dataset_file_name, num_classes=num_classes, GPU_num=GPU_num, mean=mean, std=std,schedule_file_name=schedule_file_name, train_time_k=train_time_k) + + # 算法相关信息 + with open("_temp_.txt", "r") as file: + data = json.load(file) + alg_file_name = data['alg_infos']['alg_file_name'] + alg_file_pth = data['alg_infos']['alg_file_pth'] + os.remove("_temp_.txt") + + ########### 7.输出工作、算法目录 ########### + # 获取并打印当前的年月日时分秒 + data_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + work_dir_file = os.path.join(work_dir_base, f"{dataset_file_name}-Class_{num_classes}-Alg_{alg_name}-AlgName_{alg_file_name}-Card_{GPU_num}-Data_{data_now}") + + print(f"\033[36m训练指令:python tools/train.py {alg_file_pth} --work-dir {work_dir_file}") + + + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V2.py b/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V2.py new file mode 100644 index 0000000..df9523c --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V2.py @@ -0,0 +1,357 @@ +import os, json, subprocess +from Initial_Schedule_Program.Initial_Train_Gen_configs_base_schedules_schedule_XXk import generate_times_configs_base_schedules_schedule_file +from Initial_Schedule_Program.Initial_Train_Gen_configs_base_schedules_schedule_XXe import generate_epochs_configs_base_schedules_schedule_file +from datetime import datetime + +from Initial_Alg_Program.Initial_Alg_Gen_Tool import format_all_data, write_config_to_file, get_gpu_info +from Initial_Alg_Program.Initial_Alg_Gen_0_base_ import generate_base_config +from Initial_Alg_Program.Initial_Alg_Gen_1_norm_cfg import generate_norm_cfg +from Initial_Alg_Program.Initial_Alg_Gen_2_data_preprocessor import generate_data_preprocessor +from Initial_Alg_Program.Initial_Alg_Gen_3_model import generate_model_pretrained, generate_model_backbone, generate_model_data_preprocessor, generate_model_decode_head, generate_model_auxiliary_head, generate_model_train_cfg, generate_model_test_cfg + +def load_json_file(file_name): + """ + 读取并返回指定 JSON 文件的内容。 + + :param file_name: 要读取的 JSON 文件路径 + :return: JSON 文件的内容作为字典或列表 + """ + try: + # 打开并读取 JSON 文件 + with open(file_name, 'r', encoding='utf-8') as f: + data = json.load(f) + return data + except FileNotFoundError: + print(f"\033[91mError: File {file_name} not found.\033[0m") + return None + except json.JSONDecodeError: + print(f"\033[91mError: Failed to decode JSON from {file_name}.\033[0m") + return None + except Exception as e: + print(f"\033[91mAn error occurred while reading {file_name}: {str(e)}\033[0m") + return None + +def process_data_record_json_data(json_data): + """ + 将 JSON 数据直接存入一个大的字典 all_data_record。 + :param json_data: 从 JSON 文件加载的数据 + :return: all_data_record 字典 + """ + all_data_record = {} + + # 遍历 json_data 中的每个键(即每个数据集) + for dataset_name, dataset_info in json_data.items(): + all_data_record[dataset_name] = { + "classes": dataset_info['classes'], + "palette": dataset_info['palette'], + "palette_num": dataset_info['palette_num'], + "mean": dataset_info['mean'], + "std": dataset_info['std'], + "train_imgs_num": dataset_info['train_imgs_num'] + } + + return all_data_record + +# 选择要处理的数据集 +def select_dataset(all_data_record): + """ + 让用户从 all_data_record 中选择数据集,并返回 dataset_file_name 和对应的 palette_num。 + + :param all_data_record: 包含所有数据集信息的字典 + :return: 选定的 dataset_file_name 和 palette_num + """ + # 获取所有数据集的名称 + dataset_names = list(all_data_record.keys()) + + # 显示可用的数据集,并让用户选择 + print("选择可用数据集:") + for i, name in enumerate(dataset_names): + print(f"{i + 1}. {name} - {all_data_record[name]['palette_num']}类") + + # 用户输入选择的数据集编号 + while True: + try: + selection = int(input("请选择数据集编号(输入数字):")) - 1 + if 0 <= selection < len(dataset_names): + dataset_file_name = dataset_names[selection] + break + else: + print(f"输入的编号无效,请输入 1 到 {len(dataset_names)} 之间的数字。") + except ValueError: + print("无效输入,请输入数字。") + + # 获取对应的 palette_num + palette_num = all_data_record[dataset_file_name]['palette_num'] + mean = all_data_record[dataset_file_name]['mean'] + std = all_data_record[dataset_file_name]['std'] + train_imgs_num = all_data_record[dataset_file_name]['train_imgs_num'] + + print(f" 已选择数据集: {dataset_file_name} ,其对应的分类数为: {palette_num}") + + return dataset_file_name, palette_num, mean, std, train_imgs_num + +# 选择对应的算法 +def select_alg(alg_directory): + """ + 选择一个算法文件并运行它。 + + :return: 选定的算法文件名和相对路径 + """ + # 获取 ./Alg 目录下的所有 Python 文件 + algorithms = [f for f in os.listdir(alg_directory) if f.endswith('.py')] + + # 显示可用的算法文件 + print("选择可用算法:") + for i, alg in enumerate(algorithms): + print(f"{i + 1}. {alg}") + + # 用户选择算法 + while True: + try: + selection = int(input("请选择算法编号(输入数字):")) - 1 + if 0 <= selection < len(algorithms): + selected_alg = algorithms[selection] + break + else: + print(f"输入的编号无效,请输入 1 到 {len(algorithms)} 之间的数字。") + except ValueError: + print("无效输入,请输入数字。") + + # 生成选定算法的相对路径 + relative_alg_path = os.path.join(alg_directory, selected_alg) + print(f" 已选择算法: {selected_alg} {relative_alg_path}") + + return selected_alg, relative_alg_path + +# 选择计算用的GPU +def select_GPU(): + """ + 让用户选择要使用的 GPU 数量,并返回相应的 GPU 列表(以 0,1 格式输出)。 + 如果用户没有输入任何选择,默认为选择一块 GPU,编号为 0。 + :return: 用户选择的 GPU 列表和数量 + """ + # 获取 GPU 信息 + gpu_info = get_gpu_info() + + if not gpu_info: + return None, 0 + + # 显示 GPU 信息 + print("可用的 GPU 列表:") + for idx, mem_free in gpu_info: + print(f"GPU {idx}: 剩余显存 {mem_free} MB") + + # 提供默认选择 GPU 个数和编号 + default_num_gpus = 1 + default_gpu_idx = 0 + + # 用户选择 GPU 个数(默认选择 1) + try: + num_gpus = input(f"请选择使用的 GPU 个数 (1-{len(gpu_info)}, 默认为 1): ") + num_gpus = int(num_gpus) if num_gpus else default_num_gpus + if not (1 <= num_gpus <= len(gpu_info)): + print(f"输入无效,使用默认值 1 个 GPU。") + num_gpus = default_num_gpus + except ValueError: + print(f"无效输入,使用默认值 1 个 GPU。") + num_gpus = default_num_gpus + + # 用户选择 GPU 编号(如果选择多个 GPU,逐个选择编号) + selected_gpus = [] + for i in range(num_gpus): + try: + gpu_idx = input(f"请输入要使用的第 {i + 1} 个 GPU 的编号 (默认为 {default_gpu_idx}): ") + gpu_idx = int(gpu_idx) if gpu_idx else default_gpu_idx + if gpu_idx in [idx for idx, _ in gpu_info] and gpu_idx not in selected_gpus: + selected_gpus.append(gpu_idx) + else: + print(f"无效输入,使用默认 GPU {default_gpu_idx}") + selected_gpus.append(default_gpu_idx) + except ValueError: + print(f"无效输入,使用默认 GPU {default_gpu_idx}") + selected_gpus.append(default_gpu_idx) + + # 返回以 0,1 格式的 GPU 列表和 GPU 数量 + gpu_list_str = ','.join(map(str, selected_gpus)) + print(f"\n已选择 GPU: {gpu_list_str},共 {num_gpus} 块 GPU") + + return gpu_list_str, num_gpus + +# 选择训练批次相关信息 +def select_schedule(train_imgs_num): + """ + 让用户选择训练模式(Iteration 或 Epoch),并根据选择收集参数, + 最终生成对应的训练计划 schedule 配置文件。 + """ + # 1. 让用户选择模式 + while True: + mode = input("请选择训练模式 (1: Iteration, 2: Epoch) [默认: 2]: ").strip() + if mode in ['1', '2', '']: + mode = '2' if mode == '' else mode + break + else: + print("无效输入,请输入 1 或 2。") + + # --- 模式 1: 基于 Iteration 的配置 --- + if mode == '1': + print("\n--- 您已选择 Iteration 模式 ---") + try: + train_time_or_epoch_k = input("请输入训练次数 (k为单位, 默认40k): ").strip() + train_time_or_epoch_k = int(train_time_or_epoch_k) if train_time_or_epoch_k else 40 + except ValueError: + print("无效输入,使用默认训练时间 40k") + train_time_or_epoch_k = 40 + + try: + check_num = input("请输入总的验证/保存次数 (默认10): ").strip() + check_num = int(check_num) if check_num else 10 + except ValueError: + print("无效输入,使用默认次数 10") + check_num = 10 + + try: + loggerhook_interval = input("请输入日志间隔 (默认50次迭代): ").strip() + loggerhook_interval = int(loggerhook_interval) if loggerhook_interval else 50 + except ValueError: + print("无效输入,使用默认日志间隔 50") + loggerhook_interval = 50 + + val_proportion = 1 / check_num + # 根据验证比例计算间隔,确保至少为1 + interval = max(1, int(train_time_or_epoch_k * 1000 * val_proportion)) + + schedule_file_name = f'schedule_{train_time_or_epoch_k}k_check_{interval}.py' + output_path = os.path.join('./configs/_base_/schedules/', schedule_file_name) + + generate_times_configs_base_schedules_schedule_file( + output_file=output_path, + train_time_or_epoch_k=train_time_or_epoch_k, + val_proportion=val_proportion, + loggerhook_interval=loggerhook_interval + ) + # 返回文件名和训练时长 + return schedule_file_name, "Iteration", train_time_or_epoch_k + + # --- 模式 2: 基于 Epoch 的配置 --- + elif mode == '2': + print("\n--- 您已选择 Epoch 模式 ---") + try: + max_epochs = input("请输入训练总轮数 (Epochs, 默认300): ").strip() + max_epochs = int(max_epochs) if max_epochs else 300 + except ValueError: + print("无效输入,使用默认轮数 300") + max_epochs = 300 + + try: + val_interval = input("请输入验证间隔的轮次数 (Epochs, 默认1): ").strip() + val_interval = int(val_interval) if val_interval else 1 + except ValueError: + print("无效输入,使用默认验证间隔 1") + val_interval = 1 + + try: + checkpoint_interval = input("请输入模型保存间隔的轮次数 (Epochs, 默认10): ").strip() + checkpoint_interval = int(checkpoint_interval) if checkpoint_interval else 10 + except ValueError: + print("无效输入,使用默认保存间隔 10") + checkpoint_interval = 10 + + try: + loggerhook_interval_default = train_imgs_num // 16 + loggerhook_interval = input(f"请输入日志间隔 (默认{loggerhook_interval_default}次迭代): ").strip() + loggerhook_interval = int(loggerhook_interval) if loggerhook_interval else loggerhook_interval_default + except ValueError: + print(f"无效输入,使用默认日志间隔 {loggerhook_interval_default}") + loggerhook_interval = loggerhook_interval_default + + schedule_file_name = f'schedule_{max_epochs}e_val{val_interval}_check{checkpoint_interval}.py' + output_path = os.path.join('./configs/_base_/schedules/', schedule_file_name) + + # 注意:这里调用的是基于Epoch的生成函数 + generate_epochs_configs_base_schedules_schedule_file( + output_file=output_path, + max_epochs=max_epochs, + val_interval=val_interval, + checkpoint_interval=checkpoint_interval, + loggerhook_interval=loggerhook_interval + ) + # 返回文件名和训练时长 + return schedule_file_name, "Epoch", max_epochs + +# 运行对应的代码去生成算法配置文件 +def run_alg_to_gen_alg(relative_alg_path, mean, std, alg_name, dataset_file_name, num_classes, GPU_num, schedule_file_name, train_type, train_time_or_epoch_k): + try: + # 构建命令和参数 + cmd = [ + 'python', relative_alg_path, + '--alg_name', alg_name, + '--dataset_file_name', dataset_file_name, + '--mean', str(mean), + '--std', str(std), + '--dataset_num_classes', str(num_classes), + '--GPU_num', str(GPU_num), + '--schedule_file_name', schedule_file_name, + '--train_type', train_type, + '--train_time_or_epoch_k', str(train_time_or_epoch_k) + ] + + # 打印当前执行的命令 + print(f"\n正在运行: \033[33m{' '.join(cmd)}\033[0m") + + # 运行 Python 脚本并传递参数 + subprocess.run(cmd, check=True) + + print(f"\033[92m算法生成器 {relative_alg_path} 运行成功。\033[0m") + except subprocess.CalledProcessError as e: + print(f"\033[91m算法生成器 {relative_alg_path} 运行失败,错误信息: {e}\033[0m") + +if __name__ == '__main__': + ########### 1.超参数-定义数据集、算法信息路径 ########### + train_parameter_dir = './My_All_In_One/1_Data_Parameter' + all_data_record_json = "All_Data_Record.json" + all_data_record_file = os.path.join(train_parameter_dir, all_data_record_json) # 数据集信息记录路径 + + alg_directory = './My_All_In_One/2_Alg_Program' # 算法配置生成路径 + + work_dir_base = '../DataSet_Public_outputs' # 工作路径 + + ########### 2.获取现有数据集信息 ########### + print(f"\033[36m{'='*10} 一、选择训练数据集 {'='*10}\033[0m") + data_record_json_data = load_json_file(file_name = all_data_record_file) # 加载数据集信息 + all_data_record = process_data_record_json_data(json_data = data_record_json_data) # 分析数据集信息 + dataset_file_name, num_classes, mean, std, train_imgs_num = select_dataset(all_data_record = all_data_record) # 选择特定数据集 + + ########### 3.获取现有GPU信息 ########### + print(f"\033[36m{'='*10} 二、选择训练GPU {'='*10}\033[0m") + gpu_list_str, GPU_num = select_GPU() # 选择GPU + + ########### 4.获取训练批次信息 ########### + print(f"\033[36m{'='*10} 三、选择训练批次信息 {'='*10}\033[0m") + schedule_file_name, train_type, train_time_or_epoch_k = select_schedule(train_imgs_num) # 选择训练批次 + schedule_file_name = schedule_file_name.rstrip('.py') + + ########### 5.获取现有算法信息 ########### + print(f"\033[36m{'='*10} 四、选择训练算法 {'='*10}\033[0m") + selected_alg, relative_alg_path = select_alg(alg_directory=alg_directory) # 选择算法 + alg_name = selected_alg.rstrip(".py") + + ########### 6.运行选定的算法 ########### + print(f"\033[36m{'='*5} 生成训练算法配置 {'='*5}\033[0m") + run_alg_to_gen_alg(relative_alg_path=relative_alg_path, alg_name=alg_name, dataset_file_name=dataset_file_name, num_classes=num_classes, GPU_num=GPU_num, mean=mean, std=std,schedule_file_name=schedule_file_name, train_type = train_type, train_time_or_epoch_k=train_time_or_epoch_k) + + # 算法相关信息 + with open("_temp_.txt", "r") as file: + data = json.load(file) + alg_file_name = data['alg_infos']['alg_file_name'] + alg_file_pth = data['alg_infos']['alg_file_pth'] + os.remove("_temp_.txt") + + ########### 7.输出工作、算法目录 ########### + # 获取并打印当前的年月日时分秒 + data_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + work_dir_file = os.path.join(work_dir_base, f"{dataset_file_name}-Class_{num_classes}-Alg_{alg_name}-AlgName_{alg_file_name}-Card_{GPU_num}-Data_{data_now}") + + print(f"\033[36m训练指令:python tools/train.py {alg_file_pth} --work-dir {work_dir_file}") + + + \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/3_Find_And_Delete_Special_Epoch.py b/Seg_All_In_One_MMSeg/My_All_In_One/3_Find_And_Delete_Special_Epoch.py new file mode 100644 index 0000000..9c3bd05 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/3_Find_And_Delete_Special_Epoch.py @@ -0,0 +1,78 @@ +import os +import sys + +def find_specific_epochs(root_folder): + """ + 遍历指定文件夹,查找所有 epoch_XXX.pth 文件, + 其中 XXX 为整数且不能被 10 整除,并返回它们的绝对路径。 + + :param root_folder: 要搜索的根文件夹路径 + :return: 一个包含符合条件文件绝对路径的列表 + """ + matching_files = [] + + for dirpath, _, filenames in os.walk(root_folder): + for filename in filenames: + if filename.startswith('epoch_') and filename.endswith('.pth'): + try: + number_str = filename[len('epoch_'):-len('.pth')] + if number_str.isdigit(): + epoch_number = int(number_str) + if epoch_number % 10 != 0: + full_path = os.path.join(dirpath, filename) + matching_files.append(os.path.abspath(full_path)) + except (ValueError, IndexError): + continue + + return matching_files + +if __name__ == "__main__": + # 1. 获取当前脚本文件所在的绝对目录 + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # 2. 以脚本目录为基准,构建目标路径 + # V1 + target_directory = os.path.abspath(os.path.join(script_dir, '../../Hardisk')) + # V2 + # target_directory = os.path.abspath(os.path.join(script_dir, '../../DataSet_Public_outputs')) + + # 1. 查找符合条件的文件 + found_paths = find_specific_epochs(target_directory) + + if not found_paths: + print(f"在 '{os.path.abspath(target_directory)}' 及其子目录中没有找到符合条件的文件。") + sys.exit(0) + + # 2. 列出所有找到的文件,并请求用户确认 + print("找到了以下符合条件的文件:") + for path in found_paths: + print(path) + + print("\n" + "="*50) + print("警告:接下来的操作将永久删除以上列出的所有文件!") + print("="*50 + "\n") + + # 3. 获取用户输入 + try: + # 使用 strip() 去除首尾空格,使用 lower() 转换为小写 + confirm = input("您确定要删除这 {} 个文件吗? (请输入 'yes' 进行确认,输入其他任何内容则取消): ".format(len(found_paths))) + except KeyboardInterrupt: + print("\n\n操作被用户中断。") + sys.exit(1) + + # 4. 根据用户的输入执行操作 + if confirm.lower().strip() == 'yes': + print("\n正在开始删除文件...") + deleted_count = 0 + error_count = 0 + for path in found_paths: + try: + os.remove(path) + print(f"已删除: {path}") + deleted_count += 1 + except OSError as e: + print(f"删除失败: {path} (原因: {e})") + error_count += 1 + print(f"\n操作完成。成功删除 {deleted_count} 个文件,{error_count} 个文件删除失败。") + else: + print("\n操作已取消,没有文件被删除。") \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/3_Tool_Copy_Result_To_Hardisk.sh b/Seg_All_In_One_MMSeg/My_All_In_One/3_Tool_Copy_Result_To_Hardisk.sh new file mode 100644 index 0000000..8b0bb9b --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/3_Tool_Copy_Result_To_Hardisk.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# --- 脚本说明 --- +# 功能: 使用 rsync 将指定格式的文件夹从源目录同步到目标目录。 +# - 脚本可以从任何位置安全执行。 +# - 在同步前会检查源文件/目录是否存在,如果不存在则跳过该任务。 +# +# rsync 参数说明: +# -a: 归档模式,保留文件属性。 +# -v: 详细模式。 +# -h: 人性化显示大小。 +# --progress: 显示传输进度。 +# --stats: 显示任务统计。 + +# --- 路径配置 --- +# 获取脚本文件所在的绝对目录路径,确保路径的准确性。 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# 基于脚本所在目录设置源和目标的绝对路径 +SOURCE_DIR="$SCRIPT_DIR/../../DataSet_Public_outputs" +DEST_DIR="$SCRIPT_DIR/../../Hardisk" + +# --- 主程序 --- +echo "脚本执行目录已锁定为: $SCRIPT_DIR" +echo "解析后的源目录 (Source): $SOURCE_DIR" +echo "解析后的目标根目录 (Destination): $DEST_DIR" +echo "========================================" +echo "开始执行 rsync 同步任务(带安全检查)..." +echo "" + +# --- 任务处理函数 (简化代码) --- +# 定义一个函数来处理每个同步任务,避免代码重复 +# 参数1: 任务名称 (例如: 1_cholecseg8k) +# 参数2: 源文件/目录的通配符模式 +# 参数3: 目标目录 +handle_sync_task() { + local task_name="$1" + local src_pattern="$2" + local dest_dir="$3" + + echo "--> 正在检查任务: $task_name" + + # 将通配符匹配到的文件存入数组 + local sources=($src_pattern) + + # 检查数组的第一个元素是否存在。如果通配符没有匹配到任何文件, + # bash会把通配符本身作为字符串返回,而这个字符串命名的文件通常不存在。 + if [ -e "${sources[0]}" ]; then + echo " 源文件已找到,准备同步..." + echo " 目标目录: $dest_dir" + + # 创建目标目录(如果不存在) + mkdir -p "$dest_dir" + + # 执行 rsync + rsync -avh --progress --stats "${sources[@]}" "$dest_dir/" + echo "--> 任务 '$task_name' 同步完成。" + else + echo " 警告: 源路径 '$src_pattern' 未匹配到任何文件,已跳过此任务。" + fi + echo "----------------------------------------" +} + + +# --- 任务列表 --- + +# 任务1: 同步 1_cholecseg8k +handle_sync_task \ + "1_cholecseg8k" \ + "$SOURCE_DIR/1_cholecseg8k-Class_13-Alg*" \ + "$DEST_DIR/1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg" + +# 任务2: 同步 2_autolaparo +handle_sync_task \ + "2_autolaparo" \ + "$SOURCE_DIR/2_autolaparo-Class_10-Alg*" \ + "$DEST_DIR/2_AutoLaparo-10Type-1920x1080_outputs-MMSeg" + +# 任务3: 同步 3_1_endovis_2017 +handle_sync_task \ + "3_1_endovis_2017" \ + "$SOURCE_DIR/3_1_endovis_2017-Class_8-Alg*" \ + "$DEST_DIR/3_1_Endovis_2017-8Type-512x512_outputs-MMSeg" + +# 任务4: 同步 3_2_endovis_2018 +handle_sync_task \ + "3_2_endovis_2018" \ + "$SOURCE_DIR/3_2_endovis_2018-Class_8-Alg*" \ + "$DEST_DIR/3_2_Endovis_2018-8Type-512x512_outputs-MMSeg" + +# 任务5: 同步 4_dresden +handle_sync_task \ + "4_dresden" \ + "$SOURCE_DIR/4_dresden-Class_11-Alg*" \ + "$DEST_DIR/4_Dresden-11Type-512x512_outputs-MMSeg" + + +echo "========================================" +echo "所有 rsync 同步任务已执行完毕!" \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_FLOPs_FPS_V2.py b/Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_FLOPs_FPS_V2.py new file mode 100644 index 0000000..828661f --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_FLOPs_FPS_V2.py @@ -0,0 +1,427 @@ +import os +import glob +import logging +import argparse +import re +import subprocess +import csv +from typing import Dict, Optional, Tuple, List +import time +import numpy as np +import torch +from mmengine import Config +from mmengine.model.utils import revert_sync_batchnorm +from mmengine.registry import init_default_scope +from mmengine.runner import Runner, load_checkpoint +from mmseg.registry import MODELS + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# --- 辅助函数 --- +def find_model_files(model_dir: str): + """ + 在给定的模型目录中查找配置文件、最佳检查点和日志文件。 + + Args: + model_dir (str): 模型的根目录。 + + Returns: + Optional]: 包含 'config', 'checkpoint', 'log' 路径的字典, + 如果缺少任何必要文件,则返回 None。 + """ + config_files = glob.glob(os.path.join(model_dir, '*.py')) + if not config_files: + logging.warning(f"在目录 {model_dir} 中未找到配置文件 (.py)。") + return None + config_path = config_files[0] + + checkpoint_path = os.path.join(model_dir, 'best.pth') + if not os.path.exists(checkpoint_path): + epoch_files = glob.glob(os.path.join(model_dir, 'epoch_*.pth')) + if not epoch_files: + logging.warning(f"在目录 {model_dir} 中未找到 'best.pth' 或 'epoch_*.pth' 检查点文件。") + return None + + # 通过正则表达式从文件名中提取周期数并找到最大的 + latest_epoch = -1 + latest_file = None + for f in epoch_files: + match = re.search(r'epoch_(\d+)\.pth', os.path.basename(f)) + if match: + epoch_num = int(match.group(1)) + if epoch_num > latest_epoch: + latest_epoch = epoch_num + latest_file = f + + if latest_file: + checkpoint_path = latest_file + else: + logging.warning(f"在目录 {model_dir} 中无法确定最新的检查点文件。") + return None + + return {'config': config_path, 'checkpoint': checkpoint_path} + +def find_model_config(model_dir: str): + """ + 在给定的模型目录中查找配置文件 (.py)。 + + Args: + model_dir (str): 模型的根目录。 + + Returns: + Optional[str]: 配置文件的路径,如果未找到则返回 None。 + """ + config_files = glob.glob(os.path.join(model_dir, '*.py')) + if not config_files: + logging.warning(f"在目录 {model_dir} 中未找到配置文件 (.py)。") + return None + return config_files[0] + +def get_shape_from_path(path: str): + """ + 从文件夹路径中通过正则表达式提取分辨率 (宽x高)。 + + Args: + path (str): 数据集文件夹的路径。 + + Returns: + Optional[Tuple[int, int]]: 一个包含 (高度, 宽度) 的元组,如果未找到则返回 None。 + 注意:工具需要 H W 格式。 + """ + match = re.search(r'(\d+)x(\d+)', os.path.basename(path)) + if match: + width, height = int(match.group(1)), int(match.group(2)) + return (height, width) # 返回 H, W + return None + +def get_flops_and_params(config_path: str, shape: Tuple[int, int]) -> Optional[Dict[str, str]]: + """ + 运行 mmsegmentation 的 get_flops.py 工具并解析其输出。 + 此版本适配了新版的直接输出格式 (例如 "Flops: 0.118T")。 + + Args: + config_path (str): 模型的 .py 配置文件路径。 + shape (Tuple[int, int]): 输入图像的 (H, W) 元组。 + + Returns: + Optional[Dict[str, str]]: 包含 'params' 和 'flops' 的字典,如果失败则返回 None。 + """ + # 检查工具脚本是否存在 + tool_script = 'tools/analysis_tools/get_flops.py' + if not os.path.exists(tool_script): + logging.error(f"错误: '{tool_script}' 未找到。请确保在 MMSegmentation 项目的根目录下运行此脚本。") + return None + + # 构建命令行 + command = [ + 'python', tool_script, config_path, + '--shape', str(shape[0]), str(shape[1]) + ] + + logging.info(f"执行命令: {' '.join(command)}") + + try: + # 执行命令并捕获输出 + result = subprocess.run(command, capture_output=True, text=True, check=True, encoding='utf-8') + output = result.stdout + + # 使用新的正则表达式来匹配更新后的输出格式 + flops_match = re.search(r"Flops:\s*([0-9.]+\s*[TGMK]?)", output) + params_match = re.search(r"Params:\s*([0-9.]+\s*[TGMK]?)", output) + + if flops_match and params_match: + raw_flops_str = flops_match.group(1).strip() + params = params_match.group(1).strip() + # --- 开始单位换算 --- + value_str = raw_flops_str.rstrip('TGMKtgmk').strip() + unit = raw_flops_str[-1].upper() if raw_flops_str[-1].isalpha() else 'G' + try: + value = float(value_str) + if unit == 'T': + value_in_g = value * 1000 + elif unit == 'M': + value_in_g = value / 1000 + elif unit == 'K': + value_in_g = value / 1_000_000 + else: # 默认单位是 G + value_in_g = value + # 使用 :g 格式化可以去除末尾多余的0 + flops = f"{value_in_g:g} G" + except ValueError: + flops = raw_flops_str # 如果转换失败,则使用原始值 + # --- 单位换算结束 --- + + logging.info(f"✅ 解析成功: FLOPs={flops} (原始值: {raw_flops_str}), Params={params}") + return {'flops': flops, 'params': params} + else: + logging.warning(f"❌ 无法从命令输出中解析 FLOPs 或 Params。请检查以下输出内容:\n---\n{output}\n---") + return None + + except subprocess.CalledProcessError as e: + logging.error(f"执行 get_flops.py 时出错。返回码: {e.returncode}") + logging.error(f"错误输出 (stderr):\n---\n{e.stderr}\n---") + return None + except FileNotFoundError: + logging.error("错误: 'python' 命令未找到。请确保 Python 环境已正确配置。") + return None + +def get_benchmark_stats(config_path: str, checkpoint_path: str, repeat_times: int) -> Optional[Dict[str, float]]: + """ + Runs inference benchmark based on the logic from benchmark.py. + + Args: + config_path (str): Path to the model's .py config file. + checkpoint_path (str): Path to the model's .pth checkpoint file. + repeat_times (int): Number of times to run the benchmark. + + Returns: + Optional[Dict[str, float]]: Dict containing 'average_fps' and 'fps_variance', or None on failure. + """ + try: + cfg = Config.fromfile(config_path) + init_default_scope(cfg.get('default_scope', 'mmseg')) + + torch.backends.cudnn.benchmark = False + cfg.model.pretrained = None + cfg.test_dataloader.batch_size = 1 # Crucial for FPS measurement + + overall_fps_list = [] + for time_index in range(repeat_times): + logging.info(f"--- Starting Benchmark Run {time_index + 1}/{repeat_times} ---") + + data_loader = Runner.build_dataloader(cfg.test_dataloader) + + cfg.model.train_cfg = None + model = MODELS.build(cfg.model) + + load_checkpoint(model, checkpoint_path, map_location='cpu') + + if torch.cuda.is_available(): + model = model.cuda(0) + else: + logging.warning("CUDA is not available. Benchmarking on CPU, results may be slow.") + + model = revert_sync_batchnorm(model) + model.eval() + + num_warmup = 5 + pure_inf_time = 0 + total_iters = 100 # Reduced from 200 for faster script execution + + for i, data in enumerate(data_loader): + data = model.data_preprocessor(data, True) + if torch.cuda.is_available(): + torch.cuda.synchronize() + start_time = time.perf_counter() + + with torch.no_grad(): + model(data['inputs'], data['data_samples'], mode='predict') + + if torch.cuda.is_available(): + torch.cuda.synchronize() + elapsed = time.perf_counter() - start_time + + if i >= num_warmup: + pure_inf_time += elapsed + + if (i + 1) == total_iters: + fps = (total_iters - num_warmup) / pure_inf_time + logging.info(f"Run {time_index + 1} Overall FPS: {fps:.2f} img/s") + overall_fps_list.append(fps) + break + + if not overall_fps_list: + logging.error("Benchmark failed to produce any results.") + return None + + avg_fps = round(np.mean(overall_fps_list), 2) + fps_var = round(np.var(overall_fps_list), 4) + + logging.info(f"✅ Benchmark Complete: Average FPS={avg_fps}, Variance={fps_var}") + return {'average_fps': avg_fps, 'fps_variance': fps_var} + + except Exception as e: + logging.error(f"An exception occurred during benchmarking: {e}") + return None + +# --- 主函数 --- +def main(args): + """ + 脚本主入口,负责编排整个自动化分析流程。 + """ + input_root = args.input_dir + output_root = args.output_dir + # --- 开始交互式选择修改 (V2 - 两级菜单) --- + if not os.path.isdir(input_root): + logging.error(f"输入目录不存在: {input_root}") + return + + # 1. 定义有效的数据集文件夹白名单 + VALID_DATASET_FOLDERS = [ + '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_AutoLaparo-10Type-1920x1080_outputs-MMSeg', + '3_1_Endovis_2017-8Type-512x512_outputs-MMSeg', + '3_2_Endovis_2018-8Type-512x512_outputs-MMSeg', + '4_Dresden-11Type-512x512_outputs-MMSeg' + ] + + # 2. 查找存在的、有效的数据集目录 + existing_dataset_dirs = [ + os.path.join(input_root, d) for d in VALID_DATASET_FOLDERS + if os.path.isdir(os.path.join(input_root, d)) + ] + + if not existing_dataset_dirs: + logging.error(f"在输入目录 {input_root} 中未找到任何有效的数据集文件夹。") + return + + # 3. 第一级菜单:选择数据集 + dataset_map = {str(i + 1): path for i, path in enumerate(existing_dataset_dirs)} + + print("\n" + "="*50) + print("--- 步骤 1: 请选择要处理的数据集 ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice1 = input("请输入数据集编号并按回车键: ").strip() + + model_dirs = [] # 初始化最终要处理的目录列表 + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"您已选择数据集: [{os.path.basename(selected_dataset_dir)}]") + + # 4. 查找选定数据集下的所有算法子目录 + alg_dirs = sorted([ + d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d) + ]) + + if not alg_dirs: + logging.warning(f"在 {os.path.basename(selected_dataset_dir)} 中未发现任何算法子文件夹。程序退出。") + else: + # 5. 第二级菜单:选择算法 + alg_map = {str(i + 1): path for i, path in enumerate(alg_dirs)} + print("\n" + "="*50) + print("--- 步骤 2: 请选择要处理的算法 ---") + print("0: 批量处理当前数据集下的【全部】算法") + for key, path in alg_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice2 = input("请输入算法编号 (或输入 '0' 处理全部) 并按回车键: ").strip() + + # 6. 根据第二级选择,最终确定 model_dirs + if choice2 == '0': + model_dirs = alg_dirs + logging.info(f"您选择了批量处理全部 {len(model_dirs)} 个算法。") + elif choice2 in alg_map: + model_dirs = [alg_map[choice2]] # 将单个路径放入列表中 + logging.info(f"您选择了处理单个算法: {os.path.basename(model_dirs[0])}") + else: + logging.error("无效的算法选择,程序已退出。") + else: + logging.error("无效的数据集选择,程序已退出。") + + # --- 交互式选择修改结束 --- + results: List[Dict[str, str]] = [] + # 修改后的循环,将遍历经过用户筛选后的 model_dirs 列表 + for model_dir in model_dirs: + model_name = os.path.basename(model_dir) + logging.info(f"--- 开始处理模型: {model_name} ---") + files = find_model_files(model_dir) + if not files: + logging.warning(f"跳过目录 {model_dir},因为缺少必要文件。") + continue + + # 构建输出目录 + # 从模型名中提取数据集标识作为Key + dataset_key = model_name.split('-')[0] + dataset_map = { + '1_cholecseg8k': '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_autolaparo': '2_AutoLaparo-10Type-1920x1080_outputs-MMSeg', + '3_1_endovis_2017': '3_1_Endovis_2017-8Type-512x512_outputs-MMSeg', + '3_2_endovis_2018': '3_2_Endovis_2018-8Type-512x512_outputs-MMSeg', + '4_dresden': '4_Dresden-11Type-512x512_outputs-MMSeg' + } + # 使用提取的Key(字符串)进行查询,并为默认值也使用该Key + output_dataset_folder = dataset_map.get(dataset_key, f"{dataset_key}_outputs-MMSeg") + + # 尝试从数据集文件夹名称中获取分辨率 + input_shape = get_shape_from_path(selected_dataset_dir) + if not input_shape: + # 如果无法自动提取,要求用户输入 + logging.warning(f"无法从文件夹 '{os.path.basename(selected_dataset_dir)}' 名称中自动检测分辨率。") + try: + h_str = input("请输入默认测试高度 (H),例如 512: ").strip() + w_str = input("请输入默认测试宽度 (W),例如 512: ").strip() + input_shape = (int(h_str), int(w_str)) + except ValueError: + logging.error("输入无效,必须是整数。程序退出。") + return + logging.info(f"将使用输入形状 (H, W): {input_shape} 进行计算。") + + # 加载配置 + config_file = find_model_config(model_dir) + + # 获取 FLOPs 和 Params + flops_and_params_stats = get_flops_and_params(config_file, input_shape) + benchmark_stats = get_benchmark_stats(files['config'], files['checkpoint'], args.repeat_times) + if flops_and_params_stats: + short_model_name = model_name.split('Alg_', 1)[1] + results.append({ + 'Model': short_model_name, + 'Params': flops_and_params_stats['params'] if flops_and_params_stats else 'N/A' , + 'FLOPs': flops_and_params_stats['flops'] if flops_and_params_stats else 'N/A' , + 'Input_Shape (HxW)': f"{input_shape[0]}x{input_shape[1]}", + 'Average_FPS': benchmark_stats['average_fps'] if benchmark_stats else 'N/A', + 'FPS_Variance': benchmark_stats['fps_variance'] if benchmark_stats else 'N/A' + }) + else: + logging.warning(f"未能获取模型 {model_name} 的统计信息。") + + # --- 将结果写入 CSV 文件 --- + if not results: + logging.info("没有成功获取任何模型的统计数据,不生成 CSV 文件。") + return + + # 新建文件夹并保存 CSV + final_output_dir = os.path.join(output_root, output_dataset_folder) + os.makedirs(final_output_dir, exist_ok=True) + dataset_name = os.path.basename(selected_dataset_dir).split('_outputs-MMSeg')[0] + output_csv_path = os.path.join(final_output_dir, f'{dataset_name}_flops_params_fps_summary.csv') + + try: + with open(output_csv_path, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = ['Model', 'Params', 'FLOPs', 'Input_Shape (HxW)', 'Average_FPS', 'FPS_Variance'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + + writer.writeheader() + writer.writerows(results) + + logging.info(f"=== 全部处理完成!结果已成功保存到: {output_csv_path} ===") + except IOError as e: + logging.error(f"无法写入 CSV 文件: {output_csv_path}。错误: {e}") + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="MMSegmentation 自动化评估脚本") + parser.add_argument( + '--input_dir', + type=str, + default='../Hardisk', + help="包含已训练模型文件夹的根目录。" + ) + parser.add_argument( + '--output_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="用于存储所有分析结果的根目录。" + ) + parser.add_argument( + '--repeat-times', + type=int, + default=3, + help="Number of times to repeat the benchmark for averaging." + ) + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_and_FLOPs_V1.py b/Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_and_FLOPs_V1.py new file mode 100644 index 0000000..501430a --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/4_1_predict_params_and_FLOPs_V1.py @@ -0,0 +1,332 @@ +import os +import glob +import logging +import argparse +import re +import subprocess +import csv +from typing import Dict, Optional, Tuple, List + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# --- 辅助函数 --- +def find_model_files(model_dir: str): + """ + 在给定的模型目录中查找配置文件、最佳检查点和日志文件。 + + Args: + model_dir (str): 模型的根目录。 + + Returns: + Optional]: 包含 'config', 'checkpoint', 'log' 路径的字典, + 如果缺少任何必要文件,则返回 None。 + """ + config_files = glob.glob(os.path.join(model_dir, '*.py')) + if not config_files: + logging.warning(f"在目录 {model_dir} 中未找到配置文件 (.py)。") + return None + config_path = config_files[0] + + checkpoint_path = os.path.join(model_dir, 'best.pth') + if not os.path.exists(checkpoint_path): + epoch_files = glob.glob(os.path.join(model_dir, 'epoch_*.pth')) + if not epoch_files: + logging.warning(f"在目录 {model_dir} 中未找到 'best.pth' 或 'epoch_*.pth' 检查点文件。") + return None + + # 通过正则表达式从文件名中提取周期数并找到最大的 + latest_epoch = -1 + latest_file = None + for f in epoch_files: + match = re.search(r'epoch_(\d+)\.pth', os.path.basename(f)) + if match: + epoch_num = int(match.group(1)) + if epoch_num > latest_epoch: + latest_epoch = epoch_num + latest_file = f + + if latest_file: + checkpoint_path = latest_file + else: + logging.warning(f"在目录 {model_dir} 中无法确定最新的检查点文件。") + return None + + return {'config': config_path, 'checkpoint': checkpoint_path} + +def find_model_config(model_dir: str): + """ + 在给定的模型目录中查找配置文件 (.py)。 + + Args: + model_dir (str): 模型的根目录。 + + Returns: + Optional[str]: 配置文件的路径,如果未找到则返回 None。 + """ + config_files = glob.glob(os.path.join(model_dir, '*.py')) + if not config_files: + logging.warning(f"在目录 {model_dir} 中未找到配置文件 (.py)。") + return None + return config_files[0] + +def get_shape_from_path(path: str): + """ + 从文件夹路径中通过正则表达式提取分辨率 (宽x高)。 + + Args: + path (str): 数据集文件夹的路径。 + + Returns: + Optional[Tuple[int, int]]: 一个包含 (高度, 宽度) 的元组,如果未找到则返回 None。 + 注意:工具需要 H W 格式。 + """ + match = re.search(r'(\d+)x(\d+)', os.path.basename(path)) + if match: + width, height = int(match.group(1)), int(match.group(2)) + return (height, width) # 返回 H, W + return None + +def get_flops_and_params(config_path: str, shape: Tuple[int, int]) -> Optional[Dict[str, str]]: + """ + 运行 mmsegmentation 的 get_flops.py 工具并解析其输出。 + 此版本适配了新版的直接输出格式 (例如 "Flops: 0.118T")。 + + Args: + config_path (str): 模型的 .py 配置文件路径。 + shape (Tuple[int, int]): 输入图像的 (H, W) 元组。 + + Returns: + Optional[Dict[str, str]]: 包含 'params' 和 'flops' 的字典,如果失败则返回 None。 + """ + # 检查工具脚本是否存在 + tool_script = 'tools/analysis_tools/get_flops.py' + if not os.path.exists(tool_script): + logging.error(f"错误: '{tool_script}' 未找到。请确保在 MMSegmentation 项目的根目录下运行此脚本。") + return None + + # 构建命令行 + command = [ + 'python', tool_script, config_path, + '--shape', str(shape[0]), str(shape[1]) + ] + + logging.info(f"执行命令: {' '.join(command)}") + + try: + # 执行命令并捕获输出 + result = subprocess.run(command, capture_output=True, text=True, check=True, encoding='utf-8') + output = result.stdout + + # 使用新的正则表达式来匹配更新后的输出格式 + flops_match = re.search(r"Flops:\s*([0-9.]+\s*[TGMK]?)", output) + params_match = re.search(r"Params:\s*([0-9.]+\s*[TGMK]?)", output) + + if flops_match and params_match: + raw_flops_str = flops_match.group(1).strip() + params = params_match.group(1).strip() + # --- 开始单位换算 --- + value_str = raw_flops_str.rstrip('TGMKtgmk').strip() + unit = raw_flops_str[-1].upper() if raw_flops_str[-1].isalpha() else 'G' + try: + value = float(value_str) + if unit == 'T': + value_in_g = value * 1000 + elif unit == 'M': + value_in_g = value / 1000 + elif unit == 'K': + value_in_g = value / 1_000_000 + else: # 默认单位是 G + value_in_g = value + # 使用 :g 格式化可以去除末尾多余的0 + flops = f"{value_in_g:g} G" + except ValueError: + flops = raw_flops_str # 如果转换失败,则使用原始值 + # --- 单位换算结束 --- + + logging.info(f"✅ 解析成功: FLOPs={flops} (原始值: {raw_flops_str}), Params={params}") + return {'flops': flops, 'params': params} + else: + logging.warning(f"❌ 无法从命令输出中解析 FLOPs 或 Params。请检查以下输出内容:\n---\n{output}\n---") + return None + + except subprocess.CalledProcessError as e: + logging.error(f"执行 get_flops.py 时出错。返回码: {e.returncode}") + logging.error(f"错误输出 (stderr):\n---\n{e.stderr}\n---") + return None + except FileNotFoundError: + logging.error("错误: 'python' 命令未找到。请确保 Python 环境已正确配置。") + return None + +# --- 主函数 --- +def main(args): + """ + 脚本主入口,负责编排整个自动化分析流程。 + """ + input_root = args.input_dir + output_root = args.output_dir + # --- 开始交互式选择修改 (V2 - 两级菜单) --- + if not os.path.isdir(input_root): + logging.error(f"输入目录不存在: {input_root}") + return + + # 1. 定义有效的数据集文件夹白名单 + VALID_DATASET_FOLDERS = [ + '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_AutoLaparo-10Type-1920x1080_outputs-MMSeg', + '3_1_Endovis_2017-8Type-512x512_outputs-MMSeg', + '3_2_Endovis_2018-8Type-512x512_outputs-MMSeg', + '4_Dresden-11Type-512x512_outputs-MMSeg' + ] + + # 2. 查找存在的、有效的数据集目录 + existing_dataset_dirs = [ + os.path.join(input_root, d) for d in VALID_DATASET_FOLDERS + if os.path.isdir(os.path.join(input_root, d)) + ] + + if not existing_dataset_dirs: + logging.error(f"在输入目录 {input_root} 中未找到任何有效的数据集文件夹。") + return + + # 3. 第一级菜单:选择数据集 + dataset_map = {str(i + 1): path for i, path in enumerate(existing_dataset_dirs)} + + print("\n" + "="*50) + print("--- 步骤 1: 请选择要处理的数据集 ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice1 = input("请输入数据集编号并按回车键: ").strip() + + model_dirs = [] # 初始化最终要处理的目录列表 + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"您已选择数据集: [{os.path.basename(selected_dataset_dir)}]") + + # 4. 查找选定数据集下的所有算法子目录 + alg_dirs = sorted([ + d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d) + ]) + + if not alg_dirs: + logging.warning(f"在 {os.path.basename(selected_dataset_dir)} 中未发现任何算法子文件夹。程序退出。") + else: + # 5. 第二级菜单:选择算法 + alg_map = {str(i + 1): path for i, path in enumerate(alg_dirs)} + print("\n" + "="*50) + print("--- 步骤 2: 请选择要处理的算法 ---") + print("0: 批量处理当前数据集下的【全部】算法") + for key, path in alg_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice2 = input("请输入算法编号 (或输入 '0' 处理全部) 并按回车键: ").strip() + + # 6. 根据第二级选择,最终确定 model_dirs + if choice2 == '0': + model_dirs = alg_dirs + logging.info(f"您选择了批量处理全部 {len(model_dirs)} 个算法。") + elif choice2 in alg_map: + model_dirs = [alg_map[choice2]] # 将单个路径放入列表中 + logging.info(f"您选择了处理单个算法: {os.path.basename(model_dirs[0])}") + else: + logging.error("无效的算法选择,程序已退出。") + else: + logging.error("无效的数据集选择,程序已退出。") + + # --- 交互式选择修改结束 --- + results: List[Dict[str, str]] = [] + # 修改后的循环,将遍历经过用户筛选后的 model_dirs 列表 + for model_dir in model_dirs: + model_name = os.path.basename(model_dir) + logging.info(f"--- 开始处理模型: {model_name} ---") + files = find_model_files(model_dir) + if not files: + logging.warning(f"跳过目录 {model_dir},因为缺少必要文件。") + continue + + # 构建输出目录 + # 从模型名中提取数据集标识作为Key + dataset_key = model_name.split('-')[0] + dataset_map = { + '1_cholecseg8k': '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_autolaparo': '2_AutoLaparo-10Type-1280x1024_outputs-MMSeg', + '3_1_endovis_2017': '3_1_EndoVis_2017-7Type-1280x1024_outputs-MMSeg', + '3_2_endovis_2018': '3_2_EndoVis_2018-11Type-1280x1024_outputs-MMSeg', + '4_dresden': '4_Dresden-6Type-1920x1080_outputs-MMSeg' + } + # 使用提取的Key(字符串)进行查询,并为默认值也使用该Key + output_dataset_folder = dataset_map.get(dataset_key, f"{dataset_key}_outputs-MMSeg") + + # 尝试从数据集文件夹名称中获取分辨率 + input_shape = get_shape_from_path(selected_dataset_dir) + if not input_shape: + # 如果无法自动提取,要求用户输入 + logging.warning(f"无法从文件夹 '{os.path.basename(selected_dataset_dir)}' 名称中自动检测分辨率。") + try: + h_str = input("请输入默认测试高度 (H),例如 512: ").strip() + w_str = input("请输入默认测试宽度 (W),例如 512: ").strip() + input_shape = (int(h_str), int(w_str)) + except ValueError: + logging.error("输入无效,必须是整数。程序退出。") + return + logging.info(f"将使用输入形状 (H, W): {input_shape} 进行计算。") + + # 加载配置 + config_file = find_model_config(model_dir) + + # 获取 FLOPs 和 Params + stats = get_flops_and_params(config_file, input_shape) + if stats: + short_model_name = model_name.split('Alg_', 1)[1] + results.append({ + 'Model': short_model_name, + 'Params': stats['params'], + 'FLOPs': stats['flops'], + 'Input_Shape (HxW)': f"{input_shape[0]}x{input_shape[1]}" + }) + else: + logging.warning(f"未能获取模型 {model_name} 的统计信息。") + + # --- 将结果写入 CSV 文件 --- + if not results: + logging.info("没有成功获取任何模型的统计数据,不生成 CSV 文件。") + return + + # 新建文件夹并保存 CSV + final_output_dir = os.path.join(output_root, output_dataset_folder) + os.makedirs(final_output_dir, exist_ok=True) + dataset_name = os.path.basename(selected_dataset_dir).split('_outputs-MMSeg')[0] + output_csv_path = os.path.join(final_output_dir, f'{dataset_name}_flops_params_summary.csv') + + try: + with open(output_csv_path, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = ['Model', 'Params', 'FLOPs', 'Input_Shape (HxW)'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + + writer.writeheader() + writer.writerows(results) + + logging.info(f"=== 全部处理完成!结果已成功保存到: {output_csv_path} ===") + except IOError as e: + logging.error(f"无法写入 CSV 文件: {output_csv_path}。错误: {e}") + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="MMSegmentation 自动化评估脚本") + parser.add_argument( + '--input_dir', + type=str, + default='../Hardisk', + help="包含已训练模型文件夹的根目录。" + ) + parser.add_argument( + '--output_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="用于存储所有分析结果的根目录。" + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V1.py b/Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V1.py new file mode 100644 index 0000000..47e1cfc --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V1.py @@ -0,0 +1,322 @@ +import os +import glob +import logging +import argparse +import re +import csv +from typing import Dict, Optional, List + +# TODO 这个是获取最后一次结果的 TODO + +# --- 配置日志记录 --- +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# --- 辅助函数 --- +def find_all_log_files_sorted(algorithm_dir: str) -> List[str]: + """ + 查找给定算法目录中所有的.log文件,并按从新到旧的顺序排列。 + + Args: + algorithm_dir (str): 算法的根目录。 + + Returns: + List[str]: 按时间倒序排列的日志文件路径列表。 + """ + try: + subdirs = [d for d in os.listdir(algorithm_dir) if os.path.isdir(os.path.join(algorithm_dir, d))] + except FileNotFoundError: + logging.error(f"算法目录不存在: {algorithm_dir}") + return [] + + if not subdirs: + logging.warning(f"在目录 {algorithm_dir} 中未找到任何时间戳子目录。") + return [] + + # 按名称倒序排序,最新的目录会排在最前面 + sorted_subdirs = sorted(subdirs, reverse=True) + + log_files = [] + for subdir_name in sorted_subdirs: + subdir_path = os.path.join(algorithm_dir, subdir_name) + logs_in_subdir = glob.glob(os.path.join(subdir_path, '*.log')) + if logs_in_subdir: + # 假设每个子目录只有一个log文件 + log_files.append(logs_in_subdir[0]) + + return log_files + +def parse_log_metrics(log_path: str) -> Optional[Dict]: + """ + 解析日志文件,提取最后一次完整验证(validation)的结果及其对应的Epoch。 + + Args: + log_path (str): 日志文件的路径。 + + Returns: + Optional[Dict]: 包含 'epoch', 'summary', 'class_wise' 指标的字典,如果解析失败则返回 None。 + """ + try: + with open(log_path, 'r', encoding='utf-8') as f: + content = f.read() + except IOError as e: + logging.error(f"无法读取日志文件: {log_path}。错误: {e}") + return None + # 定义正则表达式 + summary_pattern = re.compile( + r"Iter\(val\) \[\d+/\d+\]\s+aAcc:\s*([\d.]+)\s+mIoU:\s*([\d.]+)\s+mAcc:\s*([\d.]+)" + ) + class_table_pattern = re.compile( + # --- 使用新的模式匹配可变长度的顶部边框 --- + r"\+(?:-+\+)+\s*\n" + r"\|.*?Class.*?\|.*?IoU.*?\|.*?Acc.*?\|\n" + # --- 匹配中间边框 --- + r"\+(?:-+\+)+\s*\n" + # --- 捕获表格主体 --- + r"((?:\|.*?\|.*?\|.*?\|\n)+)" + # --- 匹配底部边框 --- + r"\+(?:-+\+)+\s*\n", + re.MULTILINE + ) + epoch_pattern = re.compile(r"Saving checkpoint at (\d+) epochs|resumed epoch: (\d+)") + + # 查找所有匹配项 + summary_matches = list(re.finditer(summary_pattern, content)) + table_matches = list(re.finditer(class_table_pattern, content)) + epoch_matches = list(re.finditer(epoch_pattern, content)) + + if not summary_matches or not table_matches: + logging.warning(f"❌ 在日志 {os.path.basename(log_path)} 中未能找到完整的验证结果。") + return None + + last_summary_match = summary_matches[-1] + last_table_match = None + for table in reversed(table_matches): + if table.end() < last_summary_match.start(): + last_table_match = table + break + + if not last_table_match: + logging.warning(f"❌ 在日志 {os.path.basename(log_path)} 中找到总结行但未能匹配到对应的类别表格。") + return None + + # 寻找关联的最新Epoch + last_epoch = "N/A" + latest_epoch_num = -1 + for epoch_match in epoch_matches: + if epoch_match.end() < last_table_match.start(): + epoch_str = epoch_match.group(1) or epoch_match.group(2) + if epoch_str: + epoch_num = int(epoch_str) + if epoch_num > latest_epoch_num: + latest_epoch_num = epoch_num + last_epoch = f"epoch_{epoch_num}" + + # 解析数据 + summary_groups = last_summary_match.groups() + results = { + 'epoch': last_epoch, + 'summary': { + 'aAcc': summary_groups[0], + 'mIoU': summary_groups[1], + 'mAcc': summary_groups[2] + }, + 'class_wise': [] + } + + table_content = last_table_match.group(0) + row_pattern = re.compile(r"\|\s*([\w\s]+?)\s*\|\s*([\d.]+)\s*\|\s*([\d.]+)\s*\|") + for line in table_content.strip().split('\n'): + row_match = row_pattern.match(line) + if row_match: + class_name, iou, acc = row_match.groups() + results['class_wise'].append({ + 'Class': class_name.strip(), + 'IoU': iou, + 'Acc': acc + }) + + if results['class_wise']: + logging.info(f"✅ 成功从 {os.path.basename(log_path)} 中解析出 Epoch '{last_epoch}' 的指标。") + return results + else: + logging.warning(f"❌ 在 {os.path.basename(log_path)} 中未能解析出任何类别行。") + return None + +# --- 主函数 --- +def main(args): + """ + 脚本主入口,负责编排整个自动化分析流程。 + """ + input_root = args.input_dir + output_root = args.output_dir + + if not os.path.isdir(input_root): + logging.error(f"输入目录不存在: {input_root}") + return + + # --- 交互式菜单 --- + all_dataset_dirs = sorted([ + d for d in glob.glob(os.path.join(input_root, '*_outputs-MMSeg')) if os.path.isdir(d) + ]) + + if not all_dataset_dirs: + logging.error(f"在输入目录 {input_root} 中未找到任何有效的数据集文件夹。") + return + + dataset_map = {str(i + 1): path for i, path in enumerate(all_dataset_dirs)} + + print("\n" + "="*50) + print("--- 步骤 1: 请选择要处理的数据集 ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice1 = input("请输入数据集编号并按回车键: ").strip() + + model_dirs = [] + selected_dataset_dir = None + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"您已选择数据集: [{os.path.basename(selected_dataset_dir)}]") + + alg_dirs = sorted([ + d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d) + ]) + + if not alg_dirs: + logging.warning(f"在 {os.path.basename(selected_dataset_dir)} 中未发现任何算法子文件夹。") + return + + alg_map = {str(i + 1): path for i, path in enumerate(alg_dirs)} + print("\n" + "="*50) + print("--- 步骤 2: 请选择要处理的算法 ---") + print("0: 批量处理当前数据集下的【全部】算法") + for key, path in alg_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice2 = input("请输入算法编号 (或输入 '0' 处理全部) 并按回车键: ").strip() + + if choice2 == '0': + model_dirs = alg_dirs + logging.info(f"您选择了批量处理全部 {len(model_dirs)} 个算法。") + elif choice2 in alg_map: + model_dirs = [alg_map[choice2]] + logging.info(f"您选择了处理单个算法: {os.path.basename(model_dirs[0])}") + else: + logging.error("无效的算法选择,程序已退出。") + return + else: + logging.error("无效的数据集选择,程序已退出。") + return + + # --- 开始处理选定的算法 (逻辑已修改) --- + csv_rows = [] + output_dataset_folder = "" + + for model_dir in model_dirs: + model_name = os.path.basename(model_dir) + logging.info(f"\n--- 开始处理算法: {model_name} ---") + + # (路径构建代码保持不变) + if not output_dataset_folder: + dataset_key = model_name.split('-')[0] + dataset_folder_map = { + '1_cholecseg8k': '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_autolaparo': '2_AutoLaparo-10Type-1920x1080_outputs-MMSeg', + '3_1_endovis_2017': '3_1_Endovis_2017-8Type-512x512_outputs-MMSeg', + '3_2_endovis_2018': '3_2_Endovis_2018-8Type-512x512_outputs-MMSeg', + '4_dresden': '4_Dresden-11Type-512x512_outputs-MMSeg' + } + output_dataset_folder = dataset_folder_map.get(dataset_key, f"{dataset_key}_outputs-MMSeg") + + # --- 新的循环查找逻辑 --- + # 1. 获取所有按时间倒序排列的日志文件 + all_logs_sorted = find_all_log_files_sorted(model_dir) + + if not all_logs_sorted: + logging.warning(f"跳过算法 {model_name},因为未找到任何日志文件。") + continue + + # 2. 循环尝试解析,直到成功或全部失败 + metrics = None + for log_file_path in all_logs_sorted: + logging.info(f"正在尝试解析: {os.path.relpath(log_file_path)}") + metrics = parse_log_metrics(log_file_path) + if metrics: + logging.info(f"在 {os.path.basename(log_file_path)} 中成功找到并解析了指标。") + break # 找到后立即跳出循环 + + # 3. 如果所有日志都尝试失败,则跳过此算法 + if not metrics: + logging.warning(f"❌❌❌跳过算法 {model_name},因为在其所有日志文件中都未能找到有效的指标。❌❌❌") + continue + + # --- 创建一个 "宽" 格式的行 --- + summary = metrics['summary'] + short_model_name = model_name.split('Alg_', 1)[1] + row_data = { + 'Algorithm': short_model_name, + 'Epoch': metrics['epoch'], + 'mIoU': summary['mIoU'], + 'mAcc': summary['mAcc'], + 'aAcc': summary['aAcc'] + } + + # 将每个类别的IoU和Acc作为新列添加到行数据中 + for class_data in metrics['class_wise']: + class_name = class_data['Class'].replace(' ', '_') # 清理类名以用作表头 + row_data[f'{class_name}_IoU'] = class_data['IoU'] + row_data[f'{class_name}_Acc'] = class_data['Acc'] + + csv_rows.append(row_data) + + # --- 将结果写入 CSV 文件 (逻辑已修改) --- + if not csv_rows: + logging.info("没有成功获取任何模型的统计数据,不生成 CSV 文件。") + return + + # --- 动态生成并排序表头 --- + # 基础列保持固定顺序 + base_fieldnames = ['Algorithm', 'Epoch', 'mIoU', 'mAcc', 'aAcc'] + # 从第一个结果中获取所有与类别相关的列名,并按字母排序 + first_row_keys = csv_rows[0].keys() + class_fieldnames = sorted([key for key in first_row_keys if key not in base_fieldnames]) + # 最终的完整表头 + final_fieldnames = base_fieldnames + class_fieldnames + + # --- 构建输出路径并写入文件 --- + final_output_dir = os.path.join(output_root, output_dataset_folder) + os.makedirs(final_output_dir, exist_ok=True) + dataset_name = os.path.basename(selected_dataset_dir).split('_outputs-MMSeg')[0] + # 在文件名中加入 "_wide" 以区分格式 + output_csv_path = os.path.join(final_output_dir, f'{dataset_name}_metrics_summary_wide.csv') + + try: + with open(output_csv_path, 'w', newline='', encoding='utf-8-sig') as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=final_fieldnames) + writer.writeheader() + writer.writerows(csv_rows) + + logging.info(f"=== 全部处理完成!结果已成功保存到: {output_csv_path} ===") + except IOError as e: + logging.error(f"无法写入 CSV 文件: {output_csv_path}。错误: {e}") + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="MMSegmentation 最终指标提取脚本 (V2)") + parser.add_argument( + '--input_dir', + type=str, + default='../Hardisk', + help="包含数据集输出文件夹 (例如 '..._outputs-MMSeg') 的根目录。" + ) + parser.add_argument( + '--output_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="用于存储所有分析结果的根目录。" + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V2.py b/Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V2.py new file mode 100644 index 0000000..3570c18 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/4_2_predict_matrics_from_log_V2.py @@ -0,0 +1,366 @@ +import os +import glob +import logging +import argparse +import re +import csv +from typing import Dict, Optional, List + +# TODO 这个是获取最后一次结果的 TODO + +# --- 配置日志记录 --- +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# --- 辅助函数 --- +def find_all_log_files_sorted(algorithm_dir: str) -> List[str]: + """ + 查找给定算法目录中所有的.log文件,并按从新到旧的顺序排列。 + + Args: + algorithm_dir (str): 算法的根目录。 + + Returns: + List[str]: 按时间倒序排列的日志文件路径列表。 + """ + try: + subdirs = [d for d in os.listdir(algorithm_dir) if os.path.isdir(os.path.join(algorithm_dir, d))] + except FileNotFoundError: + logging.error(f"算法目录不存在: {algorithm_dir}") + return [] + + if not subdirs: + logging.warning(f"在目录 {algorithm_dir} 中未找到任何时间戳子目录。") + return [] + + # 按名称倒序排序,最新的目录会排在最前面 + sorted_subdirs = sorted(subdirs, reverse=True) + + log_files = [] + for subdir_name in sorted_subdirs: + subdir_path = os.path.join(algorithm_dir, subdir_name) + logs_in_subdir = glob.glob(os.path.join(subdir_path, '*.log')) + if logs_in_subdir: + # 假设每个子目录只有一个log文件 + log_files.append(logs_in_subdir[0]) + + return log_files + +def get_max_epochs(config_path: str) -> Optional[int]: + """ + 从config.py文件中解析train_cfg字典以获取max_epochs的值。 + + Args: + config_path (str): config.py文件的路径。 + + Returns: + Optional[int]: max_epochs的值,如果找不到则返回None。 + """ + if not os.path.exists(config_path): + logging.error(f"配置文件不存在: {config_path}") + return None + + try: + with open(config_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 使用正则表达式查找 max_epochs + match = re.search(r"train_cfg\s*=\s*dict\(.*?max_epochs\s*=\s*(\d+),.*?\)", content, re.DOTALL) + + if match: + max_epochs = int(match.group(1)) + logging.info(f"从 {os.path.basename(config_path)} 中成功读取 max_epochs: {max_epochs}") + return max_epochs + else: + logging.warning(f"在 {config_path} 中未找到 'max_epochs'。") + return None + except Exception as e: + logging.error(f"解析 {config_path} 时出错: {e}") + return None + +def parse_log_metrics(log_path: str, max_epochs: int) -> List[Dict]: + """ + 解析日志文件,提取所有完整验证(validation)的结果,并计算其对应的Epoch。 + + Args: + log_path (str): 日志文件的路径。 + max_epochs (int): 从config.py中读取的最大epoch数。 + + Returns: + List[Dict]: 包含每次验证的 'epoch', 'summary', 'class_wise' 指标的字典列表。 + """ + try: + with open(log_path, 'r', encoding='utf-8') as f: + content = f.read() + except IOError as e: + logging.error(f"无法读取日志文件: {log_path}。错误: {e}") + return [] + + # 定义所有需要的正则表达式 + summary_pattern = re.compile( + r"Iter\(val\) \[\d+/\d+\]\s+aAcc:\s*([\d.]+)\s+mIoU:\s*([\d.]+)\s+mAcc:\s*([\d.]+)" + ) + class_table_pattern = re.compile( + r"\+(?:-+\+)+\s*\n" + r"\|.*?Class.*?\|.*?IoU.*?\|.*?Acc.*?\|\n" + r"\+(?:-+\+)+\s*\n" + r"((?:\|.*?\|.*?\|.*?\|\n)+)" + r"\+(?:-+\+)+\s*\n", + re.MULTILINE + ) + train_iter_pattern = re.compile(r"Iter\(train\)\s*\[\s*(\d+)\s*/\s*(\d+)\]") + + # 查找所有匹配项 + summary_matches = list(re.finditer(summary_pattern, content)) + table_matches = list(re.finditer(class_table_pattern, content)) + train_iter_matches = list(re.finditer(train_iter_pattern, content)) + + all_metrics = [] + + # 遍历每一次的总结行 (summary) + for i, summary_match in enumerate(summary_matches): + # 寻找与总结行对应的类别表格 + # 表格应该出现在总结行之前 + last_table_match = None + for table in reversed(table_matches): + if table.end() < summary_match.start(): + # 确保这个表格没有被上一个总结行用过 + is_already_used = False + if i > 0: + if table.end() < summary_matches[i-1].start(): + is_already_used = True + if not is_already_used: + last_table_match = table + break + + if not last_table_match: + continue + + # 寻找表格前最近的 Iter(train) 行来计算epoch + last_train_iter_match = None + for train_iter in reversed(train_iter_matches): + if train_iter.end() < last_table_match.start(): + last_train_iter_match = train_iter + break + + epoch = "N/A" + if last_train_iter_match and max_epochs: + current_iter, total_iters = last_train_iter_match.groups() + try: + # 根据公式计算epoch + epoch = int(int(current_iter) / int(total_iters) * max_epochs) + except (ValueError, ZeroDivisionError) as e: + logging.warning(f"Epoch 计算失败: {e}") + + # 解析总结指标 + summary_groups = summary_match.groups() + results = { + 'epoch': epoch, + 'summary': { + 'aAcc': summary_groups[0], + 'mIoU': summary_groups[1], + 'mAcc': summary_groups[2] + }, + 'class_wise': [] + } + + # 解析每个类别的数据 + table_content = last_table_match.group(1) + row_pattern = re.compile(r"\|\s*([\w\s.-]+?)\s*\|\s*([\d.]+)\s*\|\s*([\d.]+)\s*\|") + for line in table_content.strip().split('\n'): + row_match = row_pattern.match(line) + if row_match: + class_name, iou, acc = row_match.groups() + results['class_wise'].append({ + 'Class': class_name.strip(), + 'IoU': iou, + 'Acc': acc + }) + + if results['class_wise']: + all_metrics.append(results) + + if all_metrics: + logging.info(f"✅ 成功从 {os.path.basename(log_path)} 中解析出 {len(all_metrics)} 组指标。") + else: + logging.warning(f"❌ 在日志 {os.path.basename(log_path)} 中未能找到完整的验证结果。") + + return all_metrics + +# --- 主函数 --- +def main(args): + """ + 脚本主入口,负责编排整个自动化分析流程。 + """ + input_root = args.input_dir + output_root = args.output_dir + + if not os.path.isdir(input_root): + logging.error(f"输入目录不存在: {input_root}") + return + + # --- 交互式菜单 (这部分保持不变) --- + all_dataset_dirs = sorted([ + d for d in glob.glob(os.path.join(input_root, '*_outputs-MMSeg')) if os.path.isdir(d) + ]) + + if not all_dataset_dirs: + logging.error(f"在输入目录 {input_root} 中未找到任何有效的数据集文件夹。") + return + + dataset_map = {str(i + 1): path for i, path in enumerate(all_dataset_dirs)} + + print("\n" + "="*50) + print("--- 步骤 1: 请选择要处理的数据集 ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice1 = input("请输入数据集编号并按回车键: ").strip() + + model_dirs = [] + selected_dataset_dir = None + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"您已选择数据集: [{os.path.basename(selected_dataset_dir)}]") + + alg_dirs = sorted([ + d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d) + ]) + + if not alg_dirs: + logging.warning(f"在 {os.path.basename(selected_dataset_dir)} 中未发现任何算法子文件夹。") + return + + alg_map = {str(i + 1): path for i, path in enumerate(alg_dirs)} + print("\n" + "="*50) + print("--- 步骤 2: 请选择要处理的算法 ---") + print("0: 批量处理当前数据集下的【全部】算法") + for key, path in alg_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice2 = input("请输入算法编号 (或输入 '0' 处理全部) 并按回车键: ").strip() + + if choice2 == '0': + model_dirs = alg_dirs + logging.info(f"您选择了批量处理全部 {len(model_dirs)} 个算法。") + elif choice2 in alg_map: + model_dirs = [alg_map[choice2]] + logging.info(f"您选择了处理单个算法: {os.path.basename(model_dirs[0])}") + else: + logging.error("无效的算法选择,程序已退出。") + return + else: + logging.error("无效的数据集选择,程序已退出。") + return + + # --- 开始处理选定的算法 --- + csv_rows = [] + output_dataset_folder = "" + + for model_dir in model_dirs: + model_name = os.path.basename(model_dir) + logging.info(f"\n--- 开始处理算法: {model_name} ---") + + if not output_dataset_folder: + dataset_key = model_name.split('-')[0] + # ... (这部分路径映射逻辑保持不变) + output_dataset_folder = f"{dataset_key}_outputs-MMSeg" + + all_logs_sorted = find_all_log_files_sorted(model_dir) + + if not all_logs_sorted: + logging.warning(f"跳过算法 {model_name},因为未找到任何日志文件。") + continue + + # --- 新逻辑:获取max_epochs --- + # 假设同一算法下所有训练的config是相同的,因此我们从最新的log对应的config读取 + latest_log_path = all_logs_sorted[0] + config_path = os.path.join(os.path.dirname(latest_log_path), 'vis_data', 'config.py') + max_epochs = get_max_epochs(config_path) + if max_epochs is None: + logging.error(f"无法为算法 {model_name} 找到 max_epochs,将跳过。") + continue + + # --- 新逻辑:聚合所有日志的所有指标 --- + all_metrics_for_model = [] + for log_file_path in all_logs_sorted: + logging.info(f"正在解析: {os.path.relpath(log_file_path)}") + # 传递max_epochs + metrics_from_log = parse_log_metrics(log_file_path, max_epochs) + if metrics_from_log: + all_metrics_for_model.extend(metrics_from_log) + + if not all_metrics_for_model: + logging.warning(f"❌❌❌跳过算法 {model_name},因为在其所有日志文件中都未能找到有效的指标。❌❌❌") + continue + + # --- 新逻辑:选择mIoU最高的记录 --- + try: + best_metric = max(all_metrics_for_model, key=lambda x: float(x['summary']['mIoU'])) + logging.info(f"找到了最佳指标: Epoch '{best_metric['epoch']}', mIoU: {best_metric['summary']['mIoU']}") + except (ValueError, TypeError) as e: + logging.error(f"为算法 {model_name} 寻找最佳mIoU时出错: {e}") + continue + + # --- 创建一个 "宽" 格式的行 --- + summary = best_metric['summary'] + short_model_name = model_name.split('Alg_', 1)[1] if 'Alg_' in model_name else model_name + row_data = { + 'Algorithm': short_model_name, + 'Epoch': best_metric['epoch'], + 'mIoU': summary['mIoU'], + 'mAcc': summary['mAcc'], + 'aAcc': summary['aAcc'] + } + + for class_data in best_metric['class_wise']: + class_name = class_data['Class'].replace(' ', '_') + row_data[f'{class_name}_IoU'] = class_data['IoU'] + row_data[f'{class_name}_Acc'] = class_data['Acc'] + + csv_rows.append(row_data) + + # --- 将结果写入 CSV 文件 (这部分保持不变) --- + if not csv_rows: + logging.info("没有成功获取任何模型的统计数据,不生成 CSV 文件。") + return + + base_fieldnames = ['Algorithm', 'Epoch', 'mIoU', 'mAcc', 'aAcc'] + first_row_keys = csv_rows[0].keys() + class_fieldnames = sorted([key for key in first_row_keys if key not in base_fieldnames]) + final_fieldnames = base_fieldnames + class_fieldnames + + final_output_dir = os.path.join(output_root, os.path.basename(selected_dataset_dir)) + os.makedirs(final_output_dir, exist_ok=True) + dataset_name = os.path.basename(selected_dataset_dir).split('_outputs-MMSeg')[0] + output_csv_path = os.path.join(final_output_dir, f'{dataset_name}_metrics_summary_wide.csv') + + try: + with open(output_csv_path, 'w', newline='', encoding='utf-8-sig') as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=final_fieldnames) + writer.writeheader() + writer.writerows(csv_rows) + + logging.info(f"\n=== 全部处理完成!最佳结果已成功保存到: {output_csv_path} ===") + except IOError as e: + logging.error(f"无法写入 CSV 文件: {output_csv_path}。错误: {e}") + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="MMSegmentation 最终指标提取脚本 (V2)") + parser.add_argument( + '--input_dir', + type=str, + default='../Hardisk', + help="包含数据集输出文件夹 (例如 '..._outputs-MMSeg') 的根目录。" + ) + parser.add_argument( + '--output_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="用于存储所有分析结果的根目录。" + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/4_3_predict_draw_pictures_and_tabels.py b/Seg_All_In_One_MMSeg/My_All_In_One/4_3_predict_draw_pictures_and_tabels.py new file mode 100644 index 0000000..318b99c --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/4_3_predict_draw_pictures_and_tabels.py @@ -0,0 +1,249 @@ +import os +import glob +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns +import re + +def get_model_family(model_name): + """ + 根据模型名称提取模型族。 + 例如: 'my_bisenetv1_r50' -> 'my_bisenetv1' + 'my_fast_scnn' -> 'my_fast_scnn' + """ + # 使用正则表达式匹配,将 _rXX 或 _dXX 等后缀去掉 + match = re.match(r'^(.*?)_r\d+$', model_name) + if match: + return match.group(1) + return model_name + +def select_dataset(results_dir): + """ + 扫描目录,让用户交互式选择一个数据集。 + """ + print("正在扫描可用的数据集...") + try: + all_dataset_dirs = sorted([ + d for d in glob.glob(os.path.join(results_dir, '*_outputs-MMSeg')) if os.path.isdir(d) + ]) + except Exception as e: + print(f"扫描目录 '{results_dir}' 时出错: {e}") + return None, None + + if not all_dataset_dirs: + print(f"在 '{results_dir}' 中未找到任何数据集目录 (以 '_outputs-MMSeg' 结尾)。") + print("请确保脚本与 'BestMode_Predict_Results_DataSet_Public' 文件夹在同一级目录下。") + return None, None + + print("\n请选择要可视化的数据集:") + for i, dir_path in enumerate(all_dataset_dirs): + dataset_name = os.path.basename(dir_path).replace('_outputs-MMSeg', '') + print(f" [{i+1}] {dataset_name}") + + while True: + try: + choice = input(f"\n请输入选项编号 (1-{len(all_dataset_dirs)}): ") + choice_idx = int(choice) - 1 + if 0 <= choice_idx < len(all_dataset_dirs): + selected_dir = all_dataset_dirs[choice_idx] + dataset_name = os.path.basename(selected_dir).replace('_outputs-MMSeg', '') + return selected_dir, dataset_name + else: + print("无效的选项,请输入列表中的编号。") + except (ValueError, IndexError): + print("无效的输入,请输入一个数字编号。") + except (KeyboardInterrupt, EOFError): + print("\n操作已取消。") + return None, None + +def plot_performance_speed(selected_dir, dataset_name): + """ + 根据选定的数据集目录,加载数据并生成图表。 + """ + print(f"\n正在为数据集 '{dataset_name}' 生成图表...") + + # 构建文件路径 + metrics_file = os.path.join(selected_dir, f"{dataset_name}_metrics_summary_wide.csv") + fps_file = os.path.join(selected_dir, f"{dataset_name}_flops_params_fps_summary.csv") + + # 检查文件是否存在 + if not os.path.exists(metrics_file) or not os.path.exists(fps_file): + print(f"错误: 在目录 '{selected_dir}' 中缺少所需的数据文件。") + print(f" - 检查是否存在: {os.path.basename(metrics_file)}") + print(f" - 检查是否存在: {os.path.basename(fps_file)}") + return + + # 加载数据 + try: + metrics_df = pd.read_csv(metrics_file) + # 只保留最新的epoch结果,避免重复 + metrics_df = metrics_df.sort_values('Epoch', ascending=False).drop_duplicates('Algorithm') + + fps_df = pd.read_csv(fps_file) + except FileNotFoundError as e: + print(f"错误: 无法找到文件 {e.filename}") + return + except Exception as e: + print(f"读取CSV文件时出错: {e}") + return + + # 合并两个DataFrame + # metrics_df中的'Algorithm'列对应fps_df中的'Model'列 + merged_df = pd.merge(metrics_df, fps_df, left_on='Algorithm', right_on='Model') + + if merged_df.empty: + print("错误: 数据合并失败。请检查 'Algorithm' 和 'Model' 列中的模型名称是否匹配。") + return + + # 调用新函数来创建并保存摘要表格 + T1_create_and_save_summary_table(merged_df, selected_dir, dataset_name) + + # 调用新函数来提取和保存所有IoU数据 + T2_extract_and_save_iou_data(metrics_df, selected_dir, dataset_name) + + # 提取模型族 + merged_df['Family'] = merged_df['Model'].apply(get_model_family) + + # --- 绘图 --- + plt.style.use('seaborn-v0_8-whitegrid') + fig, ax = plt.subplots(figsize=(16, 10)) + + # 定义颜色和标记 + families = sorted(merged_df['Family'].unique()) + palette = sns.color_palette("husl", len(families)) + markers = ['o', 's', 'X', 'D', '^', 'P', '*', 'v', '<', '>'] + + # 循环绘制每个模型族 + for i, family in enumerate(families): + family_df = merged_df[merged_df['Family'] == family].sort_values('Average_FPS') + color = palette[i] + marker = markers[i % len(markers)] + + # 绘制散点 + ax.scatter(family_df['Average_FPS'], family_df['mIoU'], + color=color, marker=marker, s=150, label=family, zorder=3) + + # 如果族内有多个模型,则用线连接 + if len(family_df) > 1: + ax.plot(family_df['Average_FPS'], family_df['mIoU'], + color=color, linestyle='--', linewidth=1.5, zorder=2) + + # 在每个点旁边添加模型全名注释 + for j, row in family_df.iterrows(): + ax.text(row['Average_FPS'] * 1.01, row['mIoU'], row['Model'], + fontsize=9, verticalalignment='center') + + # 设置图表属性 + ax.set_title(f'Model Performance vs. Inference Speed ({dataset_name})', fontsize=18, pad=20) + ax.set_xlabel('Inference Speed (FPS)', fontsize=14) + ax.set_ylabel('Mean IoU (%)', fontsize=14) + ax.legend(title='Model Family', bbox_to_anchor=(1.02, 1), loc='upper left', borderaxespad=0.) + + plt.tight_layout(rect=[0, 0, 0.88, 1]) # 调整布局为图例留出空间 + plt.grid(True, which='both', linestyle='--', linewidth=0.5) + + # 保存并显示图表 + output_filename_png = f"F1_{dataset_name}_mIoU_vs_FPS.png" + save_file_path_png = os.path.join(selected_dir, output_filename_png) + plt.savefig(save_file_path_png, dpi=600) + output_filename_svg = f"F1_{dataset_name}_mIoU_vs_FPS.svg" + save_file_path_svg = os.path.join(selected_dir, output_filename_svg) + plt.savefig(save_file_path_svg) + print(f"\n图表已成功生成并保存为: {save_file_path_svg} 和 {save_file_path_png}") + plt.show() + +def T1_create_and_save_summary_table(merged_df, output_dir, dataset_name): + """ + 根据合并后的数据创建、格式化并保存性能摘要表格。 + + Args: + merged_df (pd.DataFrame): 包含所有模型指标和性能数据的DataFrame。 + output_dir (str): 保存CSV文件的目标目录。 + dataset_name (str): 数据集的名称,用于生成文件名。 + """ + print("正在创建摘要表格...") + + # 检查所需列是否存在 + required_columns = ['Model', 'mIoU', 'mAcc', 'aAcc', 'Average_FPS', 'FLOPs', 'Params'] + if not all(col in merged_df.columns for col in required_columns): + print("错误: DataFrame中缺少必要的列。请检查CSV文件内容。") + return + + # 提取并复制数据,避免修改原始DataFrame + summary_df = merged_df[required_columns].copy() + + # 清理和转换数据 + # 将 '118 G' -> 118.0 + summary_df['FLOPs'] = summary_df['FLOPs'].astype(str).str.replace(' G', '', regex=False).astype(float) + # 将 '13.274M' -> 13.274 + summary_df['Params'] = summary_df['Params'].astype(str).str.replace('M', '', regex=False).astype(float) + + # 按照用户的要求重命名列 + summary_df.rename(columns={ + 'Average_FPS': 'FPS', + 'FLOPs': 'G(GFLOPS)', + 'Params': 'Para(Params)' + }, inplace=True) + + # 按 mIoU 降序排序 + summary_df = summary_df.sort_values(by='mIoU', ascending=False) + + # 保存表格到CSV文件 + summary_filename = f"T1_{dataset_name}_performance_summary.csv" + summary_save_path = os.path.join(output_dir, summary_filename) + + try: + summary_df.to_csv(summary_save_path, index=False, float_format='%.3f') + print(f"摘要表格已成功保存到: {summary_save_path}") + except Exception as e: + print(f"保存摘要表格时出错: {e}") + +def T2_extract_and_save_iou_data(metrics_df, output_dir, dataset_name): + """ + 从 metrics DataFrame 中提取所有 mIoU 和 Class_IoU,并保存到新的CSV文件。 + + Args: + metrics_df (pd.DataFrame): 包含所有指标的原始DataFrame。 + output_dir (str): 保存CSV文件的目标目录。 + dataset_name (str): 数据集的名称,用于生成文件名。 + """ + print("正在提取所有 mIoU 和 Class_IoU 数据...") + + # 检查'Algorithm'列是否存在 + if 'Algorithm' not in metrics_df.columns: + print("错误: 'Algorithm' 列未找到,无法继续。") + return + + # 找出所有与IoU相关的列 + # 包括 'mIoU' 以及所有以 '_IoU' 结尾的列 + iou_columns = ['Algorithm', 'mIoU'] + [col for col in metrics_df.columns if col.endswith('_IoU') and col != 'mIoU'] + + # 移除重复的列名(以防万一) + iou_columns = list(dict.fromkeys(iou_columns)) + + # 提取数据 + iou_df = metrics_df[iou_columns].copy() + + # 按 mIoU 降序排序,便于查看 + iou_df = iou_df.sort_values(by='mIoU', ascending=False) + + # 定义并保存文件 + iou_filename = f"T2_{dataset_name}_all_iou_summary.csv" + iou_save_path = os.path.join(output_dir, iou_filename) + + try: + iou_df.to_csv(iou_save_path, index=False, float_format='%.2f') + print(f"所有IoU数据已成功保存到: {iou_save_path}") + except Exception as e: + print(f"保存IoU数据时出错: {e}") + +if __name__ == '__main__': + # 设置包含所有数据集结果的根目录 + results_root_dir = '../BestMode_Predict_Results_DataSet_Public' + + # 启动交互式选择 + selected_directory, selected_dataset_name = select_dataset(results_root_dir) + + # 如果用户成功选择,则生成图表 + if selected_directory and selected_dataset_name: + plot_performance_speed(selected_directory, selected_dataset_name) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/4_4_extract_loss_and_best_miou.py b/Seg_All_In_One_MMSeg/My_All_In_One/4_4_extract_loss_and_best_miou.py new file mode 100644 index 0000000..8e07899 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/4_4_extract_loss_and_best_miou.py @@ -0,0 +1,413 @@ +import os +import glob +import logging +import argparse +import re +import csv +from typing import Dict, Optional, List +from collections import defaultdict + +# --- 新增导入:用于绘图 --- +try: + import pandas as pd + import matplotlib.pyplot as plt + MATPLOTLIB_AVAILABLE = True +except ImportError: + MATPLOTLIB_AVAILABLE = False + logging.warning( + "未找到 'pandas' 或 'matplotlib' 库。" + "脚本将只生成CSV文件,无法自动绘图。" + "请运行 'pip install pandas matplotlib' 来安装它们。" + ) +# --- 导入结束 --- + + +# --- 配置日志记录 --- +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# --- 辅助函数 (与 4_3 版本相同) --- + +def find_all_log_files_sorted(algorithm_dir: str) -> List[str]: + """ + 查找给定算法目录中所有的.log文件,并按从新到旧的顺序排列。 + """ + try: + subdirs = [d for d in os.listdir(algorithm_dir) if os.path.isdir(os.path.join(algorithm_dir, d))] + except FileNotFoundError: + logging.error(f"算法目录不存在: {algorithm_dir}") + return [] + + if not subdirs: + logging.warning(f"在目录 {algorithm_dir} 中未找到任何时间戳子目录。") + return [] + + sorted_subdirs = sorted(subdirs, reverse=True) + log_files = [] + for subdir_name in sorted_subdirs: + subdir_path = os.path.join(algorithm_dir, subdir_name) + logs_in_subdir = glob.glob(os.path.join(subdir_path, '*.log')) + if logs_in_subdir: + log_files.append(logs_in_subdir[0]) + + return log_files + +def get_max_epochs(config_path: str) -> Optional[int]: + """ + 从config.py文件中解析train_cfg字典以获取max_epochs的值。 + """ + if not os.path.exists(config_path): + logging.error(f"配置文件不存在: {config_path}") + return None + + try: + with open(config_path, 'r', encoding='utf-8') as f: + content = f.read() + + match = re.search(r"train_cfg\s*=\s*dict\(.*?max_epochs\s*=\s*(\d+),.*?\)", content, re.DOTALL) + + if match: + max_epochs = int(match.group(1)) + logging.info(f"从 {os.path.basename(config_path)} 中成功读取 max_epochs: {max_epochs}") + return max_epochs + else: + logging.warning(f"在 {config_path} 中未找到 'max_epochs'。") + return None + except Exception as e: + logging.error(f"解析 {config_path} 时出错: {e}") + return None + +def parse_log_file_data(log_path: str, max_epochs: int) -> Dict: + """ + 解析日志文件,提取所有训练损失和所有验证mIoU。 + """ + try: + with open(log_path, 'r', encoding='utf-8') as f: + content = f.read() + except IOError as e: + logging.error(f"无法读取日志文件: {log_path}。错误: {e}") + return {'training_losses': {}, 'validation_mious': []} + + training_losses_by_epoch = defaultdict(list) + validation_mious = [] + + # 1. 提取训练损失 + total_iters_match = re.search(r"Iter\(train\)\s*\[\s*(\d+)\s*/\s*(\d+)\]", content) + total_iters = 0 + if total_iters_match: + try: + total_iters = int(total_iters_match.group(2)) + except ValueError: + pass + + if total_iters > 0: + train_loss_pattern = re.compile( + r"Iter\(train\)\s*\[\s*(\d+)\s*/\s*(\d+)\]" + r"(?:.*?)loss:\s*([\d\.]+)" + ) + for match in re.finditer(train_loss_pattern, content): + try: + current_iter = int(match.group(1)) + loss = float(match.group(3)) + epoch = int((current_iter / total_iters) * max_epochs) + training_losses_by_epoch[epoch].append(loss) + except (ValueError, ZeroDivisionError) as e: + logging.warning(f"解析训练损失时出错: {e}") + else: + logging.warning(f"在 {os.path.basename(log_path)} 中未找到有效的 'Iter(train)' 行来确定总迭代次数。") + + # 2. 提取验证 mIoU + val_summary_pattern = re.compile( + r"Iter\(val\) \[\d+/\d+\]\s+aAcc:\s*[\d.]+\s+mIoU:\s*([\d.]+)\s+mAcc:\s*[\d.]+" + ) + for match in re.finditer(val_summary_pattern, content): + try: + miou = float(match.group(1)) + validation_mious.append(miou) + except ValueError: + pass + + log_name = os.path.basename(log_path) + if training_losses_by_epoch: + logging.info(f"✅ 从 {log_name} 提取了 {len(training_losses_by_epoch)} 个Epoch的训练损失。") + if validation_mious: + logging.info(f"✅ 从 {log_name} 提取了 {len(validation_mious)} 次验证的mIoU。") + if not training_losses_by_epoch and not validation_mious: + logging.warning(f"❌ 在 {log_name} 中未找到训练损失或验证mIoU。") + + return { + 'training_losses': training_losses_by_epoch, + 'validation_mious': validation_mious + } + +# --- 新增:从 4_4 脚本中合并过来的绘图函数 --- + +def plot_loss_curves(csv_path: str): + """ + 读取_training_loss_summary.csv文件并绘制训练损失曲线。 + (此版本已根据用户需求修改) + + Args: + csv_path (str): 输入的CSV文件路径。 + """ + if not MATPLOTLIB_AVAILABLE: + logging.warning("由于缺少 'pandas' 或 'matplotlib',跳过绘图。") + return + + if not os.path.exists(csv_path): + logging.error(f"[绘图] 文件未找到: {csv_path}") + return + + logging.info(f"[绘图] 正在读取数据: {os.path.basename(csv_path)}") + try: + df = pd.read_csv(csv_path) + except Exception as e: + logging.error(f"[绘图] 读取CSV时出错: {e}") + return + + try: + df = df.set_index('Algorithm') + except KeyError: + logging.error("[绘图] CSV文件中未找到 'Algorithm' 列。") + return + + loss_cols = [col for col in df.columns if col.startswith('Epoch_') and col.endswith('_Loss')] + + if not loss_cols: + logging.warning("[绘图] 在CSV中未找到任何 'Epoch_X_Loss' 列。") + return + + try: + epochs = [int(col.split('_')[1]) for col in loss_cols] + except (ValueError, IndexError) as e: + logging.error(f"[绘图] 解析Epoch列名时出错: {e}。列名格式应为 'Epoch_N_Loss'。") + return + + df_losses = df[loss_cols].apply(pd.to_numeric, errors='coerce') + + # --- 开始绘图 --- + logging.info("[绘图] 正在生成图表...") + + fig, ax = plt.subplots(figsize=(14, 8)) + + for alg_name, row in df_losses.iterrows(): + # --- 修改点 1 --- + # 移除 marker 和 markersize,使用实线 (linestyle='-') + ax.plot(epochs, row.values, label=alg_name, linestyle='-') + + # --- 设置图表样式 --- + ax.set_xlabel('Epoch', fontsize=12) + ax.set_ylabel('Average Training Loss', fontsize=12) + ax.set_title(f'Training Loss per Epoch\n(Source: {os.path.basename(csv_path)})', fontsize=14) + ax.grid(True, linestyle=':', alpha=0.7) + + # --- 修改点 2 --- + # 将Y轴(纵轴)的范围设置为 0 到 5 + ax.set_ylim(bottom=0, top=5) # TODO TODO + + # 将图例放在图表右侧外部 + ax.legend(bbox_to_anchor=(1.04, 1), loc='upper left', title="Algorithms") + + fig.tight_layout(rect=[0, 0, 0.85, 1]) + + # --- 保存图表 --- + output_png_path = os.path.splitext(csv_path)[0] + '.png' + try: + plt.savefig(output_png_path, dpi=150, bbox_inches='tight') + logging.info(f"✅ 图表已成功保存到: {output_png_path}") + except IOError as e: + logging.error(f"[绘图] 保存图像时出错: {e}") + finally: + plt.close(fig) # 释放内存 + +# --- 主函数 (小幅修改以调用绘图) --- + +def main(args): + """ + 脚本主入口,负责编排整个自动化分析流程。 + """ + input_root = args.input_dir + output_root = args.output_dir + + if not os.path.isdir(input_root): + logging.error(f"输入目录不存在: {input_root}") + return + + # --- 交互式菜单 (保持不变) --- + all_dataset_dirs = sorted([ + d for d in glob.glob(os.path.join(input_root, '*_outputs-MMSeg')) if os.path.isdir(d) + ]) + if not all_dataset_dirs: + logging.error(f"在输入目录 {input_root} 中未找到任何有效的数据集文件夹。") + return + dataset_map = {str(i + 1): path for i, path in enumerate(all_dataset_dirs)} + print("\n" + "="*50) + print("--- 步骤 1: 请选择要处理的数据集 ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + choice1 = input("请输入数据集编号并按回车键: ").strip() + + model_dirs = [] + selected_dataset_dir = None + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"您已选择数据集: [{os.path.basename(selected_dataset_dir)}]") + alg_dirs = sorted([ + d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d) + ]) + if not alg_dirs: + logging.warning(f"在 {os.path.basename(selected_dataset_dir)} 中未发现任何算法子文件夹。") + return + alg_map = {str(i + 1): path for i, path in enumerate(alg_dirs)} + print("\n" + "="*50) + print("--- 步骤 2: 请选择要处理的算法 ---") + print("0: 批量处理当前数据集下的【全部】算法") + for key, path in alg_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + choice2 = input("请输入算法编号 (或输入 '0' 处理全部) 并按回车键: ").strip() + if choice2 == '0': + model_dirs = alg_dirs + logging.info(f"您选择了批量处理全部 {len(model_dirs)} 个算法。") + elif choice2 in alg_map: + model_dirs = [alg_map[choice2]] + logging.info(f"您选择了处理单个算法: {os.path.basename(model_dirs[0])}") + else: + logging.error("无效的算法选择,程序已退出。") + return + else: + logging.error("无效的数据集选择,程序已退出。") + return + # --- 交互式菜单结束 --- + + + # --- 处理逻辑 (保持不变) --- + csv_rows = [] + + for model_dir in model_dirs: + model_name = os.path.basename(model_dir) + logging.info(f"\n--- 开始处理算法: {model_name} ---") + + all_logs_sorted = find_all_log_files_sorted(model_dir) + + if not all_logs_sorted: + logging.warning(f"跳过算法 {model_name},因为未找到任何日志文件。") + continue + + latest_log_path = all_logs_sorted[0] + config_path = os.path.join(os.path.dirname(latest_log_path), 'vis_data', 'config.py') + max_epochs = get_max_epochs(config_path) + if max_epochs is None: + logging.error(f"无法为算法 {model_name} 找到 max_epochs,将跳过。") + continue + + all_losses_for_model = defaultdict(list) + all_mious_for_model = [] + + for log_file_path in all_logs_sorted: + logging.info(f"正在解析: {os.path.relpath(log_file_path)}") + parsed_data = parse_log_file_data(log_file_path, max_epochs) + for epoch, losses in parsed_data['training_losses'].items(): + all_losses_for_model[epoch].extend(losses) + if parsed_data['validation_mious']: + all_mious_for_model.extend(parsed_data['validation_mious']) + + if not all_losses_for_model and not all_mious_for_model: + logging.warning(f"❌❌❌跳过算法 {model_name},因为在其所有日志文件中都未能找到有效的训练或验证数据。❌❌❌") + continue + + best_miou = 0.0 + if all_mious_for_model: + try: + best_miou = max(all_mious_for_model) + logging.info(f"找到了最佳 mIoU: {best_miou:.4f}") + except (ValueError, TypeError) as e: + logging.error(f"为算法 {model_name} 寻找最佳mIoU时出错: {e}") + else: + logging.warning(f"算法 {model_name} 没有找到任何mIoU数据。") + + short_model_name = model_name.split('Alg_', 1)[1] if 'Alg_' in model_name else model_name + row_data = { + 'Algorithm': short_model_name + } + + sorted_epochs = sorted(all_losses_for_model.keys()) + if not sorted_epochs: + logging.warning(f"算法 {model_name} 没有找到任何训练损失数据。") + + for epoch in sorted_epochs: + losses = all_losses_for_model[epoch] + avg_loss = sum(losses) / len(losses) + row_data[f'Epoch_{epoch}_Loss'] = f"{avg_loss:.4f}" + + row_data['Best_mIoU'] = f"{best_miou:.4f}" + csv_rows.append(row_data) + # --- 处理逻辑结束 --- + + + # --- 写入 CSV 文件 (修改后) --- + if not csv_rows: + logging.info("没有成功获取任何模型的统计数据,不生成 CSV 文件。") + return + + # 动态生成所有列名 + all_fieldnames_set = set() + for row in csv_rows: + all_fieldnames_set.update(row.keys()) + + base_fields = ['Algorithm'] + miou_field = ['Best_mIoU'] + loss_fields = [f for f in all_fieldnames_set if f.startswith('Epoch_')] + + try: + loss_fields.sort(key=lambda x: int(x.split('_')[1])) + except (ValueError, IndexError): + logging.error("排序Epoch列名时出错,将按字母顺序排序。") + loss_fields.sort() + + final_fieldnames = base_fields + loss_fields + miou_field + + final_output_dir = os.path.join(output_root, os.path.basename(selected_dataset_dir)) + os.makedirs(final_output_dir, exist_ok=True) + dataset_name = os.path.basename(selected_dataset_dir).split('_outputs-MMSeg')[0] + output_csv_path = os.path.join(final_output_dir, f'{dataset_name}_training_loss_summary.csv') + + try: + with open(output_csv_path, 'w', newline='', encoding='utf-8-sig') as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=final_fieldnames, extrasaction='ignore') + writer.writeheader() + writer.writerows(csv_rows) + + logging.info(f"\n=== CSV文件已成功保存到: {output_csv_path} ===") + + # --- 新增:调用绘图函数 --- + # 仅在CSV成功写入后才尝试绘图 + plot_loss_curves(output_csv_path) + # --- 新增结束 --- + + except IOError as e: + logging.error(f"无法写入 CSV 文件: {output_csv_path}。错误: {e}") + except Exception as e_plot: + # 捕获绘图时可能发生的其他错误 + logging.error(f"在主流程中调用绘图时出错: {e_plot}") + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description="MMSegmentation 训练损失提取与绘图脚本 (V3-Integrated)" + ) + parser.add_argument( + '--input_dir', + type=str, + default='../Hardisk', + help="包含数据集输出文件夹 (例如 '..._outputs-MMSeg') 的根目录。" + ) + parser.add_argument( + '--output_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="用于存储所有分析结果 (CSV和PNG) 的根目录。" + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_0_base_.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_0_base_.py new file mode 100644 index 0000000..5b47b56 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_0_base_.py @@ -0,0 +1,28 @@ +from .Initial_Alg_Gen_Tool import get_var_from_file + +# 生成 _base_ 变量,传入算法配置、数据集配置、schedule配置 +def generate_base_config(alg_file_name, dataset_file_name, schedule_file_name): + if alg_file_name != None: + base_config =[ + f'../_base_/models/{alg_file_name}.py', + f'../_base_/datasets/{dataset_file_name}.py', #换成自己定义的数据集 + f'../_base_/default_runtime.py', + f'../_base_/schedules/{schedule_file_name}.py' + ] + else: + base_config =[ + f'../_base_/datasets/{dataset_file_name}.py', #换成自己定义的数据集 + f'../_base_/default_runtime.py', + f'../_base_/schedules/{schedule_file_name}.py' + ] + + return base_config + +if __name__ == '__main__': + # 示例用法 + alg_file_name = 'ann_r50-d8' # 算法根文件 + dataset_file_name = 'my_dataset_model' # 数据文件 + schedule_file_name = 'schedule_4k_check_400' # schedule文件 + + _base_ = generate_base_config(alg_file_name=alg_file_name, dataset_file_name=dataset_file_name, schedule_file_name=schedule_file_name) + print(_base_) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_1_norm_cfg.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_1_norm_cfg.py new file mode 100644 index 0000000..4c2f2d0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_1_norm_cfg.py @@ -0,0 +1,19 @@ +from .Initial_Alg_Gen_Tool import get_var_from_file + +# 单卡 norm_cfg = dict(type='BN') +# 多卡 norm_cfg = dict(type='SyncBN') +def generate_norm_cfg(GPU_num = 2): + GPU_num = int(GPU_num) + if GPU_num == 1: + return dict(type='BN') + elif GPU_num > 1: + return dict(type='SyncBN') + else: + raise ValueError("GPU_num需要为大于等于1的整数数值") + +if __name__ == '__main__': + # 示例用法 + GPU_num = 1 + + norm_cfg = generate_norm_cfg(GPU_num=GPU_num) + print(norm_cfg) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_2_data_preprocessor.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_2_data_preprocessor.py new file mode 100644 index 0000000..c3fa34d --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_2_data_preprocessor.py @@ -0,0 +1,23 @@ +from .Initial_Alg_Gen_Tool import get_var_from_file + +# crop_size 数据预处理是分割大小 +# crop_size = (512,512) +def generate_data_preprocessor(crop_size=None, mean=None, std=None, bgr_to_rgb=False): + + data_preprocessor = {} + if crop_size != None: + data_preprocessor['size']=crop_size + if mean != None: + data_preprocessor['mean']=mean + if std != None: + data_preprocessor['std']=std + if bgr_to_rgb != None: + data_preprocessor['bgr_to_rgb']=bgr_to_rgb + return data_preprocessor + +if __name__ == '__main__': + # 示例用法 + crop_size = (512,512) + + data_preprocessor = generate_data_preprocessor(crop_size = crop_size) + print(data_preprocessor) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_3_model.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_3_model.py new file mode 100644 index 0000000..c91403e --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_3_model.py @@ -0,0 +1,119 @@ +from .Initial_Alg_Gen_Tool import get_var_from_file, format_dict + +# 1. 修改pretrained +# pretrained_pth = './My_Local_Model/open_mmlab/resnet50_v1c.pth') +def generate_model_pretrained(pretrained_pth=None): + pretrained = pretrained_pth + if pretrained_pth == None: + return None + return pretrained + +# 2. 修改backbone +# depth=50 +def generate_model_backbone(depth=None): + backbone = {} + if depth != None: + backbone['depth']=depth + return backbone + +# 3. 修改model_data_preprocessor +# model_data_preprocessor='data_preprocessor' +def generate_model_data_preprocessor(model_data_preprocessor=None): + if model_data_preprocessor != None: + model_data_preprocessor = model_data_preprocessor + return model_data_preprocessor + +# 4. 修改decode_head +# num_classes='36' # 分割数 +# decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # 直接传入dict +# align_corners=False是否需要角对齐 +def generate_model_decode_head(num_classes=None, decode_head_loss_decode_dict=None, align_corners=None): + decode_head = {} + if num_classes != None: + decode_head['num_classes']=num_classes + if decode_head_loss_decode_dict != None: + decode_head['loss_decode']=decode_head_loss_decode_dict + if align_corners != None: + decode_head['align_corners']=align_corners + + return decode_head + +# 5. 修改auxiliary_head +# num_classes='36' # 分割数 +# auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) # 直接传入dict +# align_corners=False是否需要角对齐 +def generate_model_auxiliary_head(num_classes=None, auxiliary_head_loss_decode_dict=None, align_corners=None): + auxiliary_head = {} + if num_classes != None: + auxiliary_head['num_classes']=num_classes + if auxiliary_head_loss_decode_dict != None: + auxiliary_head['loss_decode']=auxiliary_head_loss_decode_dict + if align_corners != None: + auxiliary_head['align_corners']=align_corners + + return auxiliary_head + +# 6. 修改train_cfg +def generate_model_train_cfg(): + train_cfg = {} + + return train_cfg + +# 6. 修改test_cfg +# mode='slide' +# crop_size = (767,767) +# test_cfg_crop_div_stride = 1.5 +def generate_model_test_cfg(test_cfg_mode=None, crop_size=None, test_cfg_crop_div_stride=None): + test_cfg = {} + if test_cfg_mode != None: + test_cfg['mode'] = test_cfg_mode + if crop_size != None: + test_cfg['crop_size'] = crop_size + if test_cfg_crop_div_stride != None: + test_cfg['stride'] = (int(crop_size[0]/test_cfg_crop_div_stride), int(crop_size[1]/test_cfg_crop_div_stride)) + + return test_cfg + +if __name__ == '__main__': + # 1. 修改pretrained + pretrained_pth = './My_Local_Model/open_mmlab/resnet50_v1c.pth' + pretrained = generate_model_pretrained(pretrained_pth = pretrained_pth) + + # 2. 修改backbone + depth = 50 + backbone = generate_model_backbone(depth=depth) + + # 3. 修改data_preprocessor + model_data_preprocessor = 'data_preprocessor' + model_data_preprocessor = generate_model_data_preprocessor(model_data_preprocessor=model_data_preprocessor) + + # 4.5. 修改decode_head、auxiliary_head + num_classes='36' # 分割数 + decode_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0) # 直接传入dict + align_corners=False # 是否需要角对齐 + auxiliary_head_loss_decode_dict=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4) + + decode_head = generate_model_decode_head(num_classes=num_classes, decode_head_loss_decode_dict=decode_head_loss_decode_dict, align_corners=align_corners) + auxiliary_head = generate_model_auxiliary_head(num_classes=num_classes, auxiliary_head_loss_decode_dict=auxiliary_head_loss_decode_dict, align_corners=align_corners) + + # 6. 修改train_cfg + train_cfg = generate_model_train_cfg() + + # 6. 修改test_cfg + mode='slide' + crop_size = (767,767) + test_cfg_crop_div_stride = 1.5 + test_cfg = generate_model_test_cfg(mode=mode, crop_size=crop_size, test_cfg_crop_div_stride=test_cfg_crop_div_stride) + + # 汇总为model + model = dict( + pretrained = pretrained, + backbone = backbone, + data_preprocessor = model_data_preprocessor, + decode_head = decode_head, + auxiliary_head = auxiliary_head, + train_cfg = train_cfg, + test_cfg = test_cfg, + ) + model_ = format_dict(model) + print(model_) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_4_optimizer.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_4_optimizer.py new file mode 100644 index 0000000..61552a1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_4_optimizer.py @@ -0,0 +1,173 @@ +# optimizer(优化器设计)TODO +# type_of_back_bone = "Vit" +def generate_optim_wrapper(type_of_back_bone=None): + + # optim_wrapper 配置-1 + optim_wrapper_1 = { + 'type': 'OptimWrapper', # 表示这是一个优化器包装器(OptimWrapper) + '_delete_': True, # 通常用于在继承配置时删除旧的优化器配置,替换为新的优化器配置 + 'optimizer': { + 'type': 'AdamW', # 优化器类型为 AdamW + 'lr': 0.0001, # 学习率,通常设定为一个较小的值 + 'weight_decay': 0.0005 # 权重衰减系数 + }, + 'clip_grad': { + 'max_norm': 1, # 梯度裁剪的最大范数 + 'norm_type': 2 # L2 范数 + } + } + # optim_wrapper 配置-2 + optim_wrapper_2 = { + 'type': 'OptimWrapper', # 表示这是一个优化器包装器(OptimWrapper) + '_delete_': True, # 通常用于在继承配置时删除旧的优化器配置,替换为新的优化器配置 + 'optimizer': { + 'type': 'SGD', # 优化器类型为 SGD + 'lr': 0.05, # 学习率 + 'weight_decay': 0.0005, # 权重衰减系数 + 'momentum': 0.9 + }, + 'clip_grad': { + 'max_norm': 1, # 梯度裁剪的最大范数 + 'norm_type': 2 # L2 范数 + } + } + + optim_wrapper_list = [optim_wrapper_1, optim_wrapper_2] + + # 打印所有可用的优化器选项 + while True: + print("请选择 optim_wrapper (按 Enter 使用默认):") + for i, scheduler in enumerate(optim_wrapper_list, 1): + optimizer_type = scheduler['optimizer']['type'] + learning_rate = scheduler['optimizer']['lr'] + print(f"{i}. Optimizer: {optimizer_type}, LR: {learning_rate}") + + # 如果是vit网络,则需要额外的paramwise_cfg + if type_of_back_bone != None and (type_of_back_bone.lower() == 'vit' or type_of_back_bone.lower() == 'visiontransformer'): + custom_keys={ + 'pos_embed': dict(decay_mult=0.), # 位置嵌入(positional embeddings)。decay_mult=0. 意味着对这些嵌入不应用权重衰减 + 'cls_token': dict(decay_mult=0.), # 是在某些模型(如 Transformer 或 BERT)中,用于分类任务的特定 token + 'norm': dict(decay_mult=0.) # 对归一化层的参数也禁用了权重衰减 + } + optim_wrapper_list[i-1]['paramwise_cfg'] = dict(custom_keys) + optim_wrapper_list[i-1]['_delete_'] = True + + if type_of_back_bone != None and (type_of_back_bone.lower() == 'swin'): + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + } + optim_wrapper_list[i-1]['paramwise_cfg'] = dict(custom_keys) + optim_wrapper_list[i-1]['_delete_'] = True + + choice = input(f"输入 1 到 {len(optim_wrapper_list)} 进行选择(默认 1): ") + + # 如果用户按下 Enter 或选择 1,默认返回 optim_wrapper_1 + if choice == '' or choice == '1': + return optim_wrapper_list[0] + elif choice.isdigit() and 1 <= int(choice) <= len(optim_wrapper_list): + return optim_wrapper_list[int(choice) - 1] + else: + print(f"无效输入,请输入 1 到 {len(optim_wrapper_list)} 或按 Enter 使用默认值") + +def generate_param_scheduler(train_type, train_time_or_epoch): + """ + 根据训练模式(epoch或iteration)动态生成并选择学习率调度器。 + + Args: + train_type (str): 训练模式, 'epoch' 或 'iteration'。 + train_time_or_epoch (int): 训练的总轮数(epochs)或总迭代次数(k)。 + """ + # 1. 根据训练模式确定核心参数 + if train_type.lower() == 'epoch': + by_epoch = True + end_value = train_time_or_epoch + warmup_end = 10 # 为epoch模式设置一个合理的10个epoch的预热期 + # 按比例调整MultiStepLR的milestones + milestones = [int(end_value * 0.75), int(end_value * 0.9)] + else: # 默认为 iteration 模式 + by_epoch = False + end_value = train_time_or_epoch * 1000 + warmup_end = 1500 # iteration模式使用原有的1500次迭代预热 + # MultiStepLR的milestones, 同样适配总时长 + milestones = [int(end_value * 0.75), int(end_value * 0.9)] + + # 2. 使用动态参数定义调度器模板 + # param_scheduler 配置-1 + param_scheduler_1 = [ + dict( + type='LinearLR', + start_factor=1e-6, + by_epoch=by_epoch, + begin=0, + end=warmup_end), + dict( + type='PolyLR', + power=0.9, + begin=warmup_end, + end=end_value, + eta_min=1e-5, + by_epoch=by_epoch) + ] + # param_scheduler 配置-2 + param_scheduler_2 = [ + dict( + type='LinearLR', + start_factor=1e-6, + by_epoch=by_epoch, + begin=0, + end=warmup_end), + dict( + type='PolyLR', + power=1.0, + begin=warmup_end, + end=end_value, + eta_min=0.0, + by_epoch=by_epoch) + ] + # param_scheduler 配置-3 + param_scheduler_3 = [ + dict( + type='LinearLR', + start_factor=0.001, + by_epoch=by_epoch, + begin=0, + end=warmup_end), + dict( + type='MultiStepLR', + begin=warmup_end, + end=end_value, + milestones=milestones, + by_epoch=by_epoch) + ] + param_scheduler_list = [param_scheduler_1, param_scheduler_2, param_scheduler_3] + + # 3. 打印并让用户选择 (这部分逻辑不变) + while True: + print("请选择 param_scheduler (学习率调度器):") + for i, schedulers in enumerate(param_scheduler_list, 1): + print(f"Scheduler {i}:") + for scheduler in schedulers: + # 提取字段并处理缺省情况 + scheduler_type = scheduler.get('type', '/') + begin = scheduler.get('begin', '/') + end = scheduler.get('end', '/') + power = scheduler.get('power', '/') + eta_min = scheduler.get('eta_min', '/') + milestones_val = scheduler.get('milestones', '/') + + # 根据类型决定显示哪些信息 + if scheduler_type == 'MultiStepLR': + print(f" - {scheduler_type}: begin={begin}, end={end}, milestones={milestones_val}") + else: + print(f" - {scheduler_type}: begin={begin}, end={end}, power={power}, eta_min={eta_min}") + + choice = input(f"输入 1 到 {len(param_scheduler_list)} 进行选择(默认 1): ").strip() + + if choice == '' or choice == '1': + return param_scheduler_list[0] + elif choice.isdigit() and 1 <= int(choice) <= len(param_scheduler_list): + return param_scheduler_list[int(choice) - 1] + else: + print(f"无效输入,请输入 1 到 {len(param_scheduler_list)} 或按 Enter 使用默认值\n") \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_5_train_dataloader.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_5_train_dataloader.py new file mode 100644 index 0000000..eb05545 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_5_train_dataloader.py @@ -0,0 +1,59 @@ +# train_dataloader TODO +def generate_train_dataloader(batch_size_default=None, num_workers_default=None): + batch_size = None + if batch_size_default != None: + while True: + user_input = input(f"请输入 batch size (默认为 {batch_size_default}): ") + + # 如果用户没有输入内容,使用默认值 + if not user_input.strip(): + batch_size = batch_size_default + break + + # 尝试将输入转换为整数 + try: + batch_size = int(user_input) + if batch_size > 0: + break # 输入正确,退出循环 + else: + print("Batch size 必须是正整数,请重新输入。") + except ValueError: + print("输入无效,请输入一个有效的整数。") + print(f"将train_dataloader的batch_size设置为{batch_size}") + + num_workers = None + if num_workers_default != None: + while True: + user_input = input(f"请输入 num workers (默认为 {num_workers_default}): ") + + # 如果用户没有输入内容,使用默认值 + if not user_input.strip(): + num_workers = num_workers_default + break + + # 尝试将输入转换为整数 + try: + num_workers = int(user_input) + if num_workers > 0: + break # 输入正确,退出循环 + else: + print("Num workers 必须是正整数,请重新输入。") + except ValueError: + print("输入无效,请输入一个有效的整数。") + print(f"将train_dataloader的num_workers设置为{num_workers}") + + # 返回包含 batch_size 的字典 + train_dataloader = {} + if num_workers == None: + train_dataloader['batch_size'] = batch_size + return train_dataloader, batch_size + + if batch_size == None: + train_dataloader['num_workers'] = num_workers + return train_dataloader, num_workers + + train_dataloader['batch_size'] = batch_size + train_dataloader['num_workers'] = num_workers + return train_dataloader, batch_size, num_workers + + diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_Tool.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_Tool.py new file mode 100644 index 0000000..45540c8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_Tool.py @@ -0,0 +1,264 @@ +import ast, subprocess, os + +# 从文件中获取特定变量信息,返回变量直和类型 +def get_var_from_file(filename, var_name="norm_cfg"): + with open(filename, 'r', encoding='utf-8') as file: + code = file.read() + + # 解析文件的 AST 树 + tree = ast.parse(code) + + # 遍历 AST 树,查找指定变量 + for node in ast.walk(tree): + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == var_name: + # 将 AST 节点转换为 Python 对象 + var_value = ast.literal_eval(node.value) + # 返回变量的值和类型 + return var_value, type(var_value) + + # 如果没有找到指定的变量,返回 None 和 None + return None, None + +def get_var_from_py_file(file_path, var_name="auxiliary_head"): + # 定义一个字典用于保存文件中的变量 + context = {} + + # 读取并执行文件 + with open(file_path, 'r') as file: + exec(file.read(), context) + + # 获取 auxiliary_head 变量 + if var_name in context: + return context[var_name] + else: + raise AttributeError(f"文件中没有找到 {var_name} 变量") + +# 更新list的dict变量 +def update_list_dict_var(var, var_new): + # 判断 var 和 var_new 的长度是否相等 + if len(var) != len(var_new): + raise ValueError(f"var {len(var)} 和 var_new {len(var_new)} 的大小不相等,无法更新") + + # 遍历 var_new,按索引更新 var + for i, new_entry in enumerate(var_new): + # 将 new_entry 中的键值覆盖 var[i] 中的相同键 + var[i].update(new_entry) + return var + +if __name__ == '__main__': + # 示例调用 + var_value, var_type = get_var_from_file('./configs/_base_/models', 'norm_cfg') + if var_value is not None: + print(f"变量名: norm_cfg\n值: {var_value}\n类型: {var_type}") + else: + print("未找到指定变量") + +# 将文件以dict格式输出 +def format_dict(d, indent_level=1): + formatted_items = [] + indent = ' ' * indent_level # 根据缩进级别生成空格 + + for key, value in d.items(): + if isinstance(value, dict): + # 如果值是字典,递归调用 format_dict_as_func 并增加缩进级别 + formatted_value = format_dict(value, indent_level + 1) + formatted_items.append(f"{indent}{key}={formatted_value}") + elif isinstance(value, str): + # 如果是字符串,格式化时加引号 + formatted_items.append(f"{indent}{key}='{value}'") + else: + # 其他类型(如数值等)不加引号 + formatted_items.append(f"{indent}{key}={value}") + + # 将所有键值对合成为多行的格式,并加上结尾逗号 + formatted_str = ",\n".join(formatted_items) + + # 返回更美观的 dict() 的格式,保留缩进和换行 + return f"dict(\n{formatted_str},\n{' ' * (indent_level - 1)})" + +# 所有格式正确输出 +def format_all_data_old(data, indent_level=0): + indent = ' ' * indent_level # 根据缩进级别生成空格 + if isinstance(data, dict): + formatted_items = [] + for key, value in data.items(): + formatted_value = format_all_data(value, indent_level + 1) + formatted_items.append(f"{indent} {key}={formatted_value}") + formatted_str = ",\n".join(formatted_items) + return f"dict(\n{formatted_str},\n{indent})" + + elif isinstance(data, list): + formatted_items = [f"{indent} {format_all_data(item, indent_level + 1)}" for item in data] + formatted_str = ",\n".join(formatted_items) + return f"[\n{formatted_str},\n{indent}]" + + elif isinstance(data, tuple): + return f"{data}" + + elif isinstance(data, str): + # 如果字符串包含单引号,则使用双引号,否则使用单引号 + if "'" in data: + return f'"{data}"' + else: + return f"'{data}'" + + else: + return str(data) + +# 所有格式正确输出,加入键值中带有"."的处理 +def format_all_data(data, indent_level=0): + indent = ' ' * indent_level # 根据缩进级别生成空格 + dot_key_items = [] # 用于存储带点号的键 + regular_items = [] # 用于存储普通键 + + if isinstance(data, dict): + for key, value in data.items(): + formatted_value = format_all_data(value, indent_level + 1) + # 如果键包含点号,收集到 dot_key_items 中 + if '.' in key: + dot_key_items.append(f"('{key}', {formatted_value})") + else: + regular_items.append(f"{indent} {key}={formatted_value}") + + # 生成带点号键的部分,并放在最前面 + if dot_key_items: + dot_key_str = f"{indent} [{', '.join(dot_key_items)}]" + else: + dot_key_str = "" + + # 生成普通键的部分 + regular_str = ",\n".join(regular_items) + + # 返回最终的组合字符串,带点号键放在普通键前面 + if dot_key_str: + return f"dict(\n{dot_key_str},\n{regular_str},\n{indent})" + else: + return f"dict(\n{regular_str},\n{indent})" + + elif isinstance(data, list): + formatted_items = [f"{indent} {format_all_data(item, indent_level + 1)}" for item in data] + formatted_str = ",\n".join(formatted_items) + return f"[\n{formatted_str},\n{indent}]" + + elif isinstance(data, tuple): + return f"{data}" + + elif isinstance(data, str): + # 如果字符串包含单引号,则使用双引号,否则使用单引号 + if "'" in data: + return f'"{data}"' + else: + return f"'{data}'" + + else: + return str(data) + +# # 批量将参数内容写入文件 # V1 传统版 +# def write_config_to_file(output_file, **kwargs): +# """ +# 将传入的任意数量的参数写入指定文件,并格式化输出。 + +# :param output_file: 要写入的文件路径 +# :param kwargs: 任意数量的配置项,格式为 key=value +# """ +# with open(output_file, 'w', encoding='utf-8') as file: +# # 遍历 kwargs,将每个 key, value 写入文件 +# for key, value in kwargs.items(): +# file.write(f"{key} = {format_all_data(value)}\n\n") + +# # 打印成功信息 +# print(f"\033[93m{output_file} file generated successfully\033[0m") + +# 批量将参数内容写入文件 # V2 加入训练过程可视化 TODO +def format_all_data(value): + """ + 一个辅助函数,用于将 Python 对象格式化为适合写入配置文件的字符串。 + 使用 repr() 可以确保字符串、列表、字典等都保持其 Python 语法格式。 + """ + return repr(value) +def write_config_to_file(output_file, **kwargs): + """ + 将传入的任意数量的参数以及一个自动生成的 visualizer 配置写入指定文件。 + + :param output_file: 要写入的文件路径 (e.g., 'configs/exp1.py') + :param kwargs: 任意数量的配置项,格式为 key=value + """ + # --- 1. 从 output_file 路径中提取实验名称 --- + # 首先获取基本文件名 (e.g., 'exp1.py') + base_name = os.path.basename(output_file) + # 然后去掉文件扩展名,得到纯净的实验名 (e.g., 'exp1') + experiment_name, _ = os.path.splitext(base_name) + + # --- 2. 构建 visualizer 配置字典 --- + vis_backends = [ + dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend'), + dict( + type='WandbVisBackend', + init_kwargs=dict( + project='Seg_MMSeg_Test', # 你的 wandb 项目名称 + name=experiment_name # 使用上面提取的文件名作为实验名 + ) + ) + ] + visualizer = dict( + name='visualizer', + type='SegLocalVisualizer', + vis_backends=vis_backends + ) + + # --- 3. 将自动生成的 visualizer 添加到要写入的内容中 --- + # 如果 kwargs 中已经有 'visualizer',它将会被新的配置覆盖 + kwargs['vis_backends'] = vis_backends + kwargs['visualizer'] = visualizer + + # --- 4. 将所有配置项写入文件 --- + with open(output_file, 'w', encoding='utf-8') as file: + # 遍历所有配置项(包括新加入的 visualizer),写入文件 + for key, value in kwargs.items(): + # 使用 format_all_data 保证格式正确 + file.write(f"{key} = {format_all_data(value)}\n\n") + + # 打印成功信息 + print(f"\033[93mConfiguration saved to '{output_file}' successfully.\033[0m") + print(f"\033[96mWandB experiment name will be: '{experiment_name}'\033[0m") + +# 获取系统中所有 GPU 的可用显存信息。 +def get_gpu_info(): + """ + 获取系统中所有 GPU 的可用显存信息。 + :return: GPU 显存信息的列表,列表中的每一项是一个 (GPU编号, 剩余显存) 元组 + """ + try: + # 使用 nvidia-smi 命令获取 GPU 显存信息 + result = subprocess.run( + ['nvidia-smi', '--query-gpu=index,memory.free', '--format=csv,noheader,nounits'], + stdout=subprocess.PIPE, + encoding='utf-8' + ) + + # 解析结果 + gpu_info = [] + lines = result.stdout.strip().split('\n') + for line in lines: + index, memory_free = line.split(',') + gpu_info.append((int(index.strip()), int(memory_free.strip()))) + + return gpu_info + except FileNotFoundError: + print("\033[91mError: nvidia-smi 命令未找到,请确保 NVIDIA 驱动正确安装。\033[0m") + return [] + +# 批量生成dict +def create_dict_by_kwargs(**kwargs): + # 批量生成字典,kwargs 会自动收集所有传入的命名参数 + return {key: value for key, value in kwargs.items() if value is not None} + +if __name__ == '__main__': + # 示例字典 + my_dict = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth'} + + # 打印成 dict() 的格式 + print(format_dict(my_dict)) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Select_Tool.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Select_Tool.py new file mode 100644 index 0000000..d6bb043 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Alg_Program/Initial_Alg_Select_Tool.py @@ -0,0 +1,236 @@ +# 选择crop_size大小 +def select_crop_size(predefined_options=[(512, 512), (512, 1024), (769, 769)]): + """ + 让用户选择裁剪大小。默认选择 (512, 512),用户可以选择其他预定义的裁剪大小, + 或者输入自定义的大小,且自定义的数字不能为负数。 + + :return: 选择的裁剪大小 (width, height) + """ + # 预定义的裁剪大小选项 + predefined_options = {str(i+1): option for i, option in enumerate(predefined_options)} + + # 显示可选的裁剪大小 + print("可用的裁剪大小选项:") + for key, value in predefined_options.items(): + print(f"{key}. {value}", end=" ") + print(f"{len(predefined_options)+1}. 自定义大小") + + # 用户选择 + choice = input("请选择裁剪大小选项 (默认 1): ").strip() + + # 如果用户没有输入,使用默认值 + if choice == "" or choice == "1": + return predefined_options["1"] + + # 如果用户选择了预定义的选项 + if choice in predefined_options: + return predefined_options[choice] + + # 如果用户选择自定义大小 + if choice == f"{len(predefined_options)+1}": + while True: + try: + width = int(input("请输入裁剪宽度 (正整数): ")) + height = int(input("请输入裁剪高度 (正整数): ")) + if width > 0 and height > 0: + return (width, height) + else: + print("宽度和高度必须是正整数。") + except ValueError: + print("输入无效,请输入有效的正整数。") + + # 如果用户输入无效,返回默认值 + print("无效选择,使用默认值 (512, 512)") + return predefined_options["1"] + +# 大字典,包含所有模型的信息 +pretrained_models_dict = { + "openmmlab/resnet18_v1c": {'pth': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'depth': 18, 'type':'ResNetV1c'}, + "openmmlab/resnet50_v1c": {'pth': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'depth': 50, 'type':'ResNetV1c'}, + "openmmlab/resnet101_v1c": {'pth': './My_Local_Model/open_mmlab/resnet101_v1c.pth', 'depth': 101, 'type':'ResNetV1c'}, + "torchvision://resnet18": {'pth': './My_Local_Model/torchvision_012/resnet18.pth', 'depth': 18, 'type':'ResNet'}, + "torchvision://resnet50": {'pth': './My_Local_Model/torchvision_012/resnet50.pth', 'depth': 50, 'type':'ResNet'}, + "torchvision://resnet101": {'pth': './My_Local_Model/torchvision_012/resnet101.pth', 'depth': 101, 'type':'ResNet'}, + "openmmlab/pidnet-s":{'pth': './My_Local_Model/open_mmlab/pidnet-s.pth', 'type':'pidnet', 'size':'small'}, + "openmmlab/pidnet-m":{'pth': './My_Local_Model/open_mmlab/pidnet-m.pth', 'type':'pidnet', 'size':'medium'}, + "openmmlab/pidnet-l":{'pth': './My_Local_Model/open_mmlab/pidnet-l.pth', 'type':'pidnet', 'size':'large'}, + "openmmlab/ddrnet23-s":{'pth': './My_Local_Model/open_mmlab/ddrnet23-s.pth', 'type':'ddrnet', 'size':'small'}, + "openmmlab/ddrnet23":{'pth': './My_Local_Model/open_mmlab/ddrnet23.pth', 'type':'ddrnet', 'size':'normal'}, + "openmmlab/stdc1":{'pth': './My_Local_Model/open_mmlab/stdc1.pth', 'type':'stdc', 'size':'V1'}, + "openmmlab/stdc2":{'pth': './My_Local_Model/open_mmlab/stdc2.pth', 'type':'stdc', 'size':'V2'}, + "pretrain/vit-b16_p16_224-80ecf9dd.pth":{'pth': './My_Local_Model/pretrain/vit-b16_p16_224-80ecf9dd.pth'}, + "pretrain/beit_base_patch16_224_pt22k_ft22k.pth":{'pth': './My_Local_Model/pretrain/beit_base_patch16_224_pt22k_ft22k.pth'}, + "pretrain/beit_large_patch16_224_pt22k_ft22k.pth":{'pth': './My_Local_Model/pretrain/beit_large_patch16_224_pt22k_ft22k.pth'}, + "pretrain/swin_large-d5bdebaf.pth":{'pth': './My_Local_Model/pretrain/swin_large_patch4_window7_224_22k_20220308-d5bdebaf.pth', 'type':'swin', 'size':'large'}, + "pretrain/swin_tiny-f41b89d3.pth":{'pth': './My_Local_Model/pretrain/swin_tiny_patch4_window7_224_20220308-f41b89d3.pth', 'type':'swin', 'size':'tiny'}, + "mae_pretrain_vit_base_mmcls.pth":{'pth': './My_Local_Model/pretrain/mae_pretrain_vit_base_mmcls.pth'}, + "open-mmlab://msra/hrnetv2_w18":{'pth': './My_Local_Model/open_mmlab/msra/hrnetv2_w18.pth'}, + "open-mmlab://msra/hrnetv2_w18_small":{'pth': './My_Local_Model/open_mmlab/msra/hrnetv2_w18_small.pth'}, + 'open-mmlab://msra/hrnetv2_w48':{'pth': './My_Local_Model/open_mmlab/msra/hrnetv2_w48.pth'}, + "pretrain/swin_large-6580f57d.pth":{'pth': './My_Local_Model/pretrain/swin_large_patch4_window12_384_22k_20220412-6580f57d.pth', 'type':'swin', 'size':'large'}, # mask2former + "pretrain/swin_base-e5c09f74.pth":{'pth': './My_Local_Model/pretrain/swin_base_patch4_window12_384_22k_20220317-e5c09f74.pth', 'type':'swin', 'size':'base'}, # mask2former + "pretrain/swin_small-7ba6d6dd.pth":{'pth': './My_Local_Model/pretrain/swin_small_patch4_window7_224_20220317-7ba6d6dd.pth', 'type':'swin', 'size':'small'}, # mask2former + "pretrain/swin_tiny-1cdeb081.pth":{'pth': './My_Local_Model/pretrain/swin_tiny_patch4_window7_224_20220317-1cdeb081.pth', 'type':'swin', 'size':'tiny'}, # mask2former + # 这里可以包含更多模型信息... +} + +# 选择预训练模型 +def select_pretrained_model(model_list, need_select_pretrained = False, pretrained_models_dict = pretrained_models_dict): + """ + 让用户从给定的模型列表中选择预训练模型、是否选择预训练(否-默认开启预训练),并返回对应的 pth 路径和 其他 信息。 + + :param model_list: 可用的模型名称列表,例如 ['openmmlab/resnet50_v1c', 'openmmlab/resnet101_v1c'] + :return: (pretrained_pth, depth) + """ + + # 过滤传入的模型列表,确保它们在字典中有信息 + valid_models = {key: pretrained_models_dict[key] for key in model_list if key in pretrained_models_dict} + + if not valid_models: + print("错误:提供的模型列表中没有有效的模型信息。") + return None, None + + # 显示可用的预训练模型 + print("可用的预训练模型类型:") + for i, (model_name, model_info) in enumerate(valid_models.items(), 1): + print(f"{i}. {model_name}") + + # 用户选择 + choice = input(f"请选择预训练模型编号 (1-{len(valid_models)}, 默认 1): ").strip() + + # 如果用户没有输入,或输入无效,使用默认值 + if not choice.isdigit() or not (1 <= int(choice) <= len(valid_models)): + choice = "1" + + # 获取用户选择的模型信息 + selected_model_name = list(valid_models.keys())[int(choice) - 1] + selected_model_info = valid_models[selected_model_name] + + # TODO 选择特定信息 + # # 返回模型的 pth 路径和 depth + pretrained_pth = selected_model_info['pth'] + # depth = selected_model_info['depth'] + + # print(f" 已选择模型: {selected_model_name} depth: {depth} pth: {pretrained_pth}") + print(f" 已选择模型: {selected_model_name} pth: {pretrained_pth}") + + if need_select_pretrained == False: + print("默认开启预训练") + select_pretrained = True + return selected_model_name, select_pretrained, pretrained_pth, selected_model_info + else: + # 提示用户输入 True 或 False,或者直接按 Enter 默认使用预训练模型 + choice = input("是否使用预训练模型?输入 Y(使用)或 N(不使用),直接按 Enter 默认使用:True:") + while True: + # 如果用户没有输入,默认使用预训练模型 + if choice == '': + select_pretrained = True + break + # 转换输入为布尔值 + elif choice.lower() == 'y': + select_pretrained = True + break + elif choice.lower() == 'n': + select_pretrained = False + break + else: + print("无效输入,请输入 'True' 或 'False',或直接按 Enter 选择默认值") + + return selected_model_name, select_pretrained, pretrained_pth, selected_model_info + +# 大字典,包含所有模型的信息 +samplers_dict = { + "OHEMPixelSampler": dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000), + # 这里可以包含更多采样函数模型信息... +} + +# 选择采样函数 +def select_sampler(sampler_list, samplers_dict = samplers_dict): + + # 提示用户输入 True 或 False,或者直接按 Enter 默认使用预训练模型 + choice = input("是否使用采样函数?输入 Y(使用)或 N(不使用),直接按 Enter 默认不使用:False:") + while True: + # 如果用户没有输入,默认使用预训练模型 + if choice == '': + use_sampler = False + return None, use_sampler, None + # 转换输入为布尔值 + elif choice.lower() == 'y': + use_sampler = True + break + elif choice.lower() == 'n': + use_sampler = False + return None, use_sampler, None + else: + print("无效输入,请输入 'True' 或 'False',或直接按 Enter 选择默认值") + + # 过滤传入的模型列表,确保它们在字典中有信息 + valid_samplers = {key: samplers_dict[key] for key in sampler_list if key in samplers_dict} + + if not valid_samplers: + print("错误:提供的采样函数列表中没有有效的采样函数信息。") + return None, None + + # 如果只有一个可用的采样函数,直接选择 + if len(valid_samplers) != 1: + # 显示可用的采样函数 + print("可用的采样函数:") + for i, (sampler_name, sampler_info) in enumerate(valid_samplers.items(), 1): + print(f"{i}. {sampler_name}") + + # 用户选择 + choice = input(f"请选择采样函数编号 (1-{len(valid_samplers)}, 默认 1): ").strip() + + # 如果用户没有输入,或输入无效,使用默认值 + if not choice.isdigit() or not (1 <= int(choice) <= len(valid_samplers)): + choice = "1" + else: + choice = "1" + + # 获取用户选择的模型信息 + selected_sampler_name = list(valid_samplers.keys())[int(choice) - 1] + selected_sampler_info = valid_samplers[selected_sampler_name] + + print(f" 已选择采样函数: {selected_sampler_name}") + + return selected_sampler_name, use_sampler, selected_sampler_info + +# 选择test_cfg中是否滑动,是否默认选择select_slide +def select_test_cfg_slide(crop_size, select_slide=False): + """ + 让用户选择是否使用滑动窗口,并根据选择设置相应的模式和参数。 + + :param crop_size: 输入的裁剪大小 (宽, 高) + :return: test_cfg_mode, test_cfg_crop_div_stride, crop_size + """ + if select_slide == False: + # 提示用户是否选择滑动窗口模式 + use_slide = input("是否选择滑动窗口模式?(y/n, 默认 n): ").strip().lower() + else: + use_slide = 'y' # 使用滑动窗口模式 + + # 默认不使用滑动窗口模式 + if use_slide == 'y': + test_cfg_mode = 'slide' + # 提示用户输入 test_cfg_crop_div_stride,默认值为 1.5 + try: + test_cfg_crop_div_stride = input("请输入滑动窗口的 stride 除以 crop_size 比例 (默认 1.5): ").strip() + test_cfg_crop_div_stride = float(test_cfg_crop_div_stride) if test_cfg_crop_div_stride else 1.5 + except ValueError: + print("输入无效,使用默认比例 1.5") + test_cfg_crop_div_stride = 1.5 + + # 计算 stride + stride = tuple(int(c / test_cfg_crop_div_stride) for c in crop_size) + print(f" 已选择滑动窗口模式: {test_cfg_mode}") + print(f"crop_size: {crop_size}") + print(f"stride: {stride}") + else: + test_cfg_mode = None # 默认模式 + test_cfg_crop_div_stride = None + stride = None + print(f" 关闭滑动窗口模式") + + return test_cfg_mode, test_cfg_crop_div_stride + + diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Calculate_std_and_mean.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Calculate_std_and_mean.py new file mode 100644 index 0000000..eb735e2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Calculate_std_and_mean.py @@ -0,0 +1,46 @@ +from PIL import Image +import os +import numpy as np +from tqdm import tqdm + +def calculate_pic_std_and_mean(dataset_dir = r'./My_Data/A_Ori'): + # 获取所有jpg图像文件 + image_files = [os.path.join(dataset_dir, filename) for filename in os.listdir(dataset_dir) if filename.lower().endswith(('.jpg', '.png', '.tiff', '.jpeg', '.bmp'))] + + # 初始化用于存储累积的像素值 + sum_pixels_normalized = np.zeros(3) + sum_squared_pixels_normalized = np.zeros(3) + num_pixels = 0 + + # 使用tqdm创建一个进度条 + for image_file in tqdm(image_files, desc="Calculating mean and std"): + image = Image.open(image_file).convert('RGB') # 确保图像为RGB + image = np.array(image) # 原图像像素范围[0, 255] + + # 归一化到[0, 1]范围 + image_normalized = image / 255.0 + + # 累积归一化像素值和归一化像素平方值 + sum_pixels_normalized += np.sum(image_normalized, axis=(0, 1)) # 按通道累积 + sum_squared_pixels_normalized += np.sum(image_normalized ** 2, axis=(0, 1)) # 按通道累积像素平方值 + num_pixels += image.shape[0] * image.shape[1] # 累积总像素数 + + # 计算整个数据集的归一化后的均值 + mean_normalized = sum_pixels_normalized / num_pixels + + # 计算整个数据集的归一化后的标准差 + variance_normalized = sum_squared_pixels_normalized / num_pixels - mean_normalized ** 2 + variance_normalized = np.maximum(variance_normalized, 0) # 防止负数 + std_normalized = np.sqrt(variance_normalized) + + # 反归一化回[0, 255]范围 + mean = mean_normalized * 255 + std = std_normalized * 255 + + print(f"\033[93m计算得图片均值-Mean: {mean} 计算得图片方差-Std: {std}\033[0m") + + return mean, std + +if __name__ == '__main__': + # 示例调用 + calculate_pic_std_and_mean() diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_configs_base_datasets_my_dataset.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_configs_base_datasets_my_dataset.py new file mode 100644 index 0000000..ead6028 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_configs_base_datasets_my_dataset.py @@ -0,0 +1,121 @@ +import os + +def generate_configs_base_datasets_my_dataset_file( + output_file='./configs/_base_/datasets/my_dataset_model.py', + dataset_class_name='MyDataset_model', + data_root='/home/audience/Desktop/Seg_data/Data', + img_scale=(1920, 1080), + crop_size=(512, 512), + train_batch_size=4, + train_num_workers=4, + val_and_test_batch_size=1, + val_and_test_num_workers=4, + train_img_path='A_Ori', + train_seg_map_path='A_Label_GT_label_fold', + val_img_path='A_Ori', + val_seg_map_path='A_Label_GT_label_fold', + test_img_path='A_Ori', + test_seg_map_path='A_Label_GT_label_fold', +): + # 定义模板 + dataset_config_template = f"""# dataset settings +dataset_class_name = '{dataset_class_name}' # TODO 上一步中你定义的数据集的名字 +data_root = '{data_root}' # TODO 数据集存储路径 +# img_norm_cfg = dict( +# mean=[33.30, 35.03, 47.23], std=[48.00, 50.4, 60.51], to_rgb=True) # TODO 数据集的均值和标准差,空引用默认的,也可以网上搜代码计算 +img_scale = {img_scale} # img_scale图像尺寸 TODO (1920,1080) +crop_size = {crop_size} # 数据增强时裁剪的大小 TODO 之后可以修改 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), # ", reduce_zero_label=False" TODO 是否忽略0直选项 + dict(type='RandomResize', scale=img_scale, ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + # dict(type='GenerateEdge', edge_width=4), # For pidnet + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( # Train dataloader config + batch_size={train_batch_size}, # Batch size of a single GPU TODO + num_workers={train_num_workers}, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='DefaultSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='{train_img_path}', + seg_map_path='{train_seg_map_path}'), + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size={val_and_test_batch_size}, # Batch size of a single GPU + num_workers={val_and_test_num_workers}, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='{val_img_path}', + seg_map_path='{val_seg_map_path}'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = dict( + batch_size={val_and_test_batch_size}, # Batch size of a single GPU + num_workers={val_and_test_num_workers}, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='{test_img_path}', + seg_map_path='{test_seg_map_path}'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator +""" + + # 创建目录(如果不存在的话) + os.makedirs(os.path.dirname(output_file), exist_ok=True) + + # 写入文件 + with open(output_file, 'w') as f: + f.write(dataset_config_template) + print(f"\033[93m{output_file} file generated successfully\033[0m") + return True + +if __name__ == '__main__': + ########### 定义各参数 ########### + dataset_file_name='my_dataset_model' # 数据集 文件名.py + dataset_class_name='MyDataset_model' # 数据集 类名称 + data_root='/home/audience/Desktop/Seg_data/Data' # 数据根目录 + img_scale=(1920, 1080) # 图片大小 + # 训练、验证、测试集所在文件夹 + train_img_path='A_Ori' + train_seg_map_path='A_Label_GT_label_fold' + val_img_path='A_Ori' + val_seg_map_path='A_Label_GT_label_fold' + test_img_path='A_Ori' + test_seg_map_path='A_Label_GT_label_fold' + + # 一般不太会变的参数 + crop_size=(512, 512) # 分割大小 + train_batch_size=4 # 训练batch + train_num_workers=4 # 训练并行运行数量 + val_and_test_batch_size=1 # 验证集和测试集batch + val_and_test_num_workers=4 # 验证集和测试集并行运行数量 + + ########### 文件存储位置 ########### + output_configs_base_datasets_my_dataset=f'./configs/_base_/datasets/{dataset_file_name}.py' + + # 使用默认变量生成配置文件 + success = generate_configs_base_datasets_my_dataset_file(output_file=output_configs_base_datasets_my_dataset, dataset_class_name=dataset_class_name , data_root=data_root, img_scale=img_scale, crop_size=crop_size, train_batch_size=train_batch_size, train_num_workers=train_num_workers, val_and_test_batch_size=val_and_test_batch_size, val_and_test_num_workers=val_and_test_num_workers, train_img_path=train_img_path, train_seg_map_path=train_seg_map_path, val_img_path=val_img_path, val_seg_map_path=val_seg_map_path, test_img_path=test_img_path, test_seg_map_path=test_seg_map_path) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_datasets_init_.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_datasets_init_.py new file mode 100644 index 0000000..fbcfecf --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_datasets_init_.py @@ -0,0 +1,114 @@ +import os + +def generate_mmseg_datasets_init_file(output_file, dataset_file_names=["my_data_set_model"], dataset_class_names=["MyDataset_model"]): + # 判断数据集文件名 和 对应类名大小是否相同 # 不同则输出错误内容,并退出 + if len(dataset_file_names) != len(dataset_class_names): + print(f"\033[91mInitial_Gen_mmseg_datasets_init_.py程序中MyDataset_File_name大小和MyDataset_Class_name大小不匹配,函数退出!\033[0m") + return False + + # 模板开头部分 + header = """# Copyright (c) OpenMMLab. All rights reserved. +# yapf: disable +from .ade import ADE20KDataset +from .basesegdataset import BaseCDDataset, BaseSegDataset +from .bdd100k import BDD100KDataset +from .chase_db1 import ChaseDB1Dataset +from .cityscapes import CityscapesDataset +from .coco_stuff import COCOStuffDataset +from .dark_zurich import DarkZurichDataset +from .dataset_wrappers import MultiImageMixDataset +from .decathlon import DecathlonDataset +from .drive import DRIVEDataset +from .dsdl import DSDLSegDataset +from .hrf import HRFDataset +from .hsi_drive import HSIDrive20Dataset +from .isaid import iSAIDDataset +from .isprs import ISPRSDataset +from .levir import LEVIRCDDataset +from .lip import LIPDataset +from .loveda import LoveDADataset +from .mapillary import MapillaryDataset_v1, MapillaryDataset_v2 +from .night_driving import NightDrivingDataset +from .nyu import NYUDataset +from .pascal_context import PascalContextDataset, PascalContextDataset59 +from .potsdam import PotsdamDataset +from .refuge import REFUGEDataset +from .stare import STAREDataset +from .synapse import SynapseDataset +""" + # 增加多个 dataset_file_names imports + imports = "" + for dataset_file, dataset_name in zip(dataset_file_names, dataset_class_names): + imports += f"from .{dataset_file} import {dataset_name} # TODO\n" + + # 中间固定部分 + middle = """# yapf: disable +from .transforms import (CLAHE, AdjustGamma, Albu, BioMedical3DPad, + BioMedical3DRandomCrop, BioMedical3DRandomFlip, + BioMedicalGaussianBlur, BioMedicalGaussianNoise, + BioMedicalRandomGamma, ConcatCDInput, GenerateEdge, + LoadAnnotations, LoadBiomedicalAnnotation, + LoadBiomedicalData, LoadBiomedicalImageFromFile, + LoadImageFromNDArray, LoadMultipleRSImageFromFile, + LoadSingleRSImageFromFile, PackSegInputs, + PhotoMetricDistortion, RandomCrop, RandomCutOut, + RandomMosaic, RandomRotate, RandomRotFlip, Rerange, + ResizeShortestEdge, ResizeToMultiple, RGB2Gray, + SegRescale) +from .voc import PascalVOCDataset + +# yapf: enable +__all__ = [ +""" + # 增加多个 vars.MyDataset_C 到 __all__ + all_datasets = "" + for dataset_name in dataset_class_names: + all_datasets += f" '{dataset_name}', # TODO\n" + + # __all__ 中其他固定部分 + all_fixed = """ 'BaseSegDataset', 'BioMedical3DRandomCrop', 'BioMedical3DRandomFlip', + 'CityscapesDataset', 'PascalVOCDataset', 'ADE20KDataset', + 'PascalContextDataset', 'PascalContextDataset59', 'ChaseDB1Dataset', + 'DRIVEDataset', 'HRFDataset', 'STAREDataset', 'DarkZurichDataset', + 'NightDrivingDataset', 'COCOStuffDataset', 'LoveDADataset', + 'MultiImageMixDataset', 'iSAIDDataset', 'ISPRSDataset', 'PotsdamDataset', + 'LoadAnnotations', 'RandomCrop', 'SegRescale', 'PhotoMetricDistortion', + 'RandomRotate', 'AdjustGamma', 'CLAHE', 'Rerange', 'RGB2Gray', + 'RandomCutOut', 'RandomMosaic', 'PackSegInputs', 'ResizeToMultiple', + 'LoadImageFromNDArray', 'LoadBiomedicalImageFromFile', + 'LoadBiomedicalAnnotation', 'LoadBiomedicalData', 'GenerateEdge', + 'DecathlonDataset', 'LIPDataset', 'ResizeShortestEdge', + 'BioMedicalGaussianNoise', 'BioMedicalGaussianBlur', + 'BioMedicalRandomGamma', 'BioMedical3DPad', 'RandomRotFlip', + 'SynapseDataset', 'REFUGEDataset', 'MapillaryDataset_v1', + 'MapillaryDataset_v2', 'Albu', 'LEVIRCDDataset', + 'LoadMultipleRSImageFromFile', 'LoadSingleRSImageFromFile', + 'ConcatCDInput', 'BaseCDDataset', 'DSDLSegDataset', 'BDD100KDataset', + 'NYUDataset', 'HSIDrive20Dataset' +] +""" + + # 拼接完整内容 + content = header + imports + middle + all_datasets + all_fixed + + # 写入文件 + with open(output_file, 'w', encoding='utf-8') as file: + file.write(content) + + print(f"\033[93m{output_file} file generated successfully\033[0m") + return True + +if __name__ == "__main__": + ########### 定义各参数 ########### + # 可以定义多个数据集文件名 和 对应类名 + dataset_file_names = ["my_dataset_model"] # =['my_dataset', 'my_dataset_2'] # =["my_dataset"] + dataset_class_names = ["MyDataset_model"] # =['MyDataset', 'MyDataset2'] # =["MyDataset"] + + ########### 文件存储位置 ########### + output_mmseg_datasets_init = os.path.join('./mmseg/datasets/__init__.py') + + # 生成 ./mmseg/datasets/__init__.py 文件 + success = generate_mmseg_datasets_init_file(output_file=output_mmseg_datasets_init, dataset_file_names=dataset_file_names, dataset_class_names=dataset_class_names) + + + diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_datasets_my_dataset.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_datasets_my_dataset.py new file mode 100644 index 0000000..30de767 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_datasets_my_dataset.py @@ -0,0 +1,83 @@ +import os + +def generate_mmseg_datasets_my_dataset_file(output_file='./mmseg/datasets/my_dataset_model.py', dataset_class_name = "MyDataset_model", classes=['背景'], palette=[[0,0,0]], img_suffix=".png", seg_map_suffix="_gtFine_labelTrainIds.png", reduce_zero_label=False): + # 先判断 classes 和 palette 的大小是否一致 + if len(classes) != len(palette): + print(f"\033[91mInitial_Gen_mmseg_datasets_my_dataset.py程序中classes大小和palette大小不匹配,函数退出!\033[0m") + return False + + # 判断是否有 '背景' 和 [0, 0, 0] + if '背景' not in classes and 'background' not in classes and 'bg' not in classes and [0, 0, 0] not in palette: + # 循环提示用户直到输入有效的值 + while True: + print(f"现有Clas为:{classes}") + print(f"现有palette为:{palette}") + user_input = input("是否加入背景[0,0,0]到调色板第一位,Y加入,N不加入: ").strip().lower() + if user_input == 'y': + classes.insert(0,'背景') + palette.insert(0,[0, 0, 0]) + print("已加入背景和调色板。") + break + elif user_input == 'n': + print("未加入背景和调色板。") + break + else: + print("无效输入,请输入Y或N。") + + # 使用 f-string 进行模板文本替换 + template = f'''# Copyright (c) OpenMMLab. All rights reserved. +# import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class {dataset_class_name}(BaseSegDataset): # 表示你定义的数据的名字,顺便取一个名字即可 + """{dataset_class_name} dataset. + """ + METAINFO = dict( + classes={classes}, # 背景最好放到第一个 + palette={palette}) # TODO 标注类型和颜色 + + def __init__(self, + img_suffix='{img_suffix}', # TODO mask图像类型 + seg_map_suffix='{seg_map_suffix}', # TODO mask图像后缀 + reduce_zero_label={reduce_zero_label}, # TODO 在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + + # assert fileio.exists( + # self.data_prefix['img_path'], backend_args=self.backend_args) +''' + + # 将生成的程序内容写入文件 + with open(output_file, 'w', encoding='utf-8') as f: + f.write(template) + + print(f"\033[93m{output_file} file generated successfully, 其中标签共{len(classes)}类\033[0m") + return True, classes, palette + +if __name__ == "__main__": + + ########### 定义各参数 ########### + # 定义要替换的内容 + dataset_file_name = "my_dataset_model" + dataset_class_name = "MyDataset_model" + classes = ['肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉'] + palette = [[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118],[0,157,142],[181,85,105],[42,8,66]] + + # 一般不太会变的参数 + img_suffix = ".png" + seg_map_suffix = "_gtFine_labelTrainIds.png" + reduce_zero_label = False # 在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + + ########### 文件存储位置 ########### + output_mmseg_datasets_dataset_file_name = os.path.join(f'./mmseg/datasets/{dataset_file_name}.py') + + # 生成程序文件 + success, classes, palette = generate_mmseg_datasets_my_dataset_file(output_file=output_mmseg_datasets_dataset_file_name, dataset_class_name=dataset_class_name, classes=classes, palette=palette, img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, reduce_zero_label=reduce_zero_label) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_utils_class_names.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_utils_class_names.py new file mode 100644 index 0000000..91086c7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Data_Program/Initial_Data_Gen_mmseg_utils_class_names.py @@ -0,0 +1,617 @@ +import os + +def generate_mmseg_utils_class_names_file(output_file=f'./mmseg/utils/class_names.py', dataset_file_names=['my_dataset_model', 'my_dataset_model_2'], classes_all=[['背景_1'], ['背景_2']], palette_all=[[[0,0,0]], [[0,0,0]]]): + # 检查 dataset_file_names、classes_all 和 palette_all 的长度是否一致 + if len(dataset_file_names) != len(classes_all) or len(dataset_file_names) != len(palette_all): + print(f"\033[91mInitial_Gen_mmseg_utils_class_names.py程序中 dataset_file_names 数量 {len(dataset_file_names)} 和 classes_all {len(classes_all)} 或 palette_all {len(palette_all)} 大小不匹配,函数退出!\033[0m") + return False + + # 逐一检查 classes 和 palette 的大小是否一致 + for i, (classes, palette) in enumerate(zip(classes_all, palette_all)): + len_classes = len(classes) + len_palette = len(palette) + if len_classes != len_palette: + print(f"\033[91mInitial_Gen_mmseg_utils_class_names.py程序中 {dataset_file_names[i]} 的 classes 大小 {len_classes} 和 palette 大小 {len_palette} 不匹配,函数退出!\033[0m") + print(f"\033[91m具体 classes: \033[0m{classes}") + print(f"\033[91m具体 palette: \033[0m{palette}") + return False + + # 初始化内容变量,用于存储多组 classes 和 palette + content = '''# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.utils import is_str + + +def cityscapes_classes(): + """Cityscapes class names for external use.""" + return [ + 'road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', 'sky', + 'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle' + ] + + +def ade_classes(): + """ADE20K class names for external use.""" + return [ + 'wall', 'building', 'sky', 'floor', 'tree', 'ceiling', 'road', 'bed ', + 'windowpane', 'grass', 'cabinet', 'sidewalk', 'person', 'earth', + 'door', 'table', 'mountain', 'plant', 'curtain', 'chair', 'car', + 'water', 'painting', 'sofa', 'shelf', 'house', 'sea', 'mirror', 'rug', + 'field', 'armchair', 'seat', 'fence', 'desk', 'rock', 'wardrobe', + 'lamp', 'bathtub', 'railing', 'cushion', 'base', 'box', 'column', + 'signboard', 'chest of drawers', 'counter', 'sand', 'sink', + 'skyscraper', 'fireplace', 'refrigerator', 'grandstand', 'path', + 'stairs', 'runway', 'case', 'pool table', 'pillow', 'screen door', + 'stairway', 'river', 'bridge', 'bookcase', 'blind', 'coffee table', + 'toilet', 'flower', 'book', 'hill', 'bench', 'countertop', 'stove', + 'palm', 'kitchen island', 'computer', 'swivel chair', 'boat', 'bar', + 'arcade machine', 'hovel', 'bus', 'towel', 'light', 'truck', 'tower', + 'chandelier', 'awning', 'streetlight', 'booth', 'television receiver', + 'airplane', 'dirt track', 'apparel', 'pole', 'land', 'bannister', + 'escalator', 'ottoman', 'bottle', 'buffet', 'poster', 'stage', 'van', + 'ship', 'fountain', 'conveyer belt', 'canopy', 'washer', 'plaything', + 'swimming pool', 'stool', 'barrel', 'basket', 'waterfall', 'tent', + 'bag', 'minibike', 'cradle', 'oven', 'ball', 'food', 'step', 'tank', + 'trade name', 'microwave', 'pot', 'animal', 'bicycle', 'lake', + 'dishwasher', 'screen', 'blanket', 'sculpture', 'hood', 'sconce', + 'vase', 'traffic light', 'tray', 'ashcan', 'fan', 'pier', 'crt screen', + 'plate', 'monitor', 'bulletin board', 'shower', 'radiator', 'glass', + 'clock', 'flag' + ] + + +def voc_classes(): + """Pascal VOC class names for external use.""" + return [ + 'background', 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', + 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor' + ] + + +def pcontext_classes(): + """Pascal Context class names for external use.""" + return [ + 'aeroplane', 'bag', 'bed', 'bedclothes', 'bench', 'bicycle', 'bird', + 'boat', 'book', 'bottle', 'building', 'bus', 'cabinet', 'car', 'cat', + 'ceiling', 'chair', 'cloth', 'computer', 'cow', 'cup', 'curtain', + 'dog', 'door', 'fence', 'floor', 'flower', 'food', 'grass', 'ground', + 'horse', 'keyboard', 'light', 'motorbike', 'mountain', 'mouse', + 'person', 'plate', 'platform', 'pottedplant', 'road', 'rock', 'sheep', + 'shelves', 'sidewalk', 'sign', 'sky', 'snow', 'sofa', 'table', 'track', + 'train', 'tree', 'truck', 'tvmonitor', 'wall', 'water', 'window', + 'wood' + ] + + +def cocostuff_classes(): + """CocoStuff class names for external use.""" + return [ + 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', + 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', + 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', + 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', + 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', + 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush', 'banner', + 'blanket', 'branch', 'bridge', 'building-other', 'bush', 'cabinet', + 'cage', 'cardboard', 'carpet', 'ceiling-other', 'ceiling-tile', + 'cloth', 'clothes', 'clouds', 'counter', 'cupboard', 'curtain', + 'desk-stuff', 'dirt', 'door-stuff', 'fence', 'floor-marble', + 'floor-other', 'floor-stone', 'floor-tile', 'floor-wood', 'flower', + 'fog', 'food-other', 'fruit', 'furniture-other', 'grass', 'gravel', + 'ground-other', 'hill', 'house', 'leaves', 'light', 'mat', 'metal', + 'mirror-stuff', 'moss', 'mountain', 'mud', 'napkin', 'net', 'paper', + 'pavement', 'pillow', 'plant-other', 'plastic', 'platform', + 'playingfield', 'railing', 'railroad', 'river', 'road', 'rock', 'roof', + 'rug', 'salad', 'sand', 'sea', 'shelf', 'sky-other', 'skyscraper', + 'snow', 'solid-other', 'stairs', 'stone', 'straw', 'structural-other', + 'table', 'tent', 'textile-other', 'towel', 'tree', 'vegetable', + 'wall-brick', 'wall-concrete', 'wall-other', 'wall-panel', + 'wall-stone', 'wall-tile', 'wall-wood', 'water-other', 'waterdrops', + 'window-blind', 'window-other', 'wood' + ] + + +def loveda_classes(): + """LoveDA class names for external use.""" + return [ + 'background', 'building', 'road', 'water', 'barren', 'forest', + 'agricultural' + ] + + +def potsdam_classes(): + """Potsdam class names for external use.""" + return [ + 'impervious_surface', 'building', 'low_vegetation', 'tree', 'car', + 'clutter' + ] + + +def vaihingen_classes(): + """Vaihingen class names for external use.""" + return [ + 'impervious_surface', 'building', 'low_vegetation', 'tree', 'car', + 'clutter' + ] + + +def isaid_classes(): + """iSAID class names for external use.""" + return [ + 'background', 'ship', 'store_tank', 'baseball_diamond', 'tennis_court', + 'basketball_court', 'Ground_Track_Field', 'Bridge', 'Large_Vehicle', + 'Small_Vehicle', 'Helicopter', 'Swimming_pool', 'Roundabout', + 'Soccer_ball_field', 'plane', 'Harbor' + ] + + +def stare_classes(): + """stare class names for external use.""" + return ['background', 'vessel'] + + +def mapillary_v1_classes(): + """mapillary_v1 class names for external use.""" + return [ + 'Bird', 'Ground Animal', 'Curb', 'Fence', 'Guard Rail', 'Barrier', + 'Wall', 'Bike Lane', 'Crosswalk - Plain', 'Curb Cut', 'Parking', + 'Pedestrian Area', 'Rail Track', 'Road', 'Service Lane', 'Sidewalk', + 'Bridge', 'Building', 'Tunnel', 'Person', 'Bicyclist', 'Motorcyclist', + 'Other Rider', 'Lane Marking - Crosswalk', 'Lane Marking - General', + 'Mountain', 'Sand', 'Sky', 'Snow', 'Terrain', 'Vegetation', 'Water', + 'Banner', 'Bench', 'Bike Rack', 'Billboard', 'Catch Basin', + 'CCTV Camera', 'Fire Hydrant', 'Junction Box', 'Mailbox', 'Manhole', + 'Phone Booth', 'Pothole', 'Street Light', 'Pole', 'Traffic Sign Frame', + 'Utility Pole', 'Traffic Light', 'Traffic Sign (Back)', + 'Traffic Sign (Front)', 'Trash Can', 'Bicycle', 'Boat', 'Bus', 'Car', + 'Caravan', 'Motorcycle', 'On Rails', 'Other Vehicle', 'Trailer', + 'Truck', 'Wheeled Slow', 'Car Mount', 'Ego Vehicle', 'Unlabeled' + ] + + +def mapillary_v1_palette(): + """mapillary_v1_ palette for external use.""" + return [[165, 42, 42], [0, 192, 0], [196, 196, 196], [190, 153, 153], + [180, 165, 180], [90, 120, 150], [102, 102, 156], [128, 64, 255], + [140, 140, 200], [170, 170, 170], [250, 170, 160], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], [244, 35, 232], + [150, 100, 100], [70, 70, 70], [150, 120, 90], [220, 20, 60], + [255, 0, 0], [255, 0, 100], [255, 0, 200], [200, 128, 128], + [255, 255, 255], [64, 170, 64], [230, 160, 50], [70, 130, 180], + [190, 255, 255], [152, 251, 152], [107, 142, 35], [0, 170, 30], + [255, 255, 128], [250, 0, 30], [100, 140, 180], [220, 220, 220], + [220, 128, 128], [222, 40, 40], [100, 170, 30], [40, 40, 40], + [33, 33, 33], [100, 128, 160], [142, 0, 0], [70, 100, 150], + [210, 170, 100], [153, 153, 153], [128, 128, 128], [0, 0, 80], + [250, 170, 30], [192, 192, 192], [220, 220, 0], [140, 140, 20], + [119, 11, 32], [150, 0, 255], [0, 60, 100], [0, 0, 142], + [0, 0, 90], [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 192], [32, 32, 32], [120, 10, 10], [0, 0, 0]] + + +def mapillary_v2_classes(): + """mapillary_v2 class names for external use.""" + return [ + 'Bird', 'Ground Animal', 'Ambiguous Barrier', 'Concrete Block', 'Curb', + 'Fence', 'Guard Rail', 'Barrier', 'Road Median', 'Road Side', + 'Lane Separator', 'Temporary Barrier', 'Wall', 'Bike Lane', + 'Crosswalk - Plain', 'Curb Cut', 'Driveway', 'Parking', + 'Parking Aisle', 'Pedestrian Area', 'Rail Track', 'Road', + 'Road Shoulder', 'Service Lane', 'Sidewalk', 'Traffic Island', + 'Bridge', 'Building', 'Garage', 'Tunnel', 'Person', 'Person Group', + 'Bicyclist', 'Motorcyclist', 'Other Rider', + 'Lane Marking - Dashed Line', 'Lane Marking - Straight Line', + 'Lane Marking - Zigzag Line', 'Lane Marking - Ambiguous', + 'Lane Marking - Arrow (Left)', 'Lane Marking - Arrow (Other)', + 'Lane Marking - Arrow (Right)', + 'Lane Marking - Arrow (Split Left or Straight)', + 'Lane Marking - Arrow (Split Right or Straight)', + 'Lane Marking - Arrow (Straight)', 'Lane Marking - Crosswalk', + 'Lane Marking - Give Way (Row)', 'Lane Marking - Give Way (Single)', + 'Lane Marking - Hatched (Chevron)', + 'Lane Marking - Hatched (Diagonal)', 'Lane Marking - Other', + 'Lane Marking - Stop Line', 'Lane Marking - Symbol (Bicycle)', + 'Lane Marking - Symbol (Other)', 'Lane Marking - Text', + 'Lane Marking (only) - Dashed Line', 'Lane Marking (only) - Crosswalk', + 'Lane Marking (only) - Other', 'Lane Marking (only) - Test', + 'Mountain', 'Sand', 'Sky', 'Snow', 'Terrain', 'Vegetation', 'Water', + 'Banner', 'Bench', 'Bike Rack', 'Catch Basin', 'CCTV Camera', + 'Fire Hydrant', 'Junction Box', 'Mailbox', 'Manhole', 'Parking Meter', + 'Phone Booth', 'Pothole', 'Signage - Advertisement', + 'Signage - Ambiguous', 'Signage - Back', 'Signage - Information', + 'Signage - Other', 'Signage - Store', 'Street Light', 'Pole', + 'Pole Group', 'Traffic Sign Frame', 'Utility Pole', 'Traffic Cone', + 'Traffic Light - General (Single)', 'Traffic Light - Pedestrians', + 'Traffic Light - General (Upright)', + 'Traffic Light - General (Horizontal)', 'Traffic Light - Cyclists', + 'Traffic Light - Other', 'Traffic Sign - Ambiguous', + 'Traffic Sign (Back)', 'Traffic Sign - Direction (Back)', + 'Traffic Sign - Direction (Front)', 'Traffic Sign (Front)', + 'Traffic Sign - Parking', 'Traffic Sign - Temporary (Back)', + 'Traffic Sign - Temporary (Front)', 'Trash Can', 'Bicycle', 'Boat', + 'Bus', 'Car', 'Caravan', 'Motorcycle', 'On Rails', 'Other Vehicle', + 'Trailer', 'Truck', 'Vehicle Group', 'Wheeled Slow', 'Water Valve', + 'Car Mount', 'Dynamic', 'Ego Vehicle', 'Ground', 'Static', 'Unlabeled' + ] + + +def mapillary_v2_palette(): + """mapillary_v2_ palette for external use.""" + return [[165, 42, 42], [0, 192, 0], [250, 170, 31], [250, 170, 32], + [196, 196, 196], [190, 153, 153], [180, 165, 180], [90, 120, 150], + [250, 170, 33], [250, 170, 34], [128, 128, 128], [250, 170, 35], + [102, 102, 156], [128, 64, 255], [140, 140, 200], [170, 170, 170], + [250, 170, 36], [250, 170, 160], [250, 170, 37], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], [110, 110, 110], + [244, 35, 232], [128, 196, 128], [150, 100, 100], [70, 70, 70], + [150, 150, 150], [150, 120, 90], [220, 20, 60], [220, 20, 60], + [255, 0, 0], [255, 0, 100], [255, 0, 200], [255, 255, 255], + [255, 255, 255], [250, 170, 29], [250, 170, 28], [250, 170, 26], + [250, 170, 25], [250, 170, 24], [250, 170, 22], [250, 170, 21], + [250, 170, 20], [255, 255, 255], [250, 170, 19], [250, 170, 18], + [250, 170, 12], [250, 170, 11], [255, 255, 255], [255, 255, 255], + [250, 170, 16], [250, 170, 15], [250, 170, 15], [255, 255, 255], + [255, 255, 255], [255, 255, 255], [255, 255, 255], [64, 170, 64], + [230, 160, 50], [70, 130, 180], [190, 255, 255], [152, 251, 152], + [107, 142, 35], [0, 170, 30], [255, 255, 128], [250, 0, 30], + [100, 140, 180], [220, 128, 128], [222, 40, 40], [100, 170, 30], + [40, 40, 40], [33, 33, 33], [100, 128, 160], [20, 20, 255], + [142, 0, 0], [70, 100, 150], [250, 171, 30], [250, 172, 30], + [250, 173, 30], [250, 174, 30], [250, 175, 30], [250, 176, 30], + [210, 170, 100], [153, 153, 153], [153, 153, 153], [128, 128, 128], + [0, 0, 80], [210, 60, 60], [250, 170, 30], [250, 170, 30], + [250, 170, 30], [250, 170, 30], [250, 170, 30], [250, 170, 30], + [192, 192, 192], [192, 192, 192], [192, 192, 192], [220, 220, 0], + [220, 220, 0], [0, 0, 196], [192, 192, 192], [220, 220, 0], + [140, 140, 20], [119, 11, 32], [150, 0, 255], [0, 60, 100], + [0, 0, 142], [0, 0, 90], [0, 0, 230], [0, 80, 100], [128, 64, 64], + [0, 0, 110], [0, 0, 70], [0, 0, 142], [0, 0, 192], [170, 170, 170], + [32, 32, 32], [111, 74, 0], [120, 10, 10], [81, 0, 81], + [111, 111, 0], [0, 0, 0]] + + +def cityscapes_palette(): + """Cityscapes palette for external use.""" + return [[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], [0, 60, 100], [0, 80, 100], + [0, 0, 230], [119, 11, 32]] + + +def ade_palette(): + """ADE20K palette for external use.""" + return [[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50], + [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255], + [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7], + [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82], + [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3], + [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255], + [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220], + [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224], + [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255], + [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7], + [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153], + [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255], + [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0], + [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255], + [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255], + [11, 200, 200], [255, 82, 0], [0, 255, 245], [0, 61, 255], + [0, 255, 112], [0, 255, 133], [255, 0, 0], [255, 163, 0], + [255, 102, 0], [194, 255, 0], [0, 143, 255], [51, 255, 0], + [0, 82, 255], [0, 255, 41], [0, 255, 173], [10, 0, 255], + [173, 255, 0], [0, 255, 153], [255, 92, 0], [255, 0, 255], + [255, 0, 245], [255, 0, 102], [255, 173, 0], [255, 0, 20], + [255, 184, 184], [0, 31, 255], [0, 255, 61], [0, 71, 255], + [255, 0, 204], [0, 255, 194], [0, 255, 82], [0, 10, 255], + [0, 112, 255], [51, 0, 255], [0, 194, 255], [0, 122, 255], + [0, 255, 163], [255, 153, 0], [0, 255, 10], [255, 112, 0], + [143, 255, 0], [82, 0, 255], [163, 255, 0], [255, 235, 0], + [8, 184, 170], [133, 0, 255], [0, 255, 92], [184, 0, 255], + [255, 0, 31], [0, 184, 255], [0, 214, 255], [255, 0, 112], + [92, 255, 0], [0, 224, 255], [112, 224, 255], [70, 184, 160], + [163, 0, 255], [153, 0, 255], [71, 255, 0], [255, 0, 163], + [255, 204, 0], [255, 0, 143], [0, 255, 235], [133, 255, 0], + [255, 0, 235], [245, 0, 255], [255, 0, 122], [255, 245, 0], + [10, 190, 212], [214, 255, 0], [0, 204, 255], [20, 0, 255], + [255, 255, 0], [0, 153, 255], [0, 41, 255], [0, 255, 204], + [41, 0, 255], [41, 255, 0], [173, 0, 255], [0, 245, 255], + [71, 0, 255], [122, 0, 255], [0, 255, 184], [0, 92, 255], + [184, 255, 0], [0, 133, 255], [255, 214, 0], [25, 194, 194], + [102, 255, 0], [92, 0, 255]] + + +def voc_palette(): + """Pascal VOC palette for external use.""" + return [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0], [0, 0, 128], + [128, 0, 128], [0, 128, 128], [128, 128, 128], [64, 0, 0], + [192, 0, 0], [64, 128, 0], [192, 128, 0], [64, 0, 128], + [192, 0, 128], [64, 128, 128], [192, 128, 128], [0, 64, 0], + [128, 64, 0], [0, 192, 0], [128, 192, 0], [0, 64, 128]] + + +def pcontext_palette(): + """Pascal Context palette for external use.""" + return [[180, 120, 120], [6, 230, 230], [80, 50, 50], [4, 200, 3], + [120, 120, 80], [140, 140, 140], [204, 5, 255], [230, 230, 230], + [4, 250, 7], [224, 5, 255], [235, 255, 7], [150, 5, 61], + [120, 120, 70], [8, 255, 51], [255, 6, 82], [143, 255, 140], + [204, 255, 4], [255, 51, 7], [204, 70, 3], [0, 102, 200], + [61, 230, 250], [255, 6, 51], [11, 102, 255], [255, 7, 71], + [255, 9, 224], [9, 7, 230], [220, 220, 220], [255, 9, 92], + [112, 9, 255], [8, 255, 214], [7, 255, 224], [255, 184, 6], + [10, 255, 71], [255, 41, 10], [7, 255, 255], [224, 255, 8], + [102, 8, 255], [255, 61, 6], [255, 194, 7], [255, 122, 8], + [0, 255, 20], [255, 8, 41], [255, 5, 153], [6, 51, 255], + [235, 12, 255], [160, 150, 20], [0, 163, 255], [140, 140, 140], + [250, 10, 15], [20, 255, 0], [31, 255, 0], [255, 31, 0], + [255, 224, 0], [153, 255, 0], [0, 0, 255], [255, 71, 0], + [0, 235, 255], [0, 173, 255], [31, 0, 255]] + + +def cocostuff_palette(): + """CocoStuff palette for external use.""" + return [[0, 192, 64], [0, 192, 64], [0, 64, 96], [128, 192, 192], + [0, 64, 64], [0, 192, 224], [0, 192, 192], [128, 192, 64], + [0, 192, 96], [128, 192, 64], [128, 32, 192], [0, 0, 224], + [0, 0, 64], [0, 160, 192], [128, 0, 96], [128, 0, 192], + [0, 32, 192], [128, 128, 224], [0, 0, 192], [128, 160, 192], + [128, 128, 0], [128, 0, 32], [128, 32, 0], [128, 0, 128], + [64, 128, 32], [0, 160, 0], [0, 0, 0], [192, 128, 160], [0, 32, 0], + [0, 128, 128], [64, 128, 160], [128, 160, 0], [0, 128, 0], + [192, 128, 32], [128, 96, 128], [0, 0, 128], [64, 0, 32], + [0, 224, 128], [128, 0, 0], [192, 0, 160], [0, 96, 128], + [128, 128, 128], [64, 0, 160], [128, 224, 128], [128, 128, 64], + [192, 0, 32], [128, 96, 0], [128, 0, 192], [0, 128, 32], + [64, 224, 0], [0, 0, 64], [128, 128, 160], [64, 96, 0], + [0, 128, 192], [0, 128, 160], [192, 224, 0], [0, 128, 64], + [128, 128, 32], [192, 32, 128], [0, 64, 192], [0, 0, 32], + [64, 160, 128], [128, 64, 64], [128, 0, 160], [64, 32, 128], + [128, 192, 192], [0, 0, 160], [192, 160, 128], [128, 192, 0], + [128, 0, 96], [192, 32, 0], [128, 64, 128], [64, 128, 96], + [64, 160, 0], [0, 64, 0], [192, 128, 224], [64, 32, 0], + [0, 192, 128], [64, 128, 224], [192, 160, 0], [0, 192, 0], + [192, 128, 96], [192, 96, 128], [0, 64, 128], [64, 0, 96], + [64, 224, 128], [128, 64, 0], [192, 0, 224], [64, 96, 128], + [128, 192, 128], [64, 0, 224], [192, 224, 128], [128, 192, 64], + [192, 0, 96], [192, 96, 0], [128, 64, 192], [0, 128, 96], + [0, 224, 0], [64, 64, 64], [128, 128, 224], [0, 96, 0], + [64, 192, 192], [0, 128, 224], [128, 224, 0], [64, 192, 64], + [128, 128, 96], [128, 32, 128], [64, 0, 192], [0, 64, 96], + [0, 160, 128], [192, 0, 64], [128, 64, 224], [0, 32, 128], + [192, 128, 192], [0, 64, 224], [128, 160, 128], [192, 128, 0], + [128, 64, 32], [128, 32, 64], [192, 0, 128], [64, 192, 32], + [0, 160, 64], [64, 0, 0], [192, 192, 160], [0, 32, 64], + [64, 128, 128], [64, 192, 160], [128, 160, 64], [64, 128, 0], + [192, 192, 32], [128, 96, 192], [64, 0, 128], [64, 64, 32], + [0, 224, 192], [192, 0, 0], [192, 64, 160], [0, 96, 192], + [192, 128, 128], [64, 64, 160], [128, 224, 192], [192, 128, 64], + [192, 64, 32], [128, 96, 64], [192, 0, 192], [0, 192, 32], + [64, 224, 64], [64, 0, 64], [128, 192, 160], [64, 96, 64], + [64, 128, 192], [0, 192, 160], [192, 224, 64], [64, 128, 64], + [128, 192, 32], [192, 32, 192], [64, 64, 192], [0, 64, 32], + [64, 160, 192], [192, 64, 64], [128, 64, 160], [64, 32, 192], + [192, 192, 192], [0, 64, 160], [192, 160, 192], [192, 192, 0], + [128, 64, 96], [192, 32, 64], [192, 64, 128], [64, 192, 96], + [64, 160, 64], [64, 64, 0]] + + +def loveda_palette(): + """LoveDA palette for external use.""" + return [[255, 255, 255], [255, 0, 0], [255, 255, 0], [0, 0, 255], + [159, 129, 183], [0, 255, 0], [255, 195, 128]] + + +def potsdam_palette(): + """Potsdam palette for external use.""" + return [[255, 255, 255], [0, 0, 255], [0, 255, 255], [0, 255, 0], + [255, 255, 0], [255, 0, 0]] + + +def vaihingen_palette(): + """Vaihingen palette for external use.""" + return [[255, 255, 255], [0, 0, 255], [0, 255, 255], [0, 255, 0], + [255, 255, 0], [255, 0, 0]] + + +def isaid_palette(): + """iSAID palette for external use.""" + return [[0, 0, 0], [0, 0, 63], [0, 63, 63], [0, 63, 0], [0, 63, 127], + [0, 63, 191], [0, 63, 255], [0, 127, 63], [0, 127, + 127], [0, 0, 127], + [0, 0, 191], [0, 0, 255], [0, 191, 127], [0, 127, 191], + [0, 127, 255], [0, 100, 155]] + + +def stare_palette(): + """STARE palette for external use.""" + return [[120, 120, 120], [6, 230, 230]] + + +def synapse_palette(): + """Synapse palette for external use.""" + return [[0, 0, 0], [0, 0, 255], [0, 255, 0], [255, 0, 0], [0, 255, 255], + [255, 0, 255], [255, 255, 0], [60, 255, 255], [240, 240, 240]] + + +def synapse_classes(): + """Synapse class names for external use.""" + return [ + 'background', 'aorta', 'gallbladder', 'left_kidney', 'right_kidney', + 'liver', 'pancreas', 'spleen', 'stomach' + ] + + +def lip_classes(): + """LIP class names for external use.""" + return [ + 'background', 'hat', 'hair', 'glove', 'sunglasses', 'upperclothes', + 'dress', 'coat', 'socks', 'pants', 'jumpsuits', 'scarf', 'skirt', + 'face', 'leftArm', 'rightArm', 'leftLeg', 'rightLeg', 'leftShoe', + 'rightShoe' + ] + + +def lip_palette(): + """LIP palette for external use.""" + return [ + 'Background', 'Hat', 'Hair', 'Glove', 'Sunglasses', 'UpperClothes', + 'Dress', 'Coat', 'Socks', 'Pants', 'Jumpsuits', 'Scarf', 'Skirt', + 'Face', 'Left-arm', 'Right-arm', 'Left-leg', 'Right-leg', 'Left-shoe', + 'Right-shoe' + ] + + +def bdd100k_classes(): + """BDD100K class names for external use(the class name is compatible with + Cityscapes ).""" + return [ + 'road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', 'sky', + 'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle' + ] + + +def bdd100k_palette(): + """bdd100k palette for external use(same with cityscapes)""" + return [[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], [0, 60, 100], [0, 80, 100], + [0, 0, 230], [119, 11, 32]] + + +def hsidrive_classes(): + """HSI Drive 2.0 class names for external use.""" + return [ + 'unlabelled', 'road', 'road marks', 'vegetation', 'painted metal', + 'sky', 'concrete', 'pedestrian', 'water', 'unpainted metal', 'glass' + ] + + +def hsidrive_palette(): + """HSI Drive 2.0 palette for external use.""" + return [[0, 0, 0], [77, 77, 77], [255, 255, 255], [0, 255, 0], [255, 0, 0], + [0, 0, 255], [102, 51, 0], [255, 255, 0], [0, 207, 250], + [255, 166, 0], [0, 204, 204]] + + +''' + + # 生成多组 classes 和 palette 定义 + for dataset_file_name, classes, palette in zip(dataset_file_names, classes_all, palette_all): + # 每一组使用 f-string 进行模板替换 + content += f'''def {dataset_file_name}_classes(): # TODO + """{dataset_file_name} class names for external use.""" + return {classes} + + +def {dataset_file_name}_palette(): # TODO + """{dataset_file_name} palette for external use.""" + return {palette} + + +''' + + # dataset_aliases 和 get_classes, get_palette 函数的定义 + content += '''dataset_aliases = { +''' + + for dataset_file_name in dataset_file_names: + content += f" '{dataset_file_name}': ['{dataset_file_name}'], # TODO\n" + + # 追加固定的 hsidrive 映射 + content += ''' 'cityscapes': ['cityscapes'], + 'ade': ['ade', 'ade20k'], + 'voc': ['voc', 'pascal_voc', 'voc12', 'voc12aug'], + 'pcontext': ['pcontext', 'pascal_context', 'voc2010'], + 'loveda': ['loveda'], + 'potsdam': ['potsdam'], + 'vaihingen': ['vaihingen'], + 'cocostuff': [ + 'cocostuff', 'cocostuff10k', 'cocostuff164k', 'coco-stuff', + 'coco-stuff10k', 'coco-stuff164k', 'coco_stuff', 'coco_stuff10k', + 'coco_stuff164k' + ], + 'isaid': ['isaid', 'iSAID'], + 'stare': ['stare', 'STARE'], + 'lip': ['LIP', 'lip'], + 'mapillary_v1': ['mapillary_v1'], + 'mapillary_v2': ['mapillary_v2'], + 'bdd100k': ['bdd100k'], + 'hsidrive': [ + 'hsidrive', 'HSIDrive', 'HSI-Drive', 'hsidrive20', 'HSIDrive20', + 'HSI-Drive20' + ] +} + + +def get_classes(dataset): + """Get class names of a dataset.""" + alias2name = {} + for name, aliases in dataset_aliases.items(): + for alias in aliases: + alias2name[alias] = name + + if isinstance(dataset, str): + if dataset in alias2name: + labels = eval(alias2name[dataset] + '_classes()') + else: + raise ValueError(f'Unrecognized dataset: {dataset}') + else: + raise TypeError(f'dataset must a str, but got {type(dataset)}') + return labels + + +def get_palette(dataset): + """Get class palette (RGB) of a dataset.""" + alias2name = {} + for name, aliases in dataset_aliases.items(): + for alias in aliases: + alias2name[alias] = name + + if isinstance(dataset, str): + if dataset in alias2name: + labels = eval(alias2name[dataset] + '_palette()') + else: + raise ValueError(f'Unrecognized dataset: {dataset}') + else: + raise TypeError(f'dataset must a str, but got {type(dataset)}') + return labels +''' + + # 将生成的程序内容写入文件 + with open(output_file, 'w', encoding='utf-8') as f: + f.write(content) + + print(f"\033[93m{output_file} file generated successfully\033[0m") + return True + +if __name__ == "__main__": + ########### 定义各参数 ########### + # 可以定义多个数据集文件名 和 对应的classes和palette + dataset_file_names = ["my_dataset_model"] + # 这里的classes一定是经过“Initial_Gen_mmseg_datasets_my_dataset.py”处理的 + classes_all = [ + ['背景','肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉'], + ] + palette_all = [ + [[0,0,0],[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118],[0,157,142],[181,85,105],[42,8,66]], + ] + + ########### 文件存储位置 ########### + output_mmseg_utils_class_names = f'./mmseg/utils/class_names.py' + + # 生成程序文件 + success = generate_mmseg_utils_class_names_file(output_file=output_mmseg_utils_class_names, dataset_file_names=dataset_file_names, classes_all=classes_all, palette_all=palette_all) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/Initial_Train_Gen_configs_base_schedules_schedule_XXe.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/Initial_Train_Gen_configs_base_schedules_schedule_XXe.py new file mode 100644 index 0000000..e6fa011 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/Initial_Train_Gen_configs_base_schedules_schedule_XXe.py @@ -0,0 +1,82 @@ +import os + +def generate_epochs_configs_base_schedules_schedule_file(output_file, max_epochs=300, val_interval=1, checkpoint_interval=10, loggerhook_interval=300): + """ + 生成基于 Epoch 的 MMSegmentation schedule 配置文件。 + + Args: + output_file (str): 输出配置文件的路径。 + max_epochs (int): 最大训练轮次。 + val_interval (int): 验证间隔的轮次数。 + checkpoint_interval (int): 保存模型权重间隔的轮次数。 + loggerhook_interval (int): 日志打印的迭代间隔。 + """ + + # 定义 Epoch-based 的模板 + schedule_config_template = f"""# optimizer +# For SGD. +# optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +# For AdamW. +optimizer = dict(type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.01) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) + +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end={max_epochs}, + by_epoch=True) +] + +# training schedule for {max_epochs} epochs +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs={max_epochs}, val_interval={val_interval}) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval={loggerhook_interval}, log_metric_by_epoch=True), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=True, interval={checkpoint_interval}, save_best='mIoU', rule='greater'), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) +""" + + # --- 将生成的内容输出到界面 --- + print("\033[92m--- Generated Configuration Content ---\033[0m") + print(schedule_config_template) + print("\033[92m-------------------------------------\033[0m") + + # 创建目录(如果不存在的话) + os.makedirs(os.path.dirname(output_file), exist_ok=True) + + # 写入文件 + with open(output_file, 'w') as f: + f.write(schedule_config_template) + + print(f"\033[93m{output_file} file generated successfully\033[0m") + return True + +if __name__ == '__main__': + ########### 定义各参数 ########### + max_epochs = 300 # 训练总轮数 + val_interval = 1 # 每 1 个 epoch 验证一次 + checkpoint_interval = 10 # 每 10 个 epoch 保存一次模型 + loggerhook_interval = max_epochs # 日志间隔1次迭代 + + ########### 文件存储位置 ########### + # 文件名将反映 epoch 数量, e.g., schedule_300e.py + output_filename = f'schedule_{max_epochs}e.py' + output_configs_base_schedules_file = os.path.join('./configs/_base_/schedules/', output_filename) + + # 使用变量生成配置文件 + success = generate_epoch_based_schedule_file( + output_file=output_configs_base_schedules_file, + max_epochs=max_epochs, + val_interval=val_interval, + checkpoint_interval=checkpoint_interval, + loggerhook_interval=loggerhook_interval + ) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/Initial_Train_Gen_configs_base_schedules_schedule_XXk.py b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/Initial_Train_Gen_configs_base_schedules_schedule_XXk.py new file mode 100644 index 0000000..5db5913 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/Initial_Schedule_Program/Initial_Train_Gen_configs_base_schedules_schedule_XXk.py @@ -0,0 +1,57 @@ +import os + +def generate_times_configs_base_schedules_schedule_file(output_file, train_time_k=4, val_proportion=1/10, loggerhook_interval=50): + + # 定义模板 + schedule_config_template = f"""# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end={int(train_time_k*1000)}, + by_epoch=False) +] +# training schedule for {train_time_k}k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters={int(train_time_k*1000)}, val_interval={int(train_time_k*1000*val_proportion)}) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval={loggerhook_interval}, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval={int(train_time_k*1000*val_proportion)}, , save_best='mIoU', rule='greater'), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) +""" + + # 创建目录(如果不存在的话) + os.makedirs(os.path.dirname(output_file), exist_ok=True) + + # 写入文件 + with open(output_file, 'w') as f: + f.write(schedule_config_template) + + print(f"\033[93m{output_file} file generated successfully\033[0m") + return True + +if __name__ == '__main__': + ########### 定义各参数 ########### + train_time_k = 4 # 训练轮数(以k为单位) + + # 一般不太会变的参数 + val_proportion = 1/10 # 验证比例 + loggerhook_interval = 50 # 日志间隔50次迭代 + + checkpoint_interval = int(train_time_k*1000*val_proportion) # 训练多少轮保存pth # TODO 可自定义 + + ########### 文件存储位置 ########### + output_configs_base_schedules_schedules_Timek = os.path.join('./configs/_base_/schedules/', f'schedule_{train_time_k}k.py') + + # 使用变量生成配置文件 + success = generate_configs_base_schedules_schedule_file(output_file=output_configs_base_schedules_schedules_Timek, train_time_k=train_time_k, val_proportion=val_proportion, loggerhook_interval=loggerhook_interval) diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V1-.py b/Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V1-.py new file mode 100644 index 0000000..6457934 --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V1-.py @@ -0,0 +1,540 @@ +# -*- coding: utf-8 -*- +""" +MMSegmentation 自动化推理、评估与分析脚本 + +本脚本旨在为基于 MMSegmentation 训练的语义分割模型提供一个全面、自动化的后处理流水线。 +它能够系统地发现指定目录下的训练产物,并为每个模型执行一系列详尽的分析任务,最终生成结构化的报告。 + +核心功能: +1. 模型发现与检查点选择: + - 自动扫描指定输入目录(默认为 './Outputs_mmseg')下的所有子文件夹。 + - 智能选择用于推理的权重文件:优先选择 'best.pth',若不存在,则选择周期数(epoch)最大的检查点文件。 + - 定位与模型对应的配置文件(.py)和训练日志文件(.json)。 + +2. 验证集推理与结果生成: + - 对每个模型的验证集进行推理,并使用 tqdm 显示进度条。 + - 生成并保存原始的、未经着色的预测掩码图(predicted_raw_masks)。 + - 生成并保存包含“原始图像-预测图(着色后)-真值图标注(着色后)”的三图对比分析图像(prediction_analysis)。 + +3. 综合性能评估: + - 计算并记录多项关键评估指标,包括 IoU (mIoU), F1-Score (mFscore/mDice), Accuracy (aAcc/mAcc), Recall (mRecall), 和 Precision (mPrecision)。 + - 深入计算每个类别的混淆矩阵基本元素:真正例(TP)、真负例(TN)、假正例(FP)、假负例(FN)。这一功能通过对 IoUMetric 内部数据的再处理实现,提供了标准评估流程之外的精细化分析能力。 + +4. 模型复杂度分析: + - 计算模型的参数量(Parameters)和计算量(FLOPs),以评估其资源消耗。 + - 该计算基于 MMEngine 提供的底层分析工具,确保了与 MMSegmentation 官方工具的一致性。 + +5. 训练过程可视化: + - 解析训练日志文件(.json),提取损失(loss)、准确率(accuracy)、交并比(mIoU)等关键指标在训练过程中的变化。 + - 使用 Matplotlib 和 Seaborn 绘制指标变化曲线图,并保存为图像文件,便于直观分析模型的收敛情况。 + +6. 结构化结果输出: + - 所有生成的分析结果(指标、图像、日志图表)均被保存在一个结构化的输出目录中(默认为 './BestMode_Predict_Results_DataSet_Public')。 + - 每个模型的输出都存放在以其原始文件夹命名的子目录中,保持清晰的对应关系。 + - 最终的量化指标(包括总体指标、模型复杂度、逐类TP/TN/FP/FN)被整合并保存到一个名为 'test_set_metrics.csv' 的文件中,采用长格式(long format)存储,便于后续的数据处理和比较。 + +使用方法: + 直接在终端运行此脚本。可以通过命令行参数指定输入和输出的根目录。 + python predict.py --input_dir./Outputs_mmseg --output_dir./BestMode_Predict_Results_DataSet_Public + +依赖: + - torch, torchvision + - mmengine + - mmcv + - mmsegmentation>=1.0.0 + - numpy + - pandas + - matplotlib + - seaborn + - tqdm + - opencv-python +""" + +import os +import re +import glob +import json +import argparse +import logging +from typing import Dict, Optional, Tuple, List + +import cv2 +import numpy as np +import pandas as pd +import torch +import mmcv +import matplotlib.pyplot as plt +import seaborn as sns +from tqdm import tqdm + +from mmengine.config import Config +from mmengine.runner import Runner +from mmengine.analysis import get_model_complexity_info +from mmseg.apis import init_model, inference_model +from mmseg.evaluation.metrics import IoUMetric +from mmseg.visualization import SegLocalVisualizer +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample + +# --- 全局配置 --- +# 设置日志记录 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# 自动选择设备 +DEVICE = 'cuda:0' if torch.cuda.is_available() else 'cpu' +logging.info(f"使用设备: {DEVICE}") + +# --- 辅助函数 --- +def find_model_files(model_dir: str) -> Optional[Dict[str, str]]: + """ + 在给定的模型目录中查找配置文件、最佳检查点和日志文件。 + + Args: + model_dir (str): 模型的根目录。 + + Returns: + Optional]: 包含 'config', 'checkpoint', 'log' 路径的字典, + 如果缺少任何必要文件,则返回 None。 + """ + config_files = glob.glob(os.path.join(model_dir, '*.py')) + if not config_files: + logging.warning(f"在目录 {model_dir} 中未找到配置文件 (.py)。") + return None + config_path = config_files[0] + + checkpoint_path = os.path.join(model_dir, 'best.pth') + if not os.path.exists(checkpoint_path): + epoch_files = glob.glob(os.path.join(model_dir, 'epoch_*.pth')) + if not epoch_files: + logging.warning(f"在目录 {model_dir} 中未找到 'best.pth' 或 'epoch_*.pth' 检查点文件。") + return None + + # 通过正则表达式从文件名中提取周期数并找到最大的 + latest_epoch = -1 + latest_file = None + for f in epoch_files: + match = re.search(r'epoch_(\d+)\.pth', os.path.basename(f)) + if match: + epoch_num = int(match.group(1)) + if epoch_num > latest_epoch: + latest_epoch = epoch_num + latest_file = f + + if latest_file: + checkpoint_path = latest_file + else: + logging.warning(f"在目录 {model_dir} 中无法确定最新的检查点文件。") + return None + + + # log_files = glob.glob(os.path.join(model_dir, 'vis_data', '*.json')) + # if not log_files: + # logging.warning(f"在目录 {model_dir}/vis_data/ 中未找到日志文件 (.json)。") + # return None + # # 通常只有一个json日志文件,取第一个 + # log_path = log_files + + # return {'config': config_path, 'checkpoint': checkpoint_path, 'log': log_path} + return {'config': config_path, 'checkpoint': checkpoint_path} + +def calculate_model_complexity(cfg: Config) -> Dict[str, float]: + """ + 计算模型的 FLOPs 和参数量。 + + Args: + cfg (Config): 加载的模型配置对象。 + + Returns: + Dict[str, float]: 包含 'flops_G' 和 'params_M' 的字典。 + """ + try: + model = MODELS.build(cfg.model) + model.eval() + + # 从配置中获取输入尺寸,如果不存在则使用默认值 + if hasattr(cfg, 'crop_size'): + input_shape = (3, *cfg.crop_size) + else: + # 尝试从test_pipeline中查找 + input_shape = None + if hasattr(cfg, 'test_pipeline'): + for transform in cfg.test_pipeline: + if transform['type'] == 'Resize': + input_shape = (3, transform['scale'][1], transform['scale']) + break + if input_shape is None: + input_shape = (3, 512, 512) + logging.info(f"配置中未找到 'crop_size' 或 'Resize',使用默认输入尺寸 {input_shape} 进行 FLOPs 计算。") + + # 使用 MMEngine 的内置工具进行分析 + result = get_model_complexity_info(model, input_shape) + + flops_str = result.get('flops_str', '0.0G') + params_str = result.get('params_str', '0.0M') + + # 解析字符串以获取数值 + flops_g = float(re.search(r'(\d+\.?\d*)', flops_str).group(1)) + params_m = float(re.search(r'(\d+\.?\d*)', params_str).group(1)) + + # 单位转换 + if 'G' not in flops_str.upper(): + flops_g /= 1e3 # 假设是 MMac + if 'M' not in params_str.upper(): + params_m /= 1e6 # 假设是 K + + return {'flops_G': flops_g, 'params_M': params_m} + except Exception as e: + logging.error(f"计算模型复杂度时出错: {e}") + return {'flops_G': 0.0, 'params_M': 0.0} + +def analyze_training_log(log_path: str, output_dir: str): + """ + 解析训练日志文件并绘制指标曲线。 + + Args: + log_path (str): 训练日志文件 (.json) 的路径。 + output_dir (str): 保存绘图的目录。 + """ + try: + log_data = [] + with open(log_path, 'r') as f: + for line in f: + log_data.append(json.loads(line)) + + df = pd.DataFrame(log_data) + + # 提取关键指标 + metrics_to_plot = { + 'mIoU': 'mIoU', + 'mAcc': 'mAcc', + 'aAcc': 'aAcc', + 'loss': 'loss' + } + + # 筛选出验证和训练周期的行 + val_df = df[df['mode'] == 'val'].dropna(subset=['step']) + train_df = df[df['mode'] == 'train'].dropna(subset=['step']) + + sns.set_theme(style="whitegrid") + + for key, name in metrics_to_plot.items(): + plt.figure(figsize=(12, 6)) + + if key in val_df.columns: + sns.lineplot(data=val_df, x='step', y=key, label=f'Validation {name}') + + if key in train_df.columns and key == 'loss': + # 训练 loss 通常波动较大,可以进行平滑处理 + train_df[f'{key}_smooth'] = train_df[key].rolling(window=50, min_periods=1).mean() + sns.lineplot(data=train_df, x='step', y=f'{key}_smooth', label=f'Training {name} (Smoothed)') + + plt.title(f'{name} Curve during Training') + plt.xlabel('Training Steps') + plt.ylabel(name) + plt.legend() + plt.tight_layout() + save_path = os.path.join(output_dir, f'{name}_curve.png') + plt.savefig(save_path) + plt.close() + logging.info(f"已保存日志图表: {save_path}") + + except Exception as e: + logging.error(f"分析训练日志 {log_path} 时出错: {e}") + +def run_inference_and_evaluation(cfg: Config, checkpoint_path: str, output_dir: str) -> Dict: + """ + 执行模型推理、评估和可视化。 + + 该函数通过一次性遍历验证数据集来高效地完成所有任务,避免了重复的数据加载和模型前向传播。 + + Args: + cfg (Config): 加载的模型配置对象。 + checkpoint_path (str): 模型检查点文件的路径。 + output_dir (str): 保存所有输出的根目录。 + + Returns: + Dict: 包含评估指标和逐类 TP/TN/FP/FN 计数的字典。 + """ + # --- 1. 初始化 --- + model = init_model(cfg, checkpoint_path, device=DEVICE) + + # 以编程方式构建数据加载器,确保与训练时的数据预处理流程一致 + val_dataloader = Runner.build_dataloader(cfg.val_dataloader) + + # 实例化评估器和可视化器 + metric = IoUMetric(iou_metrics=['mIoU']) + visualizer = SegLocalVisualizer(save_dir=os.path.join(output_dir, 'prediction_analysis')) + + # 关键步骤:为可视化器设置数据集元信息(类别名和调色板) + visualizer.dataset_meta = val_dataloader.dataset.metainfo + + metric.dataset_meta = val_dataloader.dataset.metainfo + + # --- 2. 创建输出子目录 --- + raw_mask_dir = os.path.join(output_dir, 'predicted_raw_masks') + viz_dir = os.path.join(output_dir, 'prediction_analysis') + os.makedirs(raw_mask_dir, exist_ok=True) + os.makedirs(viz_dir, exist_ok=True) + + # --- 3. 推理循环 --- + model.eval() + metric.results = [] # 清空历史结果 + with torch.no_grad(): + for data in tqdm(val_dataloader, desc=f"推理与评估: {os.path.basename(output_dir)}"): + # 将数据移动到指定设备 + # inputs = data['inputs'].to(DEVICE) # 有误 + inputs = data['inputs'][0].to(DEVICE).float().unsqueeze(0) + data_samples =data['data_samples'] + + # 执行推理 + result = model(inputs, data_samples=data_samples, mode='predict') + + # 将预测结果合并到 GT 样本中,以便于评估和可视化 + for i in range(len(data_samples)): + data_samples[i].pred_sem_seg = result[i].pred_sem_seg + + # 使用评估器的 process 方法处理一个批次的结果 + predictions_as_dicts = [r.to_dict() for r in result] + metric.process(data, predictions_as_dicts) + + # 保存原始预测掩码和可视化结果 + for sample in data_samples: + pred_mask = sample.pred_sem_seg.data.squeeze().cpu().numpy().astype(np.uint8) + img_filename = os.path.basename(sample.img_path) + cv2.imwrite(os.path.join(raw_mask_dir, img_filename), pred_mask) + + # 生成并保存三图对比的可视化结果 + image = mmcv.imread(sample.img_path) + visualizer.add_datasample( + name=os.path.splitext(img_filename), + image=image, + data_sample=sample, + show=False + ) + + # --- 4. 计算最终指标 --- + metrics_summary = metric.compute_metrics(metric.results) + + # --- 5. 派生 TP, TN, FP, FN --- + if not metric.results: + logging.error("评估结果为空,无法计算 TP/TN/FP/FN。") + return {'summary': metrics_summary, 'per_class': {}} + + # 聚合所有批次的结果 + total_area_intersect = torch.stack([res for res in metric.results]).sum(0) + total_area_union = torch.stack([res[1] for res in metric.results]).sum(0) + total_area_pred_label = torch.stack([res[2] for res in metric.results]).sum(0) + total_area_label = torch.stack([res[3] for res in metric.results]).sum(0) + + # TP: 预测为正类,实际也为正类 (交集) [4] + tp = total_area_intersect.numpy() + # FP: 预测为正类,实际为负类 (预测区域 - 交集) [4] + fp = (total_area_pred_label - total_area_intersect).numpy() + # FN: 预测为负类,实际为正类 (真值区域 - 交集) [4] + fn = (total_area_label - total_area_intersect).numpy() + + # TN: 预测为负类,实际也为负类 + # TN_i = total_valid_pixels - (TP_i + FP_i + FN_i) + # total_valid_pixels = sum of all elements in confusion matrix = sum of all ground truth pixels + total_valid_pixels = total_area_label.sum().item() * len(val_dataloader.dataset) / sum(len(b['inputs']) for b in val_dataloader) # 估算 + union = total_area_pred_label + total_area_label - total_area_intersect + tn = total_valid_pixels - union.numpy() + + per_class_metrics = {'tp': tp, 'tn': tn, 'fp': fp, 'fn': fn} + + return {'summary': metrics_summary, 'per_class': per_class_metrics} + +def consolidate_and_save_results(metrics_data: Dict, complexity_data: Dict, metainfo: Dict, output_dir: str): + """ + 整合所有量化指标并保存为 CSV 文件。 + + Args: + metrics_data (Dict): 来自 run_inference_and_evaluation 的指标数据。 + complexity_data (Dict): 来自 calculate_model_complexity 的复杂度数据。 + metainfo (Dict): 数据集的元信息,包含类别名。 + output_dir (str): 保存 CSV 文件的目录。 + """ + num_classes = len(metainfo['classes']) + class_names = metainfo['classes'] + + rows = [] + + # 添加总体指标 + summary = metrics_data.get('summary', {}) + rows.append({'metric': 'iou_score', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('mIoU', 0)}) + rows.append({'metric': 'f1_score', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('mFscore', summary.get('mDice', 0))}) + rows.append({'metric': 'accuracy', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('aAcc', 0)}) + rows.append({'metric': 'recall', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('mRecall', 0)}) + rows.append({'metric': 'precision', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('mPrecision', 0)}) + + # 添加模型复杂度 + rows.append({'metric': 'params_M', 'class_id': -1, 'class_name': 'N/A', 'value': complexity_data.get('params_M', 0)}) + rows.append({'metric': 'flops_G', 'class_id': -1, 'class_name': 'N/A', 'value': complexity_data.get('flops_G', 0)}) + + # 添加逐类 TP/TN/FP/FN + per_class = metrics_data.get('per_class', {}) + for metric_name, values in per_class.items(): + if len(values) == num_classes: + for i in range(num_classes): + rows.append({ + 'metric': metric_name, + 'class_id': i, + 'class_name': class_names[i], + 'value': values[i] + }) + + df = pd.DataFrame(rows) + save_path = os.path.join(output_dir, 'prediction_analysis', 'test_set_metrics.csv') + df.to_csv(save_path, index=False) + logging.info(f"所有量化指标已保存至: {save_path}") + +# --- 主函数 --- +def main(args): + """ + 脚本主入口,负责编排整个自动化分析流程。 + """ + input_root = args.input_dir + output_root = args.output_dir + # --- 开始交互式选择修改 (V2 - 两级菜单) --- + if not os.path.isdir(input_root): + logging.error(f"输入目录不存在: {input_root}") + return + + # 1. 定义有效的数据集文件夹白名单 + VALID_DATASET_FOLDERS = [ + '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_AutoLaparo-10Type-1920x1080_outputs-MMSeg', + '3_1_Endovis_2017-8Type-512x512_outputs-MMSeg', + '3_2_Endovis_2018-8Type-512x512_outputs-MMSeg', + '4_Dresden-11Type-512x512_outputs-MMSeg' + ] + + # 2. 查找存在的、有效的数据集目录 + existing_dataset_dirs = [ + os.path.join(input_root, d) for d in VALID_DATASET_FOLDERS + if os.path.isdir(os.path.join(input_root, d)) + ] + + if not existing_dataset_dirs: + logging.error(f"在输入目录 {input_root} 中未找到任何有效的数据集文件夹。") + return + + # 3. 第一级菜单:选择数据集 + dataset_map = {str(i + 1): path for i, path in enumerate(existing_dataset_dirs)} + + print("\n" + "="*50) + print("--- 步骤 1: 请选择要处理的数据集 ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice1 = input("请输入数据集编号并按回车键: ").strip() + + model_dirs = [] # 初始化最终要处理的目录列表 + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"您已选择数据集: [{os.path.basename(selected_dataset_dir)}]") + + # 4. 查找选定数据集下的所有算法子目录 + alg_dirs = sorted([ + d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d) + ]) + + if not alg_dirs: + logging.warning(f"在 {os.path.basename(selected_dataset_dir)} 中未发现任何算法子文件夹。程序退出。") + else: + # 5. 第二级菜单:选择算法 + alg_map = {str(i + 1): path for i, path in enumerate(alg_dirs)} + print("\n" + "="*50) + print("--- 步骤 2: 请选择要处理的算法 ---") + print("0: 批量处理当前数据集下的【全部】算法") + for key, path in alg_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice2 = input("请输入算法编号 (或输入 '0' 处理全部) 并按回车键: ").strip() + + # 6. 根据第二级选择,最终确定 model_dirs + if choice2 == '0': + model_dirs = alg_dirs + logging.info(f"您选择了批量处理全部 {len(model_dirs)} 个算法。") + elif choice2 in alg_map: + model_dirs = [alg_map[choice2]] # 将单个路径放入列表中 + logging.info(f"您选择了处理单个算法: {os.path.basename(model_dirs[0])}") + else: + logging.error("无效的算法选择,程序已退出。") + else: + logging.error("无效的数据集选择,程序已退出。") + + # --- 交互式选择修改结束 --- + + # 修改后的循环,将遍历经过用户筛选后的 model_dirs 列表 + for model_dir in model_dirs: + model_name = os.path.basename(model_dir) + logging.info(f"--- 开始处理模型: {model_name} ---") + + files = find_model_files(model_dir) + if not files: + logging.warning(f"跳过目录 {model_dir},因为缺少必要文件。") + continue + + # 构建输出目录 + # 从模型名中提取数据集标识作为Key + dataset_key = model_name.split('-')[0] + dataset_map = { + '1_cholecseg8k': '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_autolaparo': '2_AutoLaparo-10Type-1280x1024_outputs-MMSeg', + '3_1_endovis_2017': '3_1_EndoVis_2017-7Type-1280x1024_outputs-MMSeg', + '3_2_endovis_2018': '3_2_EndoVis_2018-11Type-1280x1024_outputs-MMSeg', + '4_dresden': '4_Dresden-6Type-1920x1080_outputs-MMSeg' + } + # 使用提取的Key(字符串)进行查询,并为默认值也使用该Key + output_dataset_folder = dataset_map.get(dataset_key, f"{dataset_key}_outputs-MMSeg") + + final_output_dir = os.path.join(output_root, output_dataset_folder, model_name) + os.makedirs(final_output_dir, exist_ok=True) + logging.info(f"结果将保存至: {final_output_dir}") + + try: + # 加载配置 + cfg = Config.fromfile(files['config']) + + # 1. 执行推理、评估和可视化 + metrics_data = run_inference_and_evaluation(cfg, files['checkpoint'], final_output_dir) + + # 2. 分析训练日志 + analyze_training_log(files['log'], os.path.join(final_output_dir, 'prediction_analysis')) + + # 3. 计算模型复杂度 + complexity_data = calculate_model_complexity(cfg) + + # 4. 整合并保存所有量化结果 + val_dataloader = Runner.build_dataloader(cfg.val_dataloader) + metainfo = val_dataloader.dataset.metainfo + + consolidate_and_save_results(metrics_data, complexity_data, metainfo, final_output_dir) + + logging.info(f"--- 模型 {model_name} 处理完成 ---") + + except Exception as e: + logging.error(f"处理模型 {model_name} 时发生严重错误: {e}", exc_info=True) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="MMSegmentation 自动化评估脚本") + parser.add_argument( + '--input_dir', + type=str, + default='../Hardisk', + help="包含已训练模型文件夹的根目录。" + ) + parser.add_argument( + '--output_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="用于存储所有分析结果的根目录。" + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V2-.py b/Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V2-.py new file mode 100644 index 0000000..e6a3e7c --- /dev/null +++ b/Seg_All_In_One_MMSeg/My_All_In_One/x4_Predict_V2-.py @@ -0,0 +1,613 @@ +# -*- coding: utf-8 -*- +""" +MMSegmentation 自动化推理、评估与分析脚本 + +本脚本旨在为基于 MMSegmentation 训练的语义分割模型提供一个全面、自动化的后处理流水线。 +它能够系统地发现指定目录下的训练产物,并为每个模型执行一系列详尽的分析任务,最终生成结构化的报告。 + +核心功能: +1. 模型发现与检查点选择: + - 自动扫描指定输入目录(默认为 './Outputs_mmseg')下的所有子文件夹。 + - 智能选择用于推理的权重文件:优先选择 'best.pth',若不存在,则选择周期数(epoch)最大的检查点文件。 + - 定位与模型对应的配置文件(.py)和训练日志文件(.json)。 + +2. 验证集推理与结果生成: + - 对每个模型的验证集进行推理,并使用 tqdm 显示进度条。 + - 生成并保存原始的、未经着色的预测掩码图(predicted_raw_masks)。 + - 生成并保存包含“原始图像-预测图(着色后)-真值图标注(着色后)”的三图对比分析图像(prediction_analysis)。 + +3. 综合性能评估: + - 计算并记录多项关键评估指标,包括 IoU (mIoU), F1-Score (mFscore/mDice), Accuracy (aAcc/mAcc), Recall (mRecall), 和 Precision (mPrecision)。 + - 深入计算每个类别的混淆矩阵基本元素:真正例(TP)、真负例(TN)、假正例(FP)、假负例(FN)。这一功能通过对 IoUMetric 内部数据的再处理实现,提供了标准评估流程之外的精细化分析能力。 + +4. 模型复杂度分析: + - 计算模型的参数量(Parameters)和计算量(FLOPs),以评估其资源消耗。 + - 该计算基于 MMEngine 提供的底层分析工具,确保了与 MMSegmentation 官方工具的一致性。 + +5. 训练过程可视化: + - 解析训练日志文件(.json),提取损失(loss)、准确率(accuracy)、交并比(mIoU)等关键指标在训练过程中的变化。 + - 使用 Matplotlib 和 Seaborn 绘制指标变化曲线图,并保存为图像文件,便于直观分析模型的收敛情况。 + +6. 结构化结果输出: + - 所有生成的分析结果(指标、图像、日志图表)均被保存在一个结构化的输出目录中(默认为 './BestMode_Predict_Results_DataSet_Public')。 + - 每个模型的输出都存放在以其原始文件夹命名的子目录中,保持清晰的对应关系。 + - 最终的量化指标(包括总体指标、模型复杂度、逐类TP/TN/FP/FN)被整合并保存到一个名为 'test_set_metrics.csv' 的文件中,采用长格式(long format)存储,便于后续的数据处理和比较。 + +使用方法: + 直接在终端运行此脚本。可以通过命令行参数指定输入和输出的根目录。 + python predict.py --input_dir./Outputs_mmseg --output_dir./BestMode_Predict_Results_DataSet_Public + +依赖: + - torch, torchvision + - mmengine + - mmcv + - mmsegmentation>=1.0.0 + - numpy + - pandas + - matplotlib + - seaborn + - tqdm + - opencv-python +""" + +import os +import re +import glob +import json +import argparse +import logging +from typing import Dict, Optional, Tuple, List + +import cv2 +import numpy as np +import pandas as pd +import torch +import mmcv +import matplotlib.pyplot as plt +import seaborn as sns +from tqdm import tqdm + +from mmengine.config import Config +from mmengine.runner import Runner +from mmengine.analysis import get_model_complexity_info +from mmseg.apis import init_model, inference_model +from mmseg.evaluation.metrics import IoUMetric +from mmseg.visualization import SegLocalVisualizer +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample + +# --- 全局配置 --- +# 设置日志记录 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# 自动选择设备 +DEVICE = 'cuda:0' if torch.cuda.is_available() else 'cpu' +logging.info(f"使用设备: {DEVICE}") + +# --- 辅助函数 --- +def find_model_files(model_dir: str) -> Optional[Dict[str, str]]: + """ + 在给定的模型目录中查找配置文件、最佳检查点和日志文件。 + + Args: + model_dir (str): 模型的根目录。 + + Returns: + Optional]: 包含 'config', 'checkpoint', 'log' 路径的字典, + 如果缺少任何必要文件,则返回 None。 + """ + config_files = glob.glob(os.path.join(model_dir, '*.py')) + if not config_files: + logging.warning(f"在目录 {model_dir} 中未找到配置文件 (.py)。") + return None + config_path = config_files[0] + + checkpoint_path = os.path.join(model_dir, 'best.pth') + if not os.path.exists(checkpoint_path): + epoch_files = glob.glob(os.path.join(model_dir, 'epoch_*.pth')) + if not epoch_files: + logging.warning(f"在目录 {model_dir} 中未找到 'best.pth' 或 'epoch_*.pth' 检查点文件。") + return None + + # 通过正则表达式从文件名中提取周期数并找到最大的 + latest_epoch = -1 + latest_file = None + for f in epoch_files: + match = re.search(r'epoch_(\d+)\.pth', os.path.basename(f)) + if match: + epoch_num = int(match.group(1)) + if epoch_num > latest_epoch: + latest_epoch = epoch_num + latest_file = f + + if latest_file: + checkpoint_path = latest_file + else: + logging.warning(f"在目录 {model_dir} 中无法确定最新的检查点文件。") + return None + + + # log_files = glob.glob(os.path.join(model_dir, 'vis_data', '*.json')) + # if not log_files: + # logging.warning(f"在目录 {model_dir}/vis_data/ 中未找到日志文件 (.json)。") + # return None + # # 通常只有一个json日志文件,取第一个 + # log_path = log_files + + # return {'config': config_path, 'checkpoint': checkpoint_path, 'log': log_path} + return {'config': config_path, 'checkpoint': checkpoint_path} + +def calculate_model_complexity(cfg: Config) -> Dict[str, float]: + """ + 计算模型的 FLOPs 和参数量。 + + Args: + cfg (Config): 加载的模型配置对象。 + + Returns: + Dict[str, float]: 包含 'flops_G' 和 'params_M' 的字典。 + """ + try: + model = MODELS.build(cfg.model) + model.eval() + + # 从配置中获取输入尺寸,如果不存在则使用默认值 + if hasattr(cfg, 'crop_size'): + input_shape = (3, *cfg.crop_size) + else: + # 尝试从test_pipeline中查找 + input_shape = None + if hasattr(cfg, 'test_pipeline'): + for transform in cfg.test_pipeline: + if transform['type'] == 'Resize': + input_shape = (3, transform['scale'][1], transform['scale']) + break + if input_shape is None: + input_shape = (3, 512, 512) + logging.info(f"配置中未找到 'crop_size' 或 'Resize',使用默认输入尺寸 {input_shape} 进行 FLOPs 计算。") + + # 使用 MMEngine 的内置工具进行分析 + result = get_model_complexity_info(model, input_shape) + + flops_str = result.get('flops_str', '0.0G') + params_str = result.get('params_str', '0.0M') + + # 解析字符串以获取数值 + flops_g = float(re.search(r'(\d+\.?\d*)', flops_str).group(1)) + params_m = float(re.search(r'(\d+\.?\d*)', params_str).group(1)) + + # 单位转换 + if 'G' not in flops_str.upper(): + flops_g /= 1e3 # 假设是 MMac + if 'M' not in params_str.upper(): + params_m /= 1e6 # 假设是 K + + return {'flops_G': flops_g, 'params_M': params_m} + except Exception as e: + logging.error(f"计算模型复杂度时出错: {e}") + return {'flops_G': 0.0, 'params_M': 0.0} + +def analyze_training_log(log_path: str, output_dir: str): + """ + 解析训练日志文件并绘制指标曲线。 + + Args: + log_path (str): 训练日志文件 (.json) 的路径。 + output_dir (str): 保存绘图的目录。 + """ + try: + log_data = [] + with open(log_path, 'r') as f: + for line in f: + log_data.append(json.loads(line)) + + df = pd.DataFrame(log_data) + + # 提取关键指标 + metrics_to_plot = { + 'mIoU': 'mIoU', + 'mAcc': 'mAcc', + 'aAcc': 'aAcc', + 'loss': 'loss' + } + + # 筛选出验证和训练周期的行 + val_df = df[df['mode'] == 'val'].dropna(subset=['step']) + train_df = df[df['mode'] == 'train'].dropna(subset=['step']) + + sns.set_theme(style="whitegrid") + + for key, name in metrics_to_plot.items(): + plt.figure(figsize=(12, 6)) + + if key in val_df.columns: + sns.lineplot(data=val_df, x='step', y=key, label=f'Validation {name}') + + if key in train_df.columns and key == 'loss': + # 训练 loss 通常波动较大,可以进行平滑处理 + train_df[f'{key}_smooth'] = train_df[key].rolling(window=50, min_periods=1).mean() + sns.lineplot(data=train_df, x='step', y=f'{key}_smooth', label=f'Training {name} (Smoothed)') + + plt.title(f'{name} Curve during Training') + plt.xlabel('Training Steps') + plt.ylabel(name) + plt.legend() + plt.tight_layout() + save_path = os.path.join(output_dir, f'{name}_curve.png') + plt.savefig(save_path) + plt.close() + logging.info(f"已保存日志图表: {save_path}") + + except Exception as e: + logging.error(f"分析训练日志 {log_path} 时出错: {e}") + +def run_inference_and_evaluation(cfg: Config, checkpoint_path: str, output_dir: str) -> Dict: + """ + 执行模型推理、评估和可视化。 + + 该函数通过一次性遍历验证数据集来高效地完成所有任务,避免了重复的数据加载和模型前向传播。 + + Args: + cfg (Config): 加载的模型配置对象。 + checkpoint_path (str): 模型检查点文件的路径。 + output_dir (str): 保存所有输出的根目录。 + + Returns: + Dict: 包含评估指标和逐类 TP/TN/FP/FN 计数的字典。 + """ + # --- 1. 初始化 --- + model = init_model(cfg, checkpoint_path, device=DEVICE) + + # 以编程方式构建数据加载器,确保与训练时的数据预处理流程一致 + # V1. + # train_dataloader = dict( + # batch_size=1, + # dataset=dict( + # data_prefix=dict(img_path='images/train', seg_map_path='labels_GT/train'), + # data_root= + # '/home/wkmgc/Desktop/Seg/DataSet_Public/3_1_Endovis_2017-8Type-512x512', + # pipeline=[ + # dict(type='LoadImageFromFile'), + # dict(keep_ratio=True, scale=( + # 512, + # 512, + # ), type='Resize'), + # dict(type='LoadAnnotations'), + # dict(type='PackSegInputs'), + # ], + # type='PublicDataSet_Endovis_2017'), + # num_workers=4, + # persistent_workers=True, + # sampler=dict(shuffle=False, type='DefaultSampler')) + # val_dataloader = Runner.build_dataloader(train_dataloader) # TODO cfg.val_dataloader 修改为 cfg.train_dataloader + + # V2. + val_dataloader = Runner.build_dataloader(cfg.val_dataloader) + + # 实例化评估器和可视化器 + metric = IoUMetric(iou_metrics=['mIoU']) + visualizer = SegLocalVisualizer(save_dir=os.path.join(output_dir, 'prediction_analysis')) + + # 关键步骤:为可视化器设置数据集元信息(类别名和调色板) + visualizer.dataset_meta = val_dataloader.dataset.metainfo + + metric.dataset_meta = val_dataloader.dataset.metainfo + + # --- 2. 创建输出子目录 --- + raw_mask_dir = os.path.join(output_dir, 'predicted_raw_masks') + viz_dir = os.path.join(output_dir, 'prediction_analysis') + os.makedirs(raw_mask_dir, exist_ok=True) + os.makedirs(viz_dir, exist_ok=True) + + # --- 3. 推理循环 --- + model.eval() + metric.results = [] # 清空历史结果 + with torch.no_grad(): + for data in tqdm(val_dataloader, desc=f"推理与评估: {os.path.basename(output_dir)}"): + # 将数据移动到指定设备 + # inputs = data['inputs'].to(DEVICE) # 有误 + inputs = data['inputs'][0].to(DEVICE).float().unsqueeze(0) + data_samples =data['data_samples'] + + # 执行推理 + result = model(inputs, data_samples=data_samples, mode='predict') + + # 将预测结果合并到 GT 样本中,以便于评估和可视化 + for i in range(len(data_samples)): + data_samples[i].pred_sem_seg = result[i].pred_sem_seg + + # 使用评估器的 process 方法处理一个批次的结果 + predictions_as_dicts = [r.to_dict() for r in result] + metric.process(data, predictions_as_dicts) + + # 保存原始预测掩码和可视化结果 + for sample in data_samples: + pred_mask = sample.pred_sem_seg.data.squeeze().cpu().numpy().astype(np.uint8) + img_filename = os.path.basename(sample.img_path) + cv2.imwrite(os.path.join(raw_mask_dir, img_filename), pred_mask) + + # --- 开始修改:手动生成并拼接三图对比的可视化结果 --- + # 1. 读取原始图像 + original_img = mmcv.imread(sample.img_path) + # 2. 生成着色后的预测图 + # 从 sample 中获取预测结果的张量 + pred_mask_tensor = sample.pred_sem_seg.data + # 调用可视化器的内部方法进行绘制 + predicted_img_colored = visualizer._draw_sem_seg( + image=original_img.copy(), + sem_seg=pred_mask_tensor, + classes=visualizer.dataset_meta.get('classes'), + palette=visualizer.dataset_meta.get('palette') + ) + # 3. 生成着色后的真值图 + # 从 sample 中获取真值标签的张量 + gt_mask_tensor = sample.gt_sem_seg.data + # 调用可视化器的内部方法进行绘制 + gt_img_colored = visualizer._draw_sem_seg( + image=original_img.copy(), + sem_seg=gt_mask_tensor, + classes=visualizer.dataset_meta.get('classes'), + palette=visualizer.dataset_meta.get('palette') + ) + # 4. 将三张图水平拼接 + # 确保所有图像的数据类型一致,以便拼接 + original_img = original_img.astype(np.uint8) + predicted_img_colored = predicted_img_colored.astype(np.uint8) + gt_img_colored = gt_img_colored.astype(np.uint8) + # 使用 numpy.hstack 进行水平拼接 + comparison_image = np.hstack([original_img, predicted_img_colored, gt_img_colored]) + # 5. 保存拼接后的图像 + save_path = os.path.join(viz_dir, img_filename) + cv2.imwrite(save_path, comparison_image) + # --- 修改结束 --- + + # --- 4. 计算最终指标 --- + metrics_summary = metric.compute_metrics(metric.results) + + # --- 5. 派生 TP, TN, FP, FN --- + if not metric.results: + logging.error("评估结果为空,无法计算 TP/TN/FP/FN。") + return {'summary': metrics_summary, 'per_class': {}} + + # 聚合所有批次的结果 + total_area_intersect = torch.stack([res for res in metric.results]).sum(0) + total_area_union = torch.stack([res[1] for res in metric.results]).sum(0) + total_area_pred_label = torch.stack([res[2] for res in metric.results]).sum(0) + total_area_label = torch.stack([res[3] for res in metric.results]).sum(0) + + # TP: 预测为正类,实际也为正类 (交集) [4] + tp = total_area_intersect.numpy() + # FP: 预测为正类,实际为负类 (预测区域 - 交集) [4] + fp = (total_area_pred_label - total_area_intersect).numpy() + # FN: 预测为负类,实际为正类 (真值区域 - 交集) [4] + fn = (total_area_label - total_area_intersect).numpy() + + # TN: 预测为负类,实际也为负类 + # TN_i = total_valid_pixels - (TP_i + FP_i + FN_i) + # total_valid_pixels = sum of all elements in confusion matrix = sum of all ground truth pixels + total_valid_pixels = total_area_label.sum().item() * len(val_dataloader.dataset) / sum(len(b['inputs']) for b in val_dataloader) # 估算 + union = total_area_pred_label + total_area_label - total_area_intersect + tn = total_valid_pixels - union.numpy() + + per_class_metrics = {'tp': tp, 'tn': tn, 'fp': fp, 'fn': fn} + + return {'summary': metrics_summary, 'per_class': per_class_metrics} + +def consolidate_and_save_results(metrics_data: Dict, complexity_data: Dict, metainfo: Dict, output_dir: str): + """ + 整合所有量化指标并保存为 CSV 文件。 + + Args: + metrics_data (Dict): 来自 run_inference_and_evaluation 的指标数据。 + complexity_data (Dict): 来自 calculate_model_complexity 的复杂度数据。 + metainfo (Dict): 数据集的元信息,包含类别名。 + output_dir (str): 保存 CSV 文件的目录。 + """ + num_classes = len(metainfo['classes']) + class_names = metainfo['classes'] + + rows = [] + + # 添加总体指标 + summary = metrics_data.get('summary', {}) + rows.append({'metric': 'iou_score', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('mIoU', 0)}) + rows.append({'metric': 'f1_score', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('mFscore', summary.get('mDice', 0))}) + rows.append({'metric': 'accuracy', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('aAcc', 0)}) + rows.append({'metric': 'recall', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('mRecall', 0)}) + rows.append({'metric': 'precision', 'class_id': -1, 'class_name': 'Overall', 'value': summary.get('mPrecision', 0)}) + + # 添加模型复杂度 + rows.append({'metric': 'params_M', 'class_id': -1, 'class_name': 'N/A', 'value': complexity_data.get('params_M', 0)}) + rows.append({'metric': 'flops_G', 'class_id': -1, 'class_name': 'N/A', 'value': complexity_data.get('flops_G', 0)}) + + # 添加逐类 TP/TN/FP/FN + per_class = metrics_data.get('per_class', {}) + for metric_name, values in per_class.items(): + if len(values) == num_classes: + for i in range(num_classes): + rows.append({ + 'metric': metric_name, + 'class_id': i, + 'class_name': class_names[i], + 'value': values[i] + }) + + df = pd.DataFrame(rows) + save_path = os.path.join(output_dir, 'prediction_analysis', 'test_set_metrics.csv') + df.to_csv(save_path, index=False) + logging.info(f"所有量化指标已保存至: {save_path}") + +# --- 主函数 --- +def main(args): + """ + 脚本主入口,负责编排整个自动化分析流程。 + """ + input_root = args.input_dir + output_root = args.output_dir + # --- 开始交互式选择修改 (V2 - 两级菜单) --- + if not os.path.isdir(input_root): + logging.error(f"输入目录不存在: {input_root}") + return + + # 1. 定义有效的数据集文件夹白名单 + VALID_DATASET_FOLDERS = [ + '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_AutoLaparo-10Type-1920x1080_outputs-MMSeg', + '3_1_Endovis_2017-8Type-512x512_outputs-MMSeg', + '3_2_Endovis_2018-8Type-512x512_outputs-MMSeg', + '4_Dresden-11Type-512x512_outputs-MMSeg' + ] + + # 2. 查找存在的、有效的数据集目录 + existing_dataset_dirs = [ + os.path.join(input_root, d) for d in VALID_DATASET_FOLDERS + if os.path.isdir(os.path.join(input_root, d)) + ] + + if not existing_dataset_dirs: + logging.error(f"在输入目录 {input_root} 中未找到任何有效的数据集文件夹。") + return + + # 3. 第一级菜单:选择数据集 + dataset_map = {str(i + 1): path for i, path in enumerate(existing_dataset_dirs)} + + print("\n" + "="*50) + print("--- 步骤 1: 请选择要处理的数据集 ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice1 = input("请输入数据集编号并按回车键: ").strip() + + model_dirs = [] # 初始化最终要处理的目录列表 + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"您已选择数据集: [{os.path.basename(selected_dataset_dir)}]") + + # 4. 查找选定数据集下的所有算法子目录 + alg_dirs = sorted([ + d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d) + ]) + + if not alg_dirs: + logging.warning(f"在 {os.path.basename(selected_dataset_dir)} 中未发现任何算法子文件夹。程序退出。") + else: + # 5. 第二级菜单:选择算法 + alg_map = {str(i + 1): path for i, path in enumerate(alg_dirs)} + print("\n" + "="*50) + print("--- 步骤 2: 请选择要处理的算法 ---") + print("0: 批量处理当前数据集下的【全部】算法") + for key, path in alg_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice2 = input("请输入算法编号 (或输入 '0' 处理全部) 并按回车键: ").strip() + + # 6. 根据第二级选择,最终确定 model_dirs + if choice2 == '0': + model_dirs = alg_dirs + logging.info(f"您选择了批量处理全部 {len(model_dirs)} 个算法。") + elif choice2 in alg_map: + model_dirs = [alg_map[choice2]] # 将单个路径放入列表中 + logging.info(f"您选择了处理单个算法: {os.path.basename(model_dirs[0])}") + else: + logging.error("无效的算法选择,程序已退出。") + else: + logging.error("无效的数据集选择,程序已退出。") + + # --- 交互式选择修改结束 --- + + # 修改后的循环,将遍历经过用户筛选后的 model_dirs 列表 + for model_dir in model_dirs: + model_name = os.path.basename(model_dir) + logging.info(f"--- 开始处理模型: {model_name} ---") + + files = find_model_files(model_dir) + if not files: + logging.warning(f"跳过目录 {model_dir},因为缺少必要文件。") + continue + + # 构建输出目录 + # 从模型名中提取数据集标识作为Key + dataset_key = model_name.split('-')[0] + dataset_map = { + '1_cholecseg8k': '1_CholecSeg8k-13Type-1920x1080_outputs-MMSeg', + '2_autolaparo': '2_AutoLaparo-10Type-1280x1024_outputs-MMSeg', + '3_1_endovis_2017': '3_1_EndoVis_2017-7Type-1280x1024_outputs-MMSeg', + '3_2_endovis_2018': '3_2_EndoVis_2018-11Type-1280x1024_outputs-MMSeg', + '4_dresden': '4_Dresden-6Type-1920x1080_outputs-MMSeg' + } + # 使用提取的Key(字符串)进行查询,并为默认值也使用该Key + output_dataset_folder = dataset_map.get(dataset_key, f"{dataset_key}_outputs-MMSeg") + + final_output_dir = os.path.join(output_root, output_dataset_folder, model_name) + os.makedirs(final_output_dir, exist_ok=True) + logging.info(f"结果将保存至: {final_output_dir}") + + try: + # 加载配置 + cfg = Config.fromfile(files['config']) + + # 1. 执行推理、评估和可视化 + metrics_data = run_inference_and_evaluation(cfg, files['checkpoint'], final_output_dir) + + # 2. 分析训练日志 + # analyze_training_log(files['log'], os.path.join(final_output_dir, 'prediction_analysis')) + + # 3. 计算模型复杂度 + complexity_data = calculate_model_complexity(cfg) + + # 4. 整合并保存所有量化结果 + # V1. + # train_dataloader = dict( + # batch_size=1, + # dataset=dict( + # data_prefix=dict(img_path='images/train', seg_map_path='labels_GT/train'), + # data_root= + # '/home/wkmgc/Desktop/Seg/DataSet_Public/3_1_Endovis_2017-8Type-512x512', + # pipeline=[ + # dict(type='LoadImageFromFile'), + # dict(keep_ratio=True, scale=( + # 512, + # 512, + # ), type='Resize'), + # dict(type='LoadAnnotations'), + # dict(type='PackSegInputs'), + # ], + # type='PublicDataSet_Endovis_2017'), + # num_workers=4, + # persistent_workers=True, + # sampler=dict(shuffle=False, type='DefaultSampler')) + # val_dataloader = Runner.build_dataloader(train_dataloader) # TODO cfg.val_dataloader 修改为 cfg.train_dataloader + + # V2. + val_dataloader = Runner.build_dataloader(cfg.val_dataloader) + + metainfo = val_dataloader.dataset.metainfo + + consolidate_and_save_results(metrics_data, complexity_data, metainfo, final_output_dir) + + logging.info(f"--- 模型 {model_name} 处理完成 ---") + + except Exception as e: + logging.error(f"处理模型 {model_name} 时发生严重错误: {e}", exc_info=True) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="MMSegmentation 自动化评估脚本") + parser.add_argument( + '--input_dir', + type=str, + default='../Hardisk', + help="包含已训练模型文件夹的根目录。" + ) + parser.add_argument( + '--output_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="用于存储所有分析结果的根目录。" + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/README.md b/Seg_All_In_One_MMSeg/README.md new file mode 100644 index 0000000..79ecdb7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/README.md @@ -0,0 +1,420 @@ +
+ +
 
+
+ OpenMMLab website + + + HOT + + +      + OpenMMLab platform + + + TRY IT OUT + + +
+
 
+ +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mmsegmentation)](https://pypi.org/project/mmsegmentation/) +[![PyPI](https://img.shields.io/pypi/v/mmsegmentation)](https://pypi.org/project/mmsegmentation) +[![docs](https://img.shields.io/badge/docs-latest-blue)](https://mmsegmentation.readthedocs.io/en/latest/) +[![badge](https://github.com/open-mmlab/mmsegmentation/workflows/build/badge.svg)](https://github.com/open-mmlab/mmsegmentation/actions) +[![codecov](https://codecov.io/gh/open-mmlab/mmsegmentation/branch/master/graph/badge.svg)](https://codecov.io/gh/open-mmlab/mmsegmentation) +[![license](https://img.shields.io/github/license/open-mmlab/mmsegmentation.svg)](https://github.com/open-mmlab/mmsegmentation/blob/main/LICENSE) +[![issue resolution](https://isitmaintained.com/badge/resolution/open-mmlab/mmsegmentation.svg)](https://github.com/open-mmlab/mmsegmentation/issues) +[![open issues](https://isitmaintained.com/badge/open/open-mmlab/mmsegmentation.svg)](https://github.com/open-mmlab/mmsegmentation/issues) +[![Open in OpenXLab](https://cdn-static.openxlab.org.cn/app-center/openxlab_demo.svg)](https://openxlab.org.cn/apps?search=mmseg) + +Documentation: + +English | [简体中文](README_zh-CN.md) + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +## Introduction + +MMSegmentation is an open source semantic segmentation toolbox based on PyTorch. +It is a part of the OpenMMLab project. + +The [main](https://github.com/open-mmlab/mmsegmentation/tree/main) branch works with PyTorch 1.6+. + +### 🎉 Introducing MMSegmentation v1.0.0 🎉 + +We are thrilled to announce the official release of MMSegmentation's latest version! For this new release, the [main](https://github.com/open-mmlab/mmsegmentation/tree/main) branch serves as the primary branch, while the development branch is [dev-1.x](https://github.com/open-mmlab/mmsegmentation/tree/dev-1.x). The stable branch for the previous release remains as the [0.x](https://github.com/open-mmlab/mmsegmentation/tree/0.x) branch. Please note that the [master](https://github.com/open-mmlab/mmsegmentation/tree/master) branch will only be maintained for a limited time before being removed. We encourage you to be mindful of branch selection and updates during use. Thank you for your unwavering support and enthusiasm, and let's work together to make MMSegmentation even more robust and powerful! 💪 + +MMSegmentation v1.x brings remarkable improvements over the 0.x release, offering a more flexible and feature-packed experience. To utilize the new features in v1.x, we kindly invite you to consult our detailed [📚 migration guide](https://mmsegmentation.readthedocs.io/en/latest/migration/interface.html), which will help you seamlessly transition your projects. Your support is invaluable, and we eagerly await your feedback! + +![demo image](resources/seg_demo.gif) + +### Major features + +- **Unified Benchmark** + + We provide a unified benchmark toolbox for various semantic segmentation methods. + +- **Modular Design** + + We decompose the semantic segmentation framework into different components and one can easily construct a customized semantic segmentation framework by combining different modules. + +- **Support of multiple methods out of box** + + The toolbox directly supports popular and contemporary semantic segmentation frameworks, *e.g.* PSPNet, DeepLabV3, PSANet, DeepLabV3+, etc. + +- **High efficiency** + + The training speed is faster than or comparable to other codebases. + +## What's New + +v1.2.0 was released on 10/12/2023, from 1.1.0 to 1.2.0, we have added or updated the following features: + +### Highlights + +- Support for the open-vocabulary semantic segmentation algorithm [SAN](configs/san/README.md) + +- Support monocular depth estimation task, please refer to [VPD](configs/vpd/README.md) and [Adabins](projects/Adabins/README.md) for more details. + + ![depth estimation](https://github.com/open-mmlab/mmsegmentation/assets/15952744/07afd0e9-8ace-4a00-aa1e-5bf0ca92dcbc) + +- Add new projects: open-vocabulary semantic segmentation algorithm [CAT-Seg](projects/CAT-Seg/README.md), real-time semantic segmentation algofithm [PP-MobileSeg](projects/pp_mobileseg/README.md) + +## Installation + +Please refer to [get_started.md](docs/en/get_started.md#installation) for installation and [dataset_prepare.md](docs/en/user_guides/2_dataset_prepare.md#prepare-datasets) for dataset preparation. + +## Get Started + +Please see [Overview](docs/en/overview.md) for the general introduction of MMSegmentation. + +Please see [user guides](https://mmsegmentation.readthedocs.io/en/latest/user_guides/index.html#) for the basic usage of MMSegmentation. +There are also [advanced tutorials](https://mmsegmentation.readthedocs.io/en/latest/advanced_guides/index.html) for in-depth understanding of mmseg design and implementation . + +A Colab tutorial is also provided. You may preview the notebook [here](demo/MMSegmentation_Tutorial.ipynb) or directly [run](https://colab.research.google.com/github/open-mmlab/mmsegmentation/blob/main/demo/MMSegmentation_Tutorial.ipynb) on Colab. + +To migrate from MMSegmentation 0.x, please refer to [migration](docs/en/migration). + +## Tutorial + +
+ MMSegmentation Tutorials +
+ + + + + + + + + + + + + + + +
+ Get Started + + MMSeg Basic Tutorial + + MMSeg Detail Tutorial + + MMSeg Development Tutorial +
+ + + + + + + +
+ +## Benchmark and model zoo + +Results and models are available in the [model zoo](docs/en/model_zoo.md). + +
+ Overview +
+ + + + + + + + + + + + + + + + +
+ Supported backbones + + Supported methods + + Supported Head + + Supported datasets + + Other +
+ + + + + + + + + +
+ +Please refer to [FAQ](docs/en/notes/faq.md) for frequently asked questions. + +## Projects + +[Here](projects/README.md) are some implementations of SOTA models and solutions built on MMSegmentation, which are supported and maintained by community users. These projects demonstrate the best practices based on MMSegmentation for research and product development. We welcome and appreciate all the contributions to OpenMMLab ecosystem. + +## Contributing + +We appreciate all contributions to improve MMSegmentation. Please refer to [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the contributing guideline. + +## Acknowledgement + +MMSegmentation is an open source project that welcome any contribution and feedback. +We wish that the toolbox and benchmark could serve the growing research +community by providing a flexible as well as standardized toolkit to reimplement existing methods +and develop their own new semantic segmentation methods. + +## Citation + +If you find this project useful in your research, please consider cite: + +```bibtex +@misc{mmseg2020, + title={{MMSegmentation}: OpenMMLab Semantic Segmentation Toolbox and Benchmark}, + author={MMSegmentation Contributors}, + howpublished = {\url{https://github.com/open-mmlab/mmsegmentation}}, + year={2020} +} +``` + +## License + +This project is released under the [Apache 2.0 license](LICENSE). + +## OpenMMLab Family + +- [MMEngine](https://github.com/open-mmlab/mmengine): OpenMMLab foundational library for training deep learning models. +- [MMCV](https://github.com/open-mmlab/mmcv): OpenMMLab foundational library for computer vision. +- [MMPreTrain](https://github.com/open-mmlab/mmpretrain): OpenMMLab pre-training toolbox and benchmark. +- [MMagic](https://github.com/open-mmlab/mmagic): Open**MM**Lab **A**dvanced, **G**enerative and **I**ntelligent **C**reation toolbox. +- [MMDetection](https://github.com/open-mmlab/mmdetection): OpenMMLab detection toolbox and benchmark. +- [MMYOLO](https://github.com/open-mmlab/mmyolo): OpenMMLab YOLO series toolbox and benchmark. +- [MMDetection3D](https://github.com/open-mmlab/mmdetection3d): OpenMMLab's next-generation platform for general 3D object detection. +- [MMRotate](https://github.com/open-mmlab/mmrotate): OpenMMLab rotated object detection toolbox and benchmark. +- [MMTracking](https://github.com/open-mmlab/mmtracking): OpenMMLab video perception toolbox and benchmark. +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation): OpenMMLab semantic segmentation toolbox and benchmark. +- [MMOCR](https://github.com/open-mmlab/mmocr): OpenMMLab text detection, recognition, and understanding toolbox. +- [MMPose](https://github.com/open-mmlab/mmpose): OpenMMLab pose estimation toolbox and benchmark. +- [MMHuman3D](https://github.com/open-mmlab/mmhuman3d): OpenMMLab 3D human parametric model toolbox and benchmark. +- [MMFewShot](https://github.com/open-mmlab/mmfewshot): OpenMMLab fewshot learning toolbox and benchmark. +- [MMAction2](https://github.com/open-mmlab/mmaction2): OpenMMLab's next-generation action understanding toolbox and benchmark. +- [MMFlow](https://github.com/open-mmlab/mmflow): OpenMMLab optical flow toolbox and benchmark. +- [MMDeploy](https://github.com/open-mmlab/mmdeploy): OpenMMLab Model Deployment Framework. +- [MMRazor](https://github.com/open-mmlab/mmrazor): OpenMMLab model compression toolbox and benchmark. +- [MIM](https://github.com/open-mmlab/mim): MIM installs OpenMMLab packages. +- [Playground](https://github.com/open-mmlab/playground): A central hub for gathering and showcasing amazing projects built upon OpenMMLab. diff --git a/Seg_All_In_One_MMSeg/README_zh-CN.md b/Seg_All_In_One_MMSeg/README_zh-CN.md new file mode 100644 index 0000000..e047759 --- /dev/null +++ b/Seg_All_In_One_MMSeg/README_zh-CN.md @@ -0,0 +1,426 @@ +
+ +
 
+
+ OpenMMLab 官网 + + + HOT + + +      + OpenMMLab 开放平台 + + + TRY IT OUT + + +
+
 
+ +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/mmsegmentation)](https://pypi.org/project/mmsegmentation/) +[![PyPI](https://img.shields.io/pypi/v/mmsegmentation)](https://pypi.org/project/mmsegmentation) +[![docs](https://img.shields.io/badge/docs-latest-blue)](https://mmsegmentation.readthedocs.io/zh_CN/latest/) +[![badge](https://github.com/open-mmlab/mmsegmentation/workflows/build/badge.svg)](https://github.com/open-mmlab/mmsegmentation/actions) +[![codecov](https://codecov.io/gh/open-mmlab/mmsegmentation/branch/master/graph/badge.svg)](https://codecov.io/gh/open-mmlab/mmsegmentation) +[![license](https://img.shields.io/github/license/open-mmlab/mmsegmentation.svg)](https://github.com/open-mmlab/mmsegmentation/blob/main/LICENSE) +[![issue resolution](https://isitmaintained.com/badge/resolution/open-mmlab/mmsegmentation.svg)](https://github.com/open-mmlab/mmsegmentation/issues) +[![open issues](https://isitmaintained.com/badge/open/open-mmlab/mmsegmentation.svg)](https://github.com/open-mmlab/mmsegmentation/issues) +[![Open in OpenXLab](https://cdn-static.openxlab.org.cn/app-center/openxlab_demo.svg)](https://openxlab.org.cn/apps?search=mmseg) + +文档: + +[English](README.md) | 简体中文 + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +## 简介 + +MMSegmentation 是一个基于 PyTorch 的语义分割开源工具箱。它是 OpenMMLab 项目的一部分。 + +[main](https://github.com/open-mmlab/mmsegmentation/tree/main) 分支代码目前支持 PyTorch 1.6 以上的版本。 + +### 🎉 MMSegmentation v1.0.0 简介 🎉 + +我们非常高兴地宣布 MMSegmentation 最新版本的正式发布!在这个新版本中,主要分支是 [main](https://github.com/open-mmlab/mmsegmentation/tree/main) 分支,开发分支是 [dev-1.x](https://github.com/open-mmlab/mmsegmentation/tree/dev-1.x)。而之前版本的稳定分支保留为 [0.x](https://github.com/open-mmlab/mmsegmentation/tree/0.x) 分支。请注意,[master](https://github.com/open-mmlab/mmsegmentation/tree/master) 分支将只在有限的时间内维护,然后将被删除。我们鼓励您在使用过程中注意分支选择和更新。感谢您一如既往的支持和热情,让我们共同努力,使 MMSegmentation 变得更加健壮和强大!💪 + +MMSegmentation v1.x 在 0.x 版本的基础上有了显著的提升,提供了更加灵活和功能丰富的体验。为了更好使用 v1.x 中的新功能,我们诚挚邀请您查阅我们详细的 [📚 迁移指南](https://mmsegmentation.readthedocs.io/zh_CN/latest/migration/interface.html),以帮助您无缝地过渡您的项目。您的支持对我们来说非常宝贵,我们热切期待您的反馈! + +![示例图片](resources/seg_demo.gif) + +### 主要特性 + +- **统一的基准平台** + + 我们将各种各样的语义分割算法集成到了一个统一的工具箱,进行基准测试。 + +- **模块化设计** + + MMSegmentation 将分割框架解耦成不同的模块组件,通过组合不同的模块组件,用户可以便捷地构建自定义的分割模型。 + +- **丰富的即插即用的算法和模型** + + MMSegmentation 支持了众多主流的和最新的检测算法,例如 PSPNet,DeepLabV3,PSANet,DeepLabV3+ 等. + +- **速度快** + + 训练速度比其他语义分割代码库更快或者相当。 + +## 更新日志 + +最新版本 v1.2.0 在 2023.10.12 发布。 +如果想了解更多版本更新细节和历史信息,请阅读[更新日志](docs/en/notes/changelog.md)。 + +## 安装 + +请参考[快速入门文档](docs/zh_cn/get_started.md#installation)进行安装,参考[数据集准备](docs/zh_cn/user_guides/2_dataset_prepare.md)处理数据。 + +## 快速入门 + +请参考[概述](docs/zh_cn/overview.md)对 MMSegmetation 进行初步了解 + +请参考[用户指南](https://mmsegmentation.readthedocs.io/zh_CN/latest/user_guides/index.html)了解 mmseg 的基本使用,以及[进阶指南](https://mmsegmentation.readthedocs.io/zh_CN/latest/advanced_guides/index.html)深入了解 mmseg 设计和代码实现。 + +同时,我们提供了 Colab 教程。你可以在[这里](demo/MMSegmentation_Tutorial.ipynb)浏览教程,或者直接在 Colab 上[运行](https://colab.research.google.com/github/open-mmlab/mmsegmentation/blob/main/demo/MMSegmentation_Tutorial.ipynb)。 + +若需要将 0.x 版本的代码迁移至新版,请参考[迁移文档](docs/zh_cn/migration)。 + +## 教程文档 + +
+ mmsegmentation 教程文档 +
+ + + + + + + + + + + + + + + +
+ 开启 MMSeg 之旅 + + MMSeg 快速入门教程 + + MMSeg 细节介绍 + + MMSeg 开发教程 +
+ + + + + + + +
+ +## 基准测试和模型库 + +测试结果和模型可以在[模型库](docs/zh_cn/model_zoo.md)中找到。 + +
+ 概览 +
+ + + + + + + + + + + + + + + + +
+ 已支持的主干网络 + + 已支持的算法架构 + + 已支持的分割头 + + 已支持的数据集 + + 其他 +
+ + + + + + + + + +
+ +如果遇到问题,请参考 [常见问题解答](docs/zh_cn/notes/faq.md)。 + +## 社区项目 + +[这里](projects/README.md)有一些由社区用户支持和维护的基于 MMSegmentation 的 SOTA 模型和解决方案的实现。这些项目展示了基于 MMSegmentation 的研究和产品开发的最佳实践。 +我们欢迎并感谢对 OpenMMLab 生态系统的所有贡献。 + +## 贡献指南 + +我们感谢所有的贡献者为改进和提升 MMSegmentation 所作出的努力。请参考[贡献指南](.github/CONTRIBUTING.md)来了解参与项目贡献的相关指引。 + +## 致谢 + +MMSegmentation 是一个由来自不同高校和企业的研发人员共同参与贡献的开源项目。我们感谢所有为项目提供算法复现和新功能支持的贡献者,以及提供宝贵反馈的用户。我们希望这个工具箱和基准测试可以为社区提供灵活的代码工具,供用户复现已有算法并开发自己的新模型,从而不断为开源社区提供贡献。 + +## 引用 + +如果你觉得本项目对你的研究工作有所帮助,请参考如下 bibtex 引用 MMSegmentation。 + +```bibtex +@misc{mmseg2020, + title={{MMSegmentation}: OpenMMLab Semantic Segmentation Toolbox and Benchmark}, + author={MMSegmentation Contributors}, + howpublished = {\url{https://github.com/open-mmlab/mmsegmentation}}, + year={2020} +} +``` + +## 开源许可证 + +该项目采用 [Apache 2.0 开源许可证](LICENSE)。 + +## OpenMMLab 的其他项目 + +- [MMEngine](https://github.com/open-mmlab/mmengine): OpenMMLab 深度学习模型训练基础库 +- [MMCV](https://github.com/open-mmlab/mmcv): OpenMMLab 计算机视觉基础库 +- [MMPreTrain](https://github.com/open-mmlab/mmpretrain): OpenMMLab 深度学习预训练工具箱 +- [MMagic](https://github.com/open-mmlab/mmagic): OpenMMLab 新一代人工智能内容生成(AIGC)工具箱 +- [MMDetection](https://github.com/open-mmlab/mmdetection): OpenMMLab 目标检测工具箱 +- [MMYOLO](https://github.com/open-mmlab/mmyolo): OpenMMLab YOLO 系列工具箱与测试基准 +- [MMDetection3D](https://github.com/open-mmlab/mmdetection3d): OpenMMLab 新一代通用 3D 目标检测平台 +- [MMRotate](https://github.com/open-mmlab/mmrotate): OpenMMLab 旋转框检测工具箱与测试基准 +- [MMTracking](https://github.com/open-mmlab/mmtracking): OpenMMLab 一体化视频目标感知平台 +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation): OpenMMLab 语义分割工具箱 +- [MMOCR](https://github.com/open-mmlab/mmocr): OpenMMLab 全流程文字检测识别理解工具包 +- [MMPose](https://github.com/open-mmlab/mmpose): OpenMMLab 姿态估计工具箱 +- [MMHuman3D](https://github.com/open-mmlab/mmhuman3d): OpenMMLab 人体参数化模型工具箱与测试基准 +- [MMFewShot](https://github.com/open-mmlab/mmfewshot): OpenMMLab 少样本学习工具箱与测试基准 +- [MMAction2](https://github.com/open-mmlab/mmaction2): OpenMMLab 新一代视频理解工具箱 +- [MMFlow](https://github.com/open-mmlab/mmflow): OpenMMLab 光流估计工具箱与测试基准 +- [MMDeploy](https://github.com/open-mmlab/mmdeploy): OpenMMLab 模型部署框架 +- [MMRazor](https://github.com/open-mmlab/mmrazor): OpenMMLab 模型压缩工具箱与测试基准 +- [MIM](https://github.com/open-mmlab/mim): OpenMMLab 项目、算法、模型的统一入口 +- [Playground](https://github.com/open-mmlab/playground): 收集和展示 OpenMMLab 相关的前沿、有趣的社区项目 + +## 欢迎加入 OpenMMLab 社区 + +扫描下方的二维码可关注 OpenMMLab 团队的 [知乎官方账号](https://www.zhihu.com/people/openmmlab),扫描下方微信二维码添加喵喵好友,进入 MMSegmentation 微信交流社群。【加好友申请格式:研究方向+地区+学校/公司+姓名】 + +
+ +
+ +我们会在 OpenMMLab 社区为大家 + +- 📢 分享 AI 框架的前沿核心技术 +- 💻 解读 PyTorch 常用模块源码 +- 📰 发布 OpenMMLab 的相关新闻 +- 🚀 介绍 OpenMMLab 开发的前沿算法 +- 🏃 获取更高效的问题答疑和意见反馈 +- 🔥 提供与各行各业开发者充分交流的平台 + +干货满满 📘,等你来撩 💗,OpenMMLab 社区期待您的加入 👬 diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/ade20k.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/ade20k.py new file mode 100644 index 0000000..48340d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/ade20k.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'ADE20KDataset' +data_root = 'data/ade/ADEChallengeData2016' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', seg_map_path='annotations/training'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/ade20k_640x640.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/ade20k_640x640.py new file mode 100644 index 0000000..c1f642d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/ade20k_640x640.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'ADE20KDataset' +data_root = 'data/ade/ADEChallengeData2016' +crop_size = (640, 640) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2560, 640), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2560, 640), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', seg_map_path='annotations/training'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/bdd100k.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/bdd100k.py new file mode 100644 index 0000000..24cec69 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/bdd100k.py @@ -0,0 +1,70 @@ +# dataset settings +dataset_type = 'BDD100KDataset' +data_root = 'data/bdd100k/' + +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/10k/train', + seg_map_path='labels/sem_seg/masks/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/10k/val', + seg_map_path='labels/sem_seg/masks/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/chase_db1.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/chase_db1.py new file mode 100644 index 0000000..ed47c2d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/chase_db1.py @@ -0,0 +1,75 @@ +# dataset settings +dataset_type = 'ChaseDB1Dataset' +data_root = 'data/CHASE_DB1' +img_scale = (960, 999) +crop_size = (128, 128) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=img_scale, + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] + +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type='RepeatDataset', + times=40000, + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', + seg_map_path='annotations/training'), + pipeline=train_pipeline))) + +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mDice']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes.py new file mode 100644 index 0000000..b63a4cd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes.py @@ -0,0 +1,67 @@ +# dataset settings +dataset_type = 'CityscapesDataset' +data_root = 'data/cityscapes/' +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='leftImg8bit/train', seg_map_path='gtFine/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='leftImg8bit/val', seg_map_path='gtFine/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_1024x1024.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_1024x1024.py new file mode 100644 index 0000000..72be307 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_1024x1024.py @@ -0,0 +1,29 @@ +_base_ = './cityscapes.py' +crop_size = (1024, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_768x768.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_768x768.py new file mode 100644 index 0000000..fcee014 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_768x768.py @@ -0,0 +1,29 @@ +_base_ = './cityscapes.py' +crop_size = (768, 768) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2049, 1025), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2049, 1025), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_769x769.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_769x769.py new file mode 100644 index 0000000..ae40ac8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_769x769.py @@ -0,0 +1,29 @@ +_base_ = './cityscapes.py' +crop_size = (769, 769) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2049, 1025), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2049, 1025), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_832x832.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_832x832.py new file mode 100644 index 0000000..0254580 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/cityscapes_832x832.py @@ -0,0 +1,29 @@ +_base_ = './cityscapes.py' +crop_size = (832, 832) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/coco-stuff10k.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/coco-stuff10k.py new file mode 100644 index 0000000..5d6bb12 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/coco-stuff10k.py @@ -0,0 +1,69 @@ +# dataset settings +dataset_type = 'COCOStuffDataset' +data_root = 'data/coco_stuff10k' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + reduce_zero_label=True, + data_prefix=dict( + img_path='images/train2014', seg_map_path='annotations/train2014'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + reduce_zero_label=True, + data_prefix=dict( + img_path='images/test2014', seg_map_path='annotations/test2014'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/coco-stuff164k.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/coco-stuff164k.py new file mode 100644 index 0000000..a9b9d90 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/coco-stuff164k.py @@ -0,0 +1,67 @@ +# dataset settings +dataset_type = 'COCOStuffDataset' +data_root = 'data/coco_stuff164k' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/train2017', seg_map_path='annotations/train2017'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/val2017', seg_map_path='annotations/val2017'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/drive.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/drive.py new file mode 100644 index 0000000..6a3dd82 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/drive.py @@ -0,0 +1,73 @@ +# dataset settings +dataset_type = 'DRIVEDataset' +data_root = 'data/DRIVE' +img_scale = (584, 565) +crop_size = (64, 64) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=img_scale, + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type='RepeatDataset', + times=40000, + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', + seg_map_path='annotations/training'), + pipeline=train_pipeline))) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mDice']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/hrf.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/hrf.py new file mode 100644 index 0000000..353d070 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/hrf.py @@ -0,0 +1,73 @@ +# dataset settings +dataset_type = 'HRFDataset' +data_root = 'data/HRF' +img_scale = (2336, 3504) +crop_size = (256, 256) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=img_scale, + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type='RepeatDataset', + times=40000, + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', + seg_map_path='annotations/training'), + pipeline=train_pipeline))) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mDice']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/hsi_drive.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/hsi_drive.py new file mode 100644 index 0000000..2d08e2d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/hsi_drive.py @@ -0,0 +1,53 @@ +train_pipeline = [ + dict(type='LoadImageFromNpyFile'), + dict(type='LoadAnnotations'), + dict(type='RandomCrop', crop_size=(192, 384)), + dict(type='PackSegInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromNpyFile'), + dict(type='RandomCrop', crop_size=(192, 384)), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] + +train_dataloader = dict( + batch_size=4, + num_workers=1, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type='HSIDrive20Dataset', + data_root='data/HSIDrive20', + data_prefix=dict( + img_path='images/training', seg_map_path='annotations/training'), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=1, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='HSIDrive20Dataset', + data_root='data/HSIDrive20', + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) + +test_dataloader = dict( + batch_size=1, + num_workers=1, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='HSIDrive20Dataset', + data_root='data/HSIDrive20', + data_prefix=dict( + img_path='images/test', seg_map_path='annotations/test'), + pipeline=test_pipeline)) + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU'], ignore_index=0) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/isaid.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/isaid.py new file mode 100644 index 0000000..5cd4309 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/isaid.py @@ -0,0 +1,73 @@ +# dataset settings +dataset_type = 'iSAIDDataset' +data_root = 'data/iSAID' +""" +This crop_size setting is followed by the implementation of +`PointFlow: Flowing Semantics Through Points for Aerial Image +Segmentation `_. +""" + +crop_size = (896, 896) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(896, 896), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(896, 896), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='img_dir/train', seg_map_path='ann_dir/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/levir_256x256.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/levir_256x256.py new file mode 100644 index 0000000..6e018a5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/levir_256x256.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'LEVIRCDDataset' +data_root = r'data/LEVIRCD' + +albu_train_transforms = [ + dict(type='RandomBrightnessContrast', p=0.2), + dict(type='HorizontalFlip', p=0.5), + dict(type='VerticalFlip', p=0.5) +] + +train_pipeline = [ + dict(type='LoadMultipleRSImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='Albu', + keymap={ + 'img': 'image', + 'img2': 'image2', + 'gt_seg_map': 'mask' + }, + transforms=albu_train_transforms, + additional_targets={'image2': 'image'}, + bgr_to_rgb=False), + dict(type='ConcatCDInput'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadMultipleRSImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='ConcatCDInput'), + dict(type='PackSegInputs') +] + +tta_pipeline = [ + dict(type='LoadMultipleRSImageFromFile'), + dict( + type='TestTimeAug', + transforms=[[dict(type='LoadAnnotations')], + [dict(type='ConcatCDInput')], + [dict(type='PackSegInputs')]]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='train/A', + img_path2='train/B', + seg_map_path='train/label'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='test/A', img_path2='test/B', seg_map_path='test/label'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/loveda.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/loveda.py new file mode 100644 index 0000000..b93bc74 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/loveda.py @@ -0,0 +1,66 @@ +# dataset settings +dataset_type = 'LoveDADataset' +data_root = 'data/loveDA' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1024, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='img_dir/train', seg_map_path='ann_dir/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v1.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v1.py new file mode 100644 index 0000000..611aa47 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v1.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'MapillaryDataset_v1' +data_root = 'data/mapillary/' +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='training/images', seg_map_path='training/v1.2/labels'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='validation/images', + seg_map_path='validation/v1.2/labels'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v1_65.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v1_65.py new file mode 100644 index 0000000..f594f37 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v1_65.py @@ -0,0 +1,37 @@ +# dataset settings +_base_ = './mapillary_v1.py' +metainfo = dict( + classes=('Bird', 'Ground Animal', 'Curb', 'Fence', 'Guard Rail', 'Barrier', + 'Wall', 'Bike Lane', 'Crosswalk - Plain', 'Curb Cut', 'Parking', + 'Pedestrian Area', 'Rail Track', 'Road', 'Service Lane', + 'Sidewalk', 'Bridge', 'Building', 'Tunnel', 'Person', 'Bicyclist', + 'Motorcyclist', 'Other Rider', 'Lane Marking - Crosswalk', + 'Lane Marking - General', 'Mountain', 'Sand', 'Sky', 'Snow', + 'Terrain', 'Vegetation', 'Water', 'Banner', 'Bench', 'Bike Rack', + 'Billboard', 'Catch Basin', 'CCTV Camera', 'Fire Hydrant', + 'Junction Box', 'Mailbox', 'Manhole', 'Phone Booth', 'Pothole', + 'Street Light', 'Pole', 'Traffic Sign Frame', 'Utility Pole', + 'Traffic Light', 'Traffic Sign (Back)', 'Traffic Sign (Front)', + 'Trash Can', 'Bicycle', 'Boat', 'Bus', 'Car', 'Caravan', + 'Motorcycle', 'On Rails', 'Other Vehicle', 'Trailer', 'Truck', + 'Wheeled Slow', 'Car Mount', 'Ego Vehicle'), + palette=[[165, 42, 42], [0, 192, 0], [196, 196, 196], [190, 153, 153], + [180, 165, 180], [90, 120, 150], [102, 102, 156], [128, 64, 255], + [140, 140, 200], [170, 170, 170], [250, 170, 160], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], [244, 35, 232], + [150, 100, 100], [70, 70, 70], [150, 120, 90], [220, 20, 60], + [255, 0, 0], [255, 0, 100], [255, 0, 200], [200, 128, 128], + [255, 255, 255], [64, 170, 64], [230, 160, 50], [70, 130, 180], + [190, 255, 255], [152, 251, 152], [107, 142, 35], [0, 170, 30], + [255, 255, 128], [250, 0, 30], [100, 140, 180], [220, 220, 220], + [220, 128, 128], [222, 40, 40], [100, 170, 30], [40, 40, 40], + [33, 33, 33], [100, 128, 160], [142, 0, 0], [70, 100, 150], + [210, 170, 100], [153, 153, 153], [128, 128, 128], [0, 0, 80], + [250, 170, 30], [192, 192, 192], [220, 220, 0], [140, 140, 20], + [119, 11, 32], [150, 0, 255], [0, 60, 100], [0, 0, 142], + [0, 0, 90], [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 192], [32, 32, 32], [120, 10, 10]]) + +train_dataloader = dict(dataset=dict(metainfo=metainfo)) +val_dataloader = dict(dataset=dict(metainfo=metainfo)) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v2.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v2.py new file mode 100644 index 0000000..7cb7a95 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/mapillary_v2.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'MapillaryDataset_v2' +data_root = 'data/mapillary/' +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='training/images', seg_map_path='training/v2.0/labels'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='validation/images', + seg_map_path='validation/v2.0/labels'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/my_dataset.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/my_dataset.py new file mode 100644 index 0000000..35b57ce --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/my_dataset.py @@ -0,0 +1,64 @@ +# dataset settings +dataset_class_name = 'MyDataset' # TODO 上一步中你定义的数据集的名字 +data_root = '/home/audience/Desktop/Seg_data/Data' # TODO 数据集存储路径 +# img_norm_cfg = dict( +# mean=[33.30, 35.03, 47.23], std=[48.00, 50.4, 60.51], to_rgb=True) # TODO 数据集的均值和标准差,空引用默认的,也可以网上搜代码计算 +img_scale = (1920, 1080) # img_scale图像尺寸 TODO (1920,1080) +crop_size = (512, 512) # 数据增强时裁剪的大小 TODO 之后可以修改 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), # ", reduce_zero_label=False" TODO 是否忽略0直选项 + dict(type='RandomResize', scale=img_scale, ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + # dict(type='GenerateEdge', edge_width=4), # For pidnet + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( # Train dataloader config + batch_size=4, # Batch size of a single GPU TODO + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='DefaultSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='A_Ori', + seg_map_path='A_Label_GT_label_fold'), + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='A_Ori', + seg_map_path='A_Label_GT_label_fold'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='A_Ori', + seg_map_path='A_Label_GT_label_fold'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/my_dataset_model.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/my_dataset_model.py new file mode 100644 index 0000000..281efd3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/my_dataset_model.py @@ -0,0 +1,64 @@ +# dataset settings +dataset_class_name = 'MyDataset_model' # TODO 上一步中你定义的数据集的名字 +data_root = '/home/wkmgc/Desktop/Seg/Seg_All_In_One_MMSeg/My_Data' # TODO 数据集存储路径 +# img_norm_cfg = dict( +# mean=[33.30, 35.03, 47.23], std=[48.00, 50.4, 60.51], to_rgb=True) # TODO 数据集的均值和标准差,空引用默认的,也可以网上搜代码计算 +img_scale = (1920, 1080) # img_scale图像尺寸 TODO (1920,1080) +crop_size = (256, 256) # 数据增强时裁剪的大小 TODO 之后可以修改 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), # ", reduce_zero_label=False" TODO 是否忽略0直选项 + dict(type='RandomResize', scale=img_scale, ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + # dict(type='GenerateEdge', edge_width=4), # For pidnet + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( # Train dataloader config + batch_size=16, # Batch size of a single GPU TODO + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='DefaultSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='A_Ori', + seg_map_path='A_Label_GT_label_fold'), + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='A_Ori', + seg_map_path='A_Label_GT_label_fold'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='A_Ori', + seg_map_path='A_Label_GT_label_fold'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/nyu.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/nyu.py new file mode 100644 index 0000000..74d57c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/nyu.py @@ -0,0 +1,67 @@ +# dataset settings +dataset_type = 'NYUDataset' +data_root = 'data/nyu' + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadDepthAnnotation', depth_rescale_factor=1e-3), + dict(type='RandomDepthMix', prob=0.25), + dict(type='RandomFlip', prob=0.5), + dict(type='RandomCrop', crop_size=(480, 480)), + dict( + type='Albu', + transforms=[ + dict(type='RandomBrightnessContrast'), + dict(type='RandomGamma'), + dict(type='HueSaturationValue'), + ]), + dict( + type='PackSegInputs', + meta_keys=('img_path', 'depth_map_path', 'ori_shape', 'img_shape', + 'pad_shape', 'scale_factor', 'flip', 'flip_direction', + 'category_id')), +] + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2000, 480), keep_ratio=True), + dict(dict(type='LoadDepthAnnotation', depth_rescale_factor=1e-3)), + dict( + type='PackSegInputs', + meta_keys=('img_path', 'depth_map_path', 'ori_shape', 'img_shape', + 'pad_shape', 'scale_factor', 'flip', 'flip_direction', + 'category_id')) +] + +train_dataloader = dict( + batch_size=8, + num_workers=8, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/train', depth_map_path='annotations/train'), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + test_mode=True, + data_prefix=dict( + img_path='images/test', depth_map_path='annotations/test'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='DepthMetric', + min_depth_eval=0.001, + max_depth_eval=10.0, + crop_type='nyu_crop') +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/nyu_512x512.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/nyu_512x512.py new file mode 100644 index 0000000..88e3878 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/nyu_512x512.py @@ -0,0 +1,72 @@ +# dataset settings +dataset_type = 'NYUDataset' +data_root = 'data/nyu' + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadDepthAnnotation', depth_rescale_factor=1e-3), + dict(type='RandomDepthMix', prob=0.25), + dict(type='RandomFlip', prob=0.5), + dict( + type='RandomResize', + scale=(768, 512), + ratio_range=(0.8, 1.5), + keep_ratio=True), + dict(type='RandomCrop', crop_size=(512, 512)), + dict( + type='Albu', + transforms=[ + dict(type='RandomBrightnessContrast'), + dict(type='RandomGamma'), + dict(type='HueSaturationValue'), + ]), + dict( + type='PackSegInputs', + meta_keys=('img_path', 'depth_map_path', 'ori_shape', 'img_shape', + 'pad_shape', 'scale_factor', 'flip', 'flip_direction', + 'category_id')), +] + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + dict(dict(type='LoadDepthAnnotation', depth_rescale_factor=1e-3)), + dict( + type='PackSegInputs', + meta_keys=('img_path', 'depth_map_path', 'ori_shape', 'img_shape', + 'pad_shape', 'scale_factor', 'flip', 'flip_direction', + 'category_id')) +] + +train_dataloader = dict( + batch_size=8, + num_workers=8, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/train', depth_map_path='annotations/train'), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + test_mode=True, + data_prefix=dict( + img_path='images/test', depth_map_path='annotations/test'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='DepthMetric', + min_depth_eval=0.001, + max_depth_eval=10.0, + crop_type='nyu_crop') +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_context.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_context.py new file mode 100644 index 0000000..dfb1f85 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_context.py @@ -0,0 +1,56 @@ +# dataset settings +dataset_type = 'PascalContextDataset' +data_root = 'data/VOCdevkit/VOC2010/' + +img_scale = (520, 520) +crop_size = (480, 480) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=img_scale, + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClassContext'), + ann_file='ImageSets/SegmentationContext/train.txt', + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClassContext'), + ann_file='ImageSets/SegmentationContext/val.txt', + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_context_59.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_context_59.py new file mode 100644 index 0000000..7f31043 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_context_59.py @@ -0,0 +1,72 @@ +# dataset settings +dataset_type = 'PascalContextDataset59' +data_root = 'data/VOCdevkit/VOC2010/' + +img_scale = (520, 520) +crop_size = (480, 480) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=img_scale, + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClassContext'), + ann_file='ImageSets/SegmentationContext/train.txt', + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClassContext'), + ann_file='ImageSets/SegmentationContext/val.txt', + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_voc12.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_voc12.py new file mode 100644 index 0000000..5235ca9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_voc12.py @@ -0,0 +1,69 @@ +# dataset settings +dataset_type = 'PascalVOCDataset' +data_root = 'data/VOCdevkit/VOC2012' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClass'), + ann_file='ImageSets/Segmentation/train.txt', + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClass'), + ann_file='ImageSets/Segmentation/val.txt', + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_voc12_aug.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_voc12_aug.py new file mode 100644 index 0000000..69c3654 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/pascal_voc12_aug.py @@ -0,0 +1,81 @@ +# dataset settings +dataset_type = 'PascalVOCDataset' +data_root = 'data/VOCdevkit/VOC2012' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='Pad', size=crop_size), + dict(type='PackSegInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +dataset_train = dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='JPEGImages', seg_map_path='SegmentationClass'), + ann_file='ImageSets/Segmentation/train.txt', + pipeline=train_pipeline) + +dataset_aug = dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClassAug'), + ann_file='ImageSets/Segmentation/aug.txt', + pipeline=train_pipeline) + +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict(type='ConcatDataset', datasets=[dataset_train, dataset_aug])) + +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClass'), + ann_file='ImageSets/Segmentation/val.txt', + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/potsdam.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/potsdam.py new file mode 100644 index 0000000..95f6039 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/potsdam.py @@ -0,0 +1,66 @@ +# dataset settings +dataset_type = 'PotsdamDataset' +data_root = 'data/potsdam' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(512, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='img_dir/train', seg_map_path='ann_dir/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_autolaparo.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_autolaparo.py new file mode 100644 index 0000000..92939de --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_autolaparo.py @@ -0,0 +1,64 @@ +# dataset settings +dataset_class_name = 'PublicDataSet_AutoLaparo' # TODO 上一步中你定义的数据集的名字 +data_root = '/home/wkmgc/Desktop/Seg/DataSet_Public/2_AutoLaparo-10Type-1920x1080' # TODO 数据集存储路径 +# img_norm_cfg = dict( +# mean=[33.30, 35.03, 47.23], std=[48.00, 50.4, 60.51], to_rgb=True) # TODO 数据集的均值和标准差,空引用默认的,也可以网上搜代码计算 +img_scale = (1920, 1080) # img_scale图像尺寸 TODO (1920,1080) +crop_size = (256, 256) # 数据增强时裁剪的大小 TODO 之后可以修改 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), # ", reduce_zero_label=False" TODO 是否忽略0直选项 + dict(type='RandomResize', scale=img_scale, ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + # dict(type='GenerateEdge', edge_width=4), # For pidnet + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( # Train dataloader config + batch_size=16, # Batch size of a single GPU TODO + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='DefaultSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/train', + seg_map_path='labels_GT/train'), + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_cholecseg8k.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_cholecseg8k.py new file mode 100644 index 0000000..9dc18c3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_cholecseg8k.py @@ -0,0 +1,64 @@ +# dataset settings +dataset_class_name = 'PublicDataSet_CholecSeg8k' # TODO 上一步中你定义的数据集的名字 +data_root = '/home/wkmgc/Desktop/Seg/DataSet_Public/1_CholecSeg8k-13Type-1920x1080' # TODO 数据集存储路径 +# img_norm_cfg = dict( +# mean=[33.30, 35.03, 47.23], std=[48.00, 50.4, 60.51], to_rgb=True) # TODO 数据集的均值和标准差,空引用默认的,也可以网上搜代码计算 +img_scale = (1920, 1080) # img_scale图像尺寸 TODO (1920,1080) +crop_size = (256, 256) # 数据增强时裁剪的大小 TODO 之后可以修改 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), # ", reduce_zero_label=False" TODO 是否忽略0直选项 + dict(type='RandomResize', scale=img_scale, ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + # dict(type='GenerateEdge', edge_width=4), # For pidnet + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( # Train dataloader config + batch_size=16, # Batch size of a single GPU TODO + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='DefaultSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/train', + seg_map_path='labels_GT/train'), + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_dresden.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_dresden.py new file mode 100644 index 0000000..c5101d3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_dresden.py @@ -0,0 +1,64 @@ +# dataset settings +dataset_class_name = 'PublicDataSet_Dresden' # TODO 上一步中你定义的数据集的名字 +data_root = '/home/wkmgc/Desktop/Seg/DataSet_Public/4_Dresden-11Type-512x512' # TODO 数据集存储路径 +# img_norm_cfg = dict( +# mean=[33.30, 35.03, 47.23], std=[48.00, 50.4, 60.51], to_rgb=True) # TODO 数据集的均值和标准差,空引用默认的,也可以网上搜代码计算 +img_scale = (512, 512) # img_scale图像尺寸 TODO (1920,1080) +crop_size = (256, 256) # 数据增强时裁剪的大小 TODO 之后可以修改 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), # ", reduce_zero_label=False" TODO 是否忽略0直选项 + dict(type='RandomResize', scale=img_scale, ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + # dict(type='GenerateEdge', edge_width=4), # For pidnet + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( # Train dataloader config + batch_size=16, # Batch size of a single GPU TODO + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='DefaultSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/train', + seg_map_path='labels_GT/train'), + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/test', + seg_map_path='labels_GT/test'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_endovis_2017.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_endovis_2017.py new file mode 100644 index 0000000..27c6a90 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_endovis_2017.py @@ -0,0 +1,64 @@ +# dataset settings +dataset_class_name = 'PublicDataSet_Endovis_2017' # TODO 上一步中你定义的数据集的名字 +data_root = '/home/wkmgc/Desktop/Seg/DataSet_Public/3_1_Endovis_2017-8Type-512x512' # TODO 数据集存储路径 +# img_norm_cfg = dict( +# mean=[33.30, 35.03, 47.23], std=[48.00, 50.4, 60.51], to_rgb=True) # TODO 数据集的均值和标准差,空引用默认的,也可以网上搜代码计算 +img_scale = (512, 512) # img_scale图像尺寸 TODO (1920,1080) +crop_size = (256, 256) # 数据增强时裁剪的大小 TODO 之后可以修改 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), # ", reduce_zero_label=False" TODO 是否忽略0直选项 + dict(type='RandomResize', scale=img_scale, ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + # dict(type='GenerateEdge', edge_width=4), # For pidnet + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( # Train dataloader config + batch_size=16, # Batch size of a single GPU TODO + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='DefaultSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/train', + seg_map_path='labels_GT/train'), + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_endovis_2018.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_endovis_2018.py new file mode 100644 index 0000000..3014a5b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/publicdataset_endovis_2018.py @@ -0,0 +1,64 @@ +# dataset settings +dataset_class_name = 'PublicDataSet_Endovis_2018' # TODO 上一步中你定义的数据集的名字 +data_root = '/home/wkmgc/Desktop/Seg/DataSet_Public/3_2_Endovis_2018-8Type-512x512' # TODO 数据集存储路径 +# img_norm_cfg = dict( +# mean=[33.30, 35.03, 47.23], std=[48.00, 50.4, 60.51], to_rgb=True) # TODO 数据集的均值和标准差,空引用默认的,也可以网上搜代码计算 +img_scale = (512, 512) # img_scale图像尺寸 TODO (1920,1080) +crop_size = (256, 256) # 数据增强时裁剪的大小 TODO 之后可以修改 +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), # ", reduce_zero_label=False" TODO 是否忽略0直选项 + dict(type='RandomResize', scale=img_scale, ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + # dict(type='GenerateEdge', edge_width=4), # For pidnet + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( # Train dataloader config + batch_size=16, # Batch size of a single GPU TODO + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='DefaultSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/train', + seg_map_path='labels_GT/train'), + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_class_name, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='images/val', + seg_map_path='labels_GT/val'), + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/refuge.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/refuge.py new file mode 100644 index 0000000..79bb4d4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/refuge.py @@ -0,0 +1,90 @@ +# dataset settings +dataset_type = 'REFUGEDataset' +data_root = 'data/REFUGE' +train_img_scale = (2056, 2124) +val_img_scale = (1634, 1634) +test_img_scale = (1634, 1634) +crop_size = (512, 512) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=False), + dict( + type='RandomResize', + scale=train_img_scale, + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +val_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=val_img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=False), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=test_img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=False), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=dict(backend='local')), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', seg_map_path='annotations/training'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=val_pipeline)) +test_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/test', seg_map_path='annotations/test'), + pipeline=val_pipeline)) + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mDice']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/stare.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/stare.py new file mode 100644 index 0000000..b7545dc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/stare.py @@ -0,0 +1,73 @@ +# dataset settings +dataset_type = 'STAREDataset' +data_root = 'data/STARE' +img_scale = (605, 700) +crop_size = (128, 128) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=img_scale, + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type='RepeatDataset', + times=40000, + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', + seg_map_path='annotations/training'), + pipeline=train_pipeline))) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mDice']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/synapse.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/synapse.py new file mode 100644 index 0000000..8685291 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/synapse.py @@ -0,0 +1,41 @@ +dataset_type = 'SynapseDataset' +data_root = 'data/synapse/' +img_scale = (224, 224) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict(type='RandomRotFlip', rotate_prob=0.5, flip_prob=0.5, degree=20), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=6, + num_workers=2, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='img_dir/train', seg_map_path='ann_dir/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mDice']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/datasets/vaihingen.py b/Seg_All_In_One_MMSeg/configs/_base_/datasets/vaihingen.py new file mode 100644 index 0000000..6c78994 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/datasets/vaihingen.py @@ -0,0 +1,66 @@ +# dataset settings +dataset_type = 'ISPRSDataset' +data_root = 'data/vaihingen' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(512, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='img_dir/train', seg_map_path='ann_dir/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/_base_/default_runtime.py b/Seg_All_In_One_MMSeg/configs/_base_/default_runtime.py new file mode 100644 index 0000000..272b4d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/default_runtime.py @@ -0,0 +1,15 @@ +default_scope = 'mmseg' +env_cfg = dict( + cudnn_benchmark=True, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict(by_epoch=False) +log_level = 'INFO' +load_from = None +resume = False + +tta_model = dict(type='SegTTAModel') diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/ann_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/ann_r50-d8.py new file mode 100644 index 0000000..a1ef956 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/ann_r50-d8.py @@ -0,0 +1,54 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='ANNHead', + in_channels=[1024, 2048], + in_index=[2, 3], + channels=512, + project_channels=256, + query_scales=(1, ), + key_pool_scales=(1, 3, 6, 8), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/apcnet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/apcnet_r50-d8.py new file mode 100644 index 0000000..63269f9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/apcnet_r50-d8.py @@ -0,0 +1,52 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='APCHead', + in_channels=2048, + in_index=3, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict(type='SyncBN', requires_grad=True), + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv1_r18-d32.py b/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv1_r18-d32.py new file mode 100644 index 0000000..8872cb3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv1_r18-d32.py @@ -0,0 +1,78 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='BiSeNetV1', + in_channels=3, + context_channels=(128, 256, 512), # (512, 1024, 2048), # (512, 1024, 2048), + spatial_channels=(64, 64, 64, 128), # (256, 256, 256, 512), # (256, 256, 256, 512), + out_indices=(0, 1, 2), + out_channels=256, # 1024, # 1024, + backbone_cfg=dict( + type='ResNet', + in_channels=3, + depth=18, # 50, # 101, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 1, 1), + strides=(1, 2, 2, 2), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True + # init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://resnet18_v1c') + ), + norm_cfg=norm_cfg, + align_corners=False, + init_cfg=None), + decode_head=dict( + type='FCNHead', + in_channels=256, # 1024 # 1024 + in_index=0, + channels=256, # 1024 # 1024 + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, # 512 + channels=64, # 256 + num_convs=1, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, # 512 # 512 + channels=64, # 256 # 256 + num_convs=1, + num_classes=19, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv2.py b/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv2.py new file mode 100644 index 0000000..ae84512 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv2.py @@ -0,0 +1,88 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='BiSeNetV2', + detail_channels=(64, 64, 128), + semantic_channels=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + init_cfg=None, + align_corners=False), + decode_head=dict( + type='FCNHead', + in_channels=128, + in_index=0, + channels=1024, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=19, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=19, + in_index=3, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=19, + in_index=4, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv2_large.py b/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv2_large.py new file mode 100644 index 0000000..144e7f6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/bisenetv2_large.py @@ -0,0 +1,88 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='BiSeNetV2', + detail_channels=(128, 128, 256), # (64, 64, 128) + semantic_channels=(32, 64, 128, 256), # (16, 32, 64, 128) + semantic_expansion_ratio=6, + bga_channels=256, # 128 + out_indices=(0, 1, 2, 3, 4), + init_cfg=None, + align_corners=False), + decode_head=dict( + type='FCNHead', + in_channels=256, # 128 + in_index=0, + channels=1024, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=32, # 16 + channels=16, + num_convs=2, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=64, # 32 + channels=64, + num_convs=2, + num_classes=19, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, # 64 + channels=256, + num_convs=2, + num_classes=19, + in_index=3, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=256, # 128 + channels=1024, + num_convs=2, + num_classes=19, + in_index=4, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/ccnet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/ccnet_r50-d8.py new file mode 100644 index 0000000..575d8eb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/ccnet_r50-d8.py @@ -0,0 +1,52 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='CCHead', + in_channels=2048, + in_index=3, + channels=512, + recurrence=2, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/cgnet.py b/Seg_All_In_One_MMSeg/configs/_base_/models/cgnet.py new file mode 100644 index 0000000..4aec7d6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/cgnet.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', eps=1e-03, requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[72.39239876, 82.90891754, 73.15835921], + std=[1, 1, 1], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='CGNet', + norm_cfg=norm_cfg, + in_channels=3, + num_channels=(32, 64, 128), + num_blocks=(3, 21), + dilations=(2, 4), + reductions=(8, 16)), + decode_head=dict( + type='FCNHead', + in_channels=256, + in_index=2, + channels=256, + num_convs=0, + concat_input=False, + dropout_ratio=0, + num_classes=19, + norm_cfg=norm_cfg, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + class_weight=[ + 2.5959933, 6.7415504, 3.5354059, 9.8663225, 9.690899, 9.369352, + 10.289121, 9.953208, 4.3097677, 9.490387, 7.674431, 9.396905, + 10.347791, 6.3927646, 10.226669, 10.241062, 10.280587, + 10.396974, 10.055647 + ] + )), + # model training and testing settings + train_cfg=dict(sampler=None), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/danet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/danet_r50-d8.py new file mode 100644 index 0000000..8163b3d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/danet_r50-d8.py @@ -0,0 +1,52 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='DAHead', + in_channels=2048, + in_index=3, + channels=512, + pam_channels=64, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/ddrnet.py b/Seg_All_In_One_MMSeg/configs/_base_/models/ddrnet.py new file mode 100644 index 0000000..4430466 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/ddrnet.py @@ -0,0 +1,55 @@ +# The class_weight is borrowed from https://github.com/openseg-group/OCNet.pytorch/issues/14 # noqa +# Licensed under the MIT License +# class_weight = [ +# 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, 1.0489, 0.8786, +# 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, 1.0865, 1.0955, 1.0865, 1.1529, +# 1.0507 +# ] +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/pretrain/ddrnet23-in1kpre_3rdparty-9ca29f62.pth' # noqa +# crop_size = (1024, 1024) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=(1024, 1024), + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='DDRNet', + in_channels=3, + channels=64, + ppm_channels=128, + norm_cfg=norm_cfg, + align_corners=False, + init_cfg=dict(type='Pretrained', checkpoint=checkpoint)), + decode_head=dict( + type='DDRHead', + in_channels=64 * 4, + channels=128, + dropout_ratio=0., + num_classes=19, + align_corners=False, + norm_cfg=norm_cfg, + loss_decode=[ + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + # class_weight=class_weight, + loss_weight=1.0), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + # class_weight=class_weight,s + loss_weight=0.4), + ]), + + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3_r50-d8.py new file mode 100644 index 0000000..22efe9a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3_r50-d8.py @@ -0,0 +1,52 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='ASPPHead', + in_channels=2048, + in_index=3, + channels=512, + dilations=(1, 12, 24, 36), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3_unet_s5-d16.py b/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3_unet_s5-d16.py new file mode 100644 index 0000000..92df52c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3_unet_s5-d16.py @@ -0,0 +1,58 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='UNet', + in_channels=3, + base_channels=64, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1), + with_cp=False, + conv_cfg=None, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + upsample_cfg=dict(type='InterpConv'), + norm_eval=False), + decode_head=dict( + type='ASPPHead', + in_channels=64, + in_index=4, + channels=16, + dilations=(1, 12, 24, 36), + dropout_ratio=0.1, + num_classes=2, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=128, + in_index=3, + channels=64, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=2, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='slide', crop_size=256, stride=170)) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3plus_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3plus_r50-d8.py new file mode 100644 index 0000000..74dbed5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/deeplabv3plus_r50-d8.py @@ -0,0 +1,54 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='DepthwiseSeparableASPPHead', + in_channels=2048, + in_index=3, + channels=512, + dilations=(1, 12, 24, 36), + c1_in_channels=256, + c1_channels=48, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/dmnet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/dmnet_r50-d8.py new file mode 100644 index 0000000..f66a042 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/dmnet_r50-d8.py @@ -0,0 +1,52 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='DMHead', + in_channels=2048, + in_index=3, + channels=512, + filter_sizes=(1, 3, 5, 7), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict(type='SyncBN', requires_grad=True), + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/dnl_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/dnl_r50-d8.py new file mode 100644 index 0000000..ee64056 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/dnl_r50-d8.py @@ -0,0 +1,54 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='DNLHead', + in_channels=2048, + in_index=3, + channels=512, + dropout_ratio=0.1, + reduction=2, + use_scale=True, + mode='embedded_gaussian', + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/dpt_vit-b16.py b/Seg_All_In_One_MMSeg/configs/_base_/models/dpt_vit-b16.py new file mode 100644 index 0000000..90845b3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/dpt_vit-b16.py @@ -0,0 +1,39 @@ +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='pretrain/vit-b16_p16_224-80ecf9dd.pth', # noqa + backbone=dict( + type='VisionTransformer', + img_size=224, + embed_dims=768, + num_layers=12, + num_heads=12, + out_indices=(2, 5, 8, 11), + final_norm=False, + with_cls_token=True, + output_cls_token=True), + decode_head=dict( + type='DPTHead', + in_channels=(768, 768, 768, 768), + channels=256, + embed_dims=768, + post_process_channels=[96, 192, 384, 768], + num_classes=150, + readout_type='project', + input_transform='multiple_select', + in_index=(0, 1, 2, 3), + norm_cfg=norm_cfg, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=None, + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) # yapf: disable diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/emanet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/emanet_r50-d8.py new file mode 100644 index 0000000..c55af4f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/emanet_r50-d8.py @@ -0,0 +1,55 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='EMAHead', + in_channels=2048, + in_index=3, + channels=256, + ema_channels=512, + num_bases=64, + num_stages=3, + momentum=0.1, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/en_bisenetv2.py b/Seg_All_In_One_MMSeg/configs/_base_/models/en_bisenetv2.py new file mode 100644 index 0000000..6baf8b5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/en_bisenetv2.py @@ -0,0 +1,92 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='EnBiSeNetV2', + in_channels=3, + detail_channels_stages=(64, 64, 128), + semantic_channels_stages=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + init_cfg=None, + align_corners=False), + decode_head=dict( + type='FCNHead', + in_channels=128, # Should match bga_channels + in_index=4, # The final output from backbone + channels=1024, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, # from detail branch + in_index=0, + channels=256, + num_convs=2, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=64, # from semantic branch stage 3 + in_index=1, + channels=256, + num_convs=2, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, # from semantic branch stage 4 + in_index=2, + channels=256, + num_convs=2, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, # from semantic fused output + in_index=3, + channels=256, + num_convs=2, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/encnet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/encnet_r50-d8.py new file mode 100644 index 0000000..63cec9e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/encnet_r50-d8.py @@ -0,0 +1,56 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='EncHead', + in_channels=[512, 1024, 2048], + in_index=(1, 2, 3), + channels=512, + num_codes=32, + use_se_loss=True, + add_lateral=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_se_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.2)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/erfnet_fcn.py b/Seg_All_In_One_MMSeg/configs/_base_/models/erfnet_fcn.py new file mode 100644 index 0000000..4d68a72 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/erfnet_fcn.py @@ -0,0 +1,40 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='ERFNet', + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128), + dec_upsample_channels=(64, 16), + dec_stages_non_bottleneck=(2, 2), + dec_non_bottleneck_channels=(64, 16), + dropout_ratio=0.1, + init_cfg=None), + decode_head=dict( + type='FCNHead', + in_channels=16, + channels=128, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/fast_scnn.py b/Seg_All_In_One_MMSeg/configs/_base_/models/fast_scnn.py new file mode 100644 index 0000000..11127b0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/fast_scnn.py @@ -0,0 +1,65 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True, momentum=0.01) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='FastSCNN', + downsample_dw_channels=(32, 48), + global_in_channels=64, + global_block_channels=(64, 96, 128), + global_block_strides=(2, 2, 1), + global_out_channels=128, + higher_in_channels=64, + lower_in_channels=128, + fusion_out_channels=128, + out_indices=(0, 1, 2), + norm_cfg=norm_cfg, + align_corners=False), + decode_head=dict( + type='DepthwiseSeparableFCNHead', + in_channels=128, + channels=128, + concat_input=False, + num_classes=19, + in_index=-1, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=32, + num_convs=1, + num_classes=19, + in_index=-2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.4)), + dict( + type='FCNHead', + in_channels=64, + channels=32, + num_convs=1, + num_classes=19, + in_index=-3, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.4)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/fastfcn_r50-d32_jpu_psp.py b/Seg_All_In_One_MMSeg/configs/_base_/models/fastfcn_r50-d32_jpu_psp.py new file mode 100644 index 0000000..6a0fbbd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/fastfcn_r50-d32_jpu_psp.py @@ -0,0 +1,61 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', # 'open-mmlab://resnet18_v1c', 'open-mmlab://resnet50_v1c' 'open-mmlab://resnet101_v1c' + backbone=dict( + type='ResNetV1c', + depth=50, # 18, 50, 101, + num_stages=4, + dilations=(1, 1, 2, 4), + strides=(1, 2, 2, 2), + out_indices=(1, 2, 3), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + neck=dict( + type='JPU', + in_channels=(512, 1024, 2048), # (128, 256, 512), (512, 1024, 2048), (512, 1024, 2048) + mid_channels=512, # 128, 512, 512 + start_level=0, + end_level=-1, + dilations=(1, 2, 4, 8), + align_corners=False, + norm_cfg=norm_cfg), + decode_head=dict( + type='PSPHead', + in_channels=2048, # 512, 2048, 2048 + in_index=2, + channels=512, # 128, 512, 512 + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, # 256, 1024, 1024 + in_index=1, + channels=256, # 64, 256, 256 + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_hr18.py b/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_hr18.py new file mode 100644 index 0000000..01a447a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_hr18.py @@ -0,0 +1,60 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://msra/hrnetv2_w18', + backbone=dict( + type='HRNet', + norm_cfg=norm_cfg, + norm_eval=False, + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(18, 36)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(18, 36, 72)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(18, 36, 72, 144)))), + decode_head=dict( + type='FCNHead', + in_channels=[18, 36, 72, 144], + in_index=(0, 1, 2, 3), + channels=sum([18, 36, 72, 144]), + input_transform='resize_concat', + kernel_size=1, + num_convs=1, + concat_input=False, + dropout_ratio=-1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_r50-d8.py new file mode 100644 index 0000000..9a76a6c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_r50-d8.py @@ -0,0 +1,53 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='FCNHead', + in_channels=2048, + in_index=3, + channels=512, + num_convs=2, + concat_input=True, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_unet_s5-d16.py b/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_unet_s5-d16.py new file mode 100644 index 0000000..9f880d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/fcn_unet_s5-d16.py @@ -0,0 +1,59 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='UNet', + in_channels=3, + base_channels=64, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1), + with_cp=False, + conv_cfg=None, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + upsample_cfg=dict(type='InterpConv'), + norm_eval=False), + decode_head=dict( + type='FCNHead', + in_channels=64, + in_index=4, + channels=64, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=2, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=128, + in_index=3, + channels=64, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=2, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='slide', crop_size=256, stride=170)) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/fpn_poolformer_s12.py b/Seg_All_In_One_MMSeg/configs/_base_/models/fpn_poolformer_s12.py new file mode 100644 index 0000000..086c804 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/fpn_poolformer_s12.py @@ -0,0 +1,54 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/poolformer/poolformer-s12_3rdparty_32xb128_in1k_20220414-f8d83051.pth' # noqa +# TODO: delete custom_imports after mmpretrain supports auto import +# please install mmpretrain >= 1.0.0rc7 +# import mmpretrain.models to trigger register_module in mmpretrain +custom_imports = dict( + imports=['mmpretrain.models'], allow_failed_imports=False) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='mmpretrain.PoolFormer', + arch='s12', + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, prefix='backbone.'), + in_patch_size=7, + in_stride=4, + in_pad=2, + down_patch_size=3, + down_stride=2, + down_pad=1, + drop_rate=0., + drop_path_rate=0., + out_indices=(0, 2, 4, 6), + frozen_stages=0, + ), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=4), + decode_head=dict( + type='FPNHead', + in_channels=[256, 256, 256, 256], + in_index=[0, 1, 2, 3], + feature_strides=[4, 8, 16, 32], + channels=128, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/fpn_r50.py b/Seg_All_In_One_MMSeg/configs/_base_/models/fpn_r50.py new file mode 100644 index 0000000..3baa097 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/fpn_r50.py @@ -0,0 +1,44 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 1, 1), + strides=(1, 2, 2, 2), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=4), + decode_head=dict( + type='FPNHead', + in_channels=[256, 256, 256, 256], + in_index=[0, 1, 2, 3], + feature_strides=[4, 8, 16, 32], + channels=128, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/gcnet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/gcnet_r50-d8.py new file mode 100644 index 0000000..8238d4b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/gcnet_r50-d8.py @@ -0,0 +1,54 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='GCHead', + in_channels=2048, + in_index=3, + channels=512, + ratio=1 / 4., + pooling_type='att', + fusion_types=('channel_add', ), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/icnet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/icnet_r50-d8.py new file mode 100644 index 0000000..4377053 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/icnet_r50-d8.py @@ -0,0 +1,82 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='ICNet', + backbone_cfg=dict( + type='ResNetV1c', + in_channels=3, + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + in_channels=3, + layer_channels=(512, 2048), + light_branch_middle_channels=32, + psp_out_channels=512, + out_channels=(64, 256, 256), + norm_cfg=norm_cfg, + align_corners=False, + ), + neck=dict( + type='ICNeck', + in_channels=(64, 256, 256), + out_channels=128, + norm_cfg=norm_cfg, + align_corners=False), + decode_head=dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + in_index=2, + dropout_ratio=0, + num_classes=19, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=19, + in_index=0, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/isanet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/isanet_r50-d8.py new file mode 100644 index 0000000..e028ba8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/isanet_r50-d8.py @@ -0,0 +1,53 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='ISAHead', + in_channels=2048, + in_index=3, + channels=512, + isa_channels=256, + down_factor=(8, 8), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/knet_r50-d8_my.py b/Seg_All_In_One_MMSeg/configs/_base_/models/knet_r50-d8_my.py new file mode 100644 index 0000000..2c09129 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/knet_r50-d8_my.py @@ -0,0 +1,83 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +# model settings +num_stages = 3 +conv_kernel_size = 1 +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='IterativeDecodeHead', + num_stages=num_stages, + kernel_update_head=[ + dict( + type='KernelUpdateHead', + num_classes=150, + num_ffn_fcs=2, + num_heads=8, + num_mask_fcs=1, + feedforward_channels=2048, + in_channels=512, + out_channels=512, + dropout=0.0, + conv_kernel_size=conv_kernel_size, + ffn_act_cfg=dict(type='ReLU', inplace=True), + with_ffn=True, + feat_transform_cfg=dict( + conv_cfg=dict(type='Conv2d'), act_cfg=None), + kernel_updator_cfg=dict( + type='KernelUpdator', + in_channels=256, + feat_channels=256, + out_channels=256, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN'))) for _ in range(num_stages) + ], + kernel_generate_head=dict( + type='ASPPHead', + # in_channels=2048, + # in_index=3, + # channels=512, + # dilations=(1, 12, 24, 36), + # dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) + diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/lraspp_m-v3-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/lraspp_m-v3-d8.py new file mode 100644 index 0000000..acf70e7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/lraspp_m-v3-d8.py @@ -0,0 +1,33 @@ +# model settings +norm_cfg = dict(type='SyncBN', eps=0.001, requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='MobileNetV3', + arch='large', + out_indices=(1, 3, 16), + norm_cfg=norm_cfg), + decode_head=dict( + type='LRASPPHead', + in_channels=(16, 24, 960), + in_index=(0, 1, 2), + channels=128, + input_transform='multiple_select', + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/mask2former_my.py b/Seg_All_In_One_MMSeg/configs/_base_/models/mask2former_my.py new file mode 100644 index 0000000..42c84da --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/mask2former_my.py @@ -0,0 +1,136 @@ +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='SwinTransformer', + # pretrain_img_size=384, + # embed_dims=128, + # depths=[2, 2, 18, 2], + # num_heads=[4, 8, 16, 32], + # window_size=12, + # mlp_ratio=4, + # qkv_bias=True, + # qk_scale=None, + # drop_rate=0., + # attn_drop_rate=0., + # drop_path_rate=0.3, + # patch_norm=True, + out_indices=(0, 1, 2, 3), + # with_cp=False, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint='https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window12_384_20220317-55b0104a.pth')), + decode_head=dict( + type='Mask2FormerHead', + in_channels=[128, 256, 512, 1024], # TODO + strides=[4, 8, 16, 32], + feat_channels=256, + out_channels=256, + num_classes=150, # TODO + num_queries=100, + num_transformer_feat_level=3, + align_corners=False, + pixel_decoder=dict( + type='mmdet.MSDeformAttnPixelDecoder', + num_outs=3, + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU'), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + num_heads=8, + num_levels=3, + num_points=4, + im2col_step=64, + dropout=0.0, + batch_first=True, + norm_cfg=None, + init_cfg=None), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True))), + init_cfg=None), + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + init_cfg=None), + enforce_decoder_input_project=False, + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + transformer_decoder=dict( # Mask2FormerTransformerDecoder + return_intermediate=True, + num_layers=9, + layer_cfg=dict( # Mask2FormerTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + act_cfg=dict(type='ReLU', inplace=True), + ffn_drop=0.0, + dropout_layer=None, + add_identity=True)), + init_cfg=None), + loss_cls=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * 150 + [0.1]), # TODO + loss_mask=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice=dict( + type='mmdet.DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0), + train_cfg=dict( + num_points=12544, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='mmdet.HungarianAssigner', + match_costs=[ + dict(type='mmdet.ClassificationCost', weight=2.0), + dict( + type='mmdet.CrossEntropyLossCost', + weight=5.0, + use_sigmoid=True), + dict( + type='mmdet.DiceCost', + weight=5.0, + pred_act=True, + eps=1.0) + ]), + sampler=dict(type='mmdet.MaskPseudoSampler'))), + train_cfg=dict(), + test_cfg=dict(mode='whole')) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/maskformer_my.py b/Seg_All_In_One_MMSeg/configs/_base_/models/maskformer_my.py new file mode 100644 index 0000000..580179a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/maskformer_my.py @@ -0,0 +1,108 @@ +data_preprocessor = dict( + type='SegDataPreProcessor', + # size=crop_size, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +# model_cfg +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='ResNet', + # depth=50, + # num_stages=4, + out_indices=(0, 1, 2, 3), + # dilations=(1, 1, 1, 1), + # strides=(1, 2, 2, 2), + # norm_cfg=norm_cfg, + # norm_eval=True, + # style='pytorch', + # contract_dilation=True, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + decode_head=dict( + type='MaskFormerHead', + in_channels=[256, 512, 1024, + 2048], # input channels of pixel_decoder modules + feat_channels=256, + in_index=[0, 1, 2, 3], + num_classes=150, # TODO + out_channels=256, + num_queries=100, + pixel_decoder=dict( + type='mmdet.PixelDecoder', + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU')), + enforce_decoder_input_project=False, + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + transformer_decoder=dict( # DetrTransformerDecoder + return_intermediate=True, + num_layers=6, + layer_cfg=dict( # DetrTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.1, + proj_drop=0.1, + dropout_layer=None, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.1, + proj_drop=0.1, + dropout_layer=None, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + act_cfg=dict(type='ReLU', inplace=True), + ffn_drop=0.1, + dropout_layer=None, + add_identity=True)), + init_cfg=None), + loss_cls=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + reduction='mean', + class_weight=[1.0] * 150 + [0.1]), # TODO + loss_mask=dict( + type='mmdet.FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + reduction='mean', + loss_weight=20.0), + loss_dice=dict( + type='mmdet.DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=1.0), + train_cfg=dict( + assigner=dict( + type='mmdet.HungarianAssigner', + match_costs=[ + dict(type='mmdet.ClassificationCost', weight=1.0), + dict( + type='mmdet.FocalLossCost', + weight=20.0, + binary_input=True), + dict( + type='mmdet.DiceCost', + weight=1.0, + pred_act=True, + eps=1.0) + ]), + sampler=dict(type='mmdet.MaskPseudoSampler'))), + # training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole'), +) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/my_bisenetv2_A1.py b/Seg_All_In_One_MMSeg/configs/_base_/models/my_bisenetv2_A1.py new file mode 100644 index 0000000..9d8bbdd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/my_bisenetv2_A1.py @@ -0,0 +1,88 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='My_BiSeNetV2_A1', + detail_channels=(64, 64, 128), + semantic_channels=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + init_cfg=None, + align_corners=False), + decode_head=dict( + type='FCNHead', + in_channels=128, + in_index=0, + channels=1024, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=19, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=19, + in_index=3, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=19, + in_index=4, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/my_bisenetv2_A2.py b/Seg_All_In_One_MMSeg/configs/_base_/models/my_bisenetv2_A2.py new file mode 100644 index 0000000..d457b20 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/my_bisenetv2_A2.py @@ -0,0 +1,88 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='My_BiSeNetV2_A2', + detail_channels=(64, 64, 128), + semantic_channels=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + init_cfg=None, + align_corners=False), + decode_head=dict( + type='FCNHead', + in_channels=128, + in_index=0, + channels=1024, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=19, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=19, + in_index=3, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=19, + in_index=4, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/nonlocal_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/nonlocal_r50-d8.py new file mode 100644 index 0000000..7d73a84 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/nonlocal_r50-d8.py @@ -0,0 +1,54 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='NLHead', + in_channels=2048, + in_index=3, + channels=512, + dropout_ratio=0.1, + reduction=2, + use_scale=True, + mode='embedded_gaussian', + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/ocrnet_hr18.py b/Seg_All_In_One_MMSeg/configs/_base_/models/ocrnet_hr18.py new file mode 100644 index 0000000..6c7fcfe --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/ocrnet_hr18.py @@ -0,0 +1,76 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='CascadeEncoderDecoder', + data_preprocessor=data_preprocessor, + num_stages=2, + pretrained='open-mmlab://msra/hrnetv2_w18', + backbone=dict( + type='HRNet', + norm_cfg=norm_cfg, + norm_eval=False, + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(18, 36)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(18, 36, 72)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(18, 36, 72, 144)))), + decode_head=[ + dict( + type='FCNHead', + in_channels=[18, 36, 72, 144], + channels=sum([18, 36, 72, 144]), + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + kernel_size=1, + num_convs=1, + concat_input=False, + dropout_ratio=-1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[18, 36, 72, 144], + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + channels=512, + ocr_channels=256, + dropout_ratio=-1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/ocrnet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/ocrnet_r50-d8.py new file mode 100644 index 0000000..0a2588f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/ocrnet_r50-d8.py @@ -0,0 +1,55 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='CascadeEncoderDecoder', + data_preprocessor=data_preprocessor, + num_stages=2, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=[ + dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=2048, + in_index=3, + channels=512, + ocr_channels=256, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/pidnet.py b/Seg_All_In_One_MMSeg/configs/_base_/models/pidnet.py new file mode 100644 index 0000000..4411be3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/pidnet.py @@ -0,0 +1,68 @@ +# The class_weight is borrowed from https://github.com/openseg-group/OCNet.pytorch/issues/14 # noqa +# Licensed under the MIT License +# 无侧重 +# class_weight = [ +# 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, 1.0489, 0.8786, 1.0023, +# 0.9539, 0.9843, 1.1116, 0.9037, 1.0865, 1.0955, 1.0865, 1.1529, 1.0507 +# ] +# class_weight = [ +# 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, 1.0489, 0.8786, 1.0023, +# 0.9539, 0.9843, 1.1116 +# ] + + +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/pidnet/pidnet-s_imagenet1k_20230306-715e6273.pth' # noqa +# crop_size = (1024, 1024) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size=(1024, 1024)) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='PIDNet', + in_channels=3, + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + align_corners=False, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU', inplace=True), + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file)), + decode_head=dict( + type='PIDHead', + in_channels=128, + channels=128, + num_classes=19, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU', inplace=True), + align_corners=True, + loss_decode=[ + dict( + type='CrossEntropyLoss', + use_sigmoid=False, + # class_weight=class_weight, + loss_weight=0.4), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + # class_weight=class_weight, + loss_weight=1.0), + dict(type='BoundaryLoss', loss_weight=20.0), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + # class_weight=class_weight, + loss_weight=1.0) + ]), + train_cfg=dict(), + test_cfg=dict(mode='whole')) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/pointrend_r50.py b/Seg_All_In_One_MMSeg/configs/_base_/models/pointrend_r50.py new file mode 100644 index 0000000..8a27e85 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/pointrend_r50.py @@ -0,0 +1,64 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='CascadeEncoderDecoder', + data_preprocessor=data_preprocessor, + num_stages=2, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 1, 1), + strides=(1, 2, 2, 2), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + neck=dict( + type='FPN', + in_channels=[256, 512, 1024, 2048], + out_channels=256, + num_outs=4), + decode_head=[ + dict( + type='FPNHead', + in_channels=[256, 256, 256, 256], + in_index=[0, 1, 2, 3], + feature_strides=[4, 8, 16, 32], + channels=128, + dropout_ratio=-1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='PointHead', + in_channels=[256], + in_index=[0], + channels=256, + num_fcs=3, + coarse_pred_each_layer=True, + dropout_ratio=-1, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ], + # model training and testing settings + train_cfg=dict( + num_points=2048, oversample_ratio=3, importance_sample_ratio=0.75), + test_cfg=dict( + mode='whole', + subdivision_steps=2, + subdivision_num_points=8196, + scale_factor=2)) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/psanet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/psanet_r50-d8.py new file mode 100644 index 0000000..40fd5a9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/psanet_r50-d8.py @@ -0,0 +1,57 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='PSAHead', + in_channels=2048, + in_index=3, + channels=512, + mask_size=(97, 97), + psa_type='bi-direction', + compact=False, + shrink_factor=2, + normalization_factor=1.0, + psa_softmax=True, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/pspnet_r50-d8.py b/Seg_All_In_One_MMSeg/configs/_base_/models/pspnet_r50-d8.py new file mode 100644 index 0000000..c257b8b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/pspnet_r50-d8.py @@ -0,0 +1,52 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=3, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/pspnet_unet_s5-d16.py b/Seg_All_In_One_MMSeg/configs/_base_/models/pspnet_unet_s5-d16.py new file mode 100644 index 0000000..834a22a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/pspnet_unet_s5-d16.py @@ -0,0 +1,58 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='UNet', + in_channels=3, + base_channels=64, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1), + with_cp=False, + conv_cfg=None, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + upsample_cfg=dict(type='InterpConv'), + norm_eval=False), + decode_head=dict( + type='PSPHead', + in_channels=64, + in_index=4, + channels=16, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=2, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=128, + in_index=3, + channels=64, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=2, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='slide', crop_size=256, stride=170)) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/san_vit-b16.py b/Seg_All_In_One_MMSeg/configs/_base_/models/san_vit-b16.py new file mode 100644 index 0000000..96ac41b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/san_vit-b16.py @@ -0,0 +1,137 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) + +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[122.7709, 116.7460, 104.0937], + std=[68.5005, 66.6322, 70.3232], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size_divisor=640, + test_cfg=dict(size_divisor=32)) + +num_classes = 171 +model = dict( + type='MultimodalEncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='pretrain/clip_vit_base_patch16_224.pth', + asymetric_input=True, + encoder_resolution=0.5, + image_encoder=dict( + type='VisionTransformer', + img_size=(224, 224), + patch_size=16, + patch_pad=0, + in_channels=3, + embed_dims=768, + num_layers=9, + num_heads=12, + mlp_ratio=4, + out_origin=True, + out_indices=(2, 5, 8), + qkv_bias=True, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.0, + with_cls_token=True, + output_cls_token=True, + patch_bias=False, + pre_norm=True, + norm_cfg=dict(type='LN', eps=1e-5), + act_cfg=dict(type='QuickGELU'), + norm_eval=False, + interpolate_mode='bicubic', + frozen_exclude=['pos_embed']), + text_encoder=dict( + type='CLIPTextEncoder', + dataset_name=None, + templates='vild', + embed_dims=512, + num_layers=12, + num_heads=8, + mlp_ratio=4, + output_dims=512, + cache_feature=True, + cat_bg=True, + norm_cfg=dict(type='LN', eps=1e-5) + ), + decode_head=dict( + type='SideAdapterCLIPHead', + num_classes=num_classes, + deep_supervision_idxs=[7], + san_cfg=dict( + in_channels=3, + clip_channels=768, + embed_dims=240, + patch_size=16, + patch_bias=True, + num_queries=100, + cfg_encoder=dict( + num_encode_layer=8, + num_heads=6, + mlp_ratio=4 + ), + fusion_index=[0, 1, 2, 3], + cfg_decoder=dict( + num_heads=12, + num_layers=1, + embed_channels=256, + mlp_channels=256, + num_mlp=3, + rescale=True), + norm_cfg=dict(type='LN', eps=1e-6), + ), + maskgen_cfg=dict( + sos_token_format='cls_token', + sos_token_num=100, + cross_attn=False, + num_layers=3, + embed_dims=768, + num_heads=12, + mlp_ratio=4, + qkv_bias=True, + out_dims=512, + final_norm=True, + act_cfg=dict(type='QuickGELU'), + norm_cfg=dict(type='LN', eps=1e-5), + frozen_exclude=[] + ), + align_corners=False, + train_cfg=dict( + num_points=12544, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='ClassificationCost', weight=2.0), + dict( + type='CrossEntropyLossCost', + weight=5.0, + use_sigmoid=True), + dict( + type='DiceCost', + weight=5.0, + pred_act=True, + eps=1.0) + ])), + loss_decode=[dict(type='CrossEntropyLoss', + loss_name='loss_cls_ce', + loss_weight=2.0, + class_weight=[1.0] * num_classes + [0.1]), + dict(type='CrossEntropyLoss', + use_sigmoid=True, + loss_name='loss_mask_ce', + loss_weight=5.0), + dict(type='DiceLoss', + ignore_index=None, + naive_dice=True, + eps=1, + loss_name='loss_mask_dice', + loss_weight=5.0) + ]), + + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) # yapf: disable diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/segformer_mit-b0.py b/Seg_All_In_One_MMSeg/configs/_base_/models/segformer_mit-b0.py new file mode 100644 index 0000000..46841ad --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/segformer_mit-b0.py @@ -0,0 +1,42 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='MixVisionTransformer', + in_channels=3, + embed_dims=32, + num_stages=4, + num_layers=[2, 2, 2, 2], + num_heads=[1, 2, 5, 8], + patch_sizes=[7, 3, 3, 3], + sr_ratios=[8, 4, 2, 1], + out_indices=(0, 1, 2, 3), + mlp_ratio=4, + qkv_bias=True, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.1), + decode_head=dict( + type='SegformerHead', + in_channels=[32, 64, 160, 256], + in_index=[0, 1, 2, 3], + channels=256, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/segmenter_vit-b16_mask.py b/Seg_All_In_One_MMSeg/configs/_base_/models/segmenter_vit-b16_mask.py new file mode 100644 index 0000000..8f3dad1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/segmenter_vit-b16_mask.py @@ -0,0 +1,44 @@ +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segmenter/vit_base_p16_384_20220308-96dfe169.pth' # noqa +# model settings +backbone_norm_cfg = dict(type='LN', eps=1e-6, requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[127.5, 127.5, 127.5], + std=[127.5, 127.5, 127.5], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=checkpoint, + backbone=dict( + type='VisionTransformer', + img_size=(512, 512), + patch_size=16, + in_channels=3, + embed_dims=768, + num_layers=12, + num_heads=12, + drop_path_rate=0.1, + attn_drop_rate=0.0, + drop_rate=0.0, + final_norm=True, + norm_cfg=backbone_norm_cfg, + with_cls_token=True, + interpolate_mode='bicubic', + ), + decode_head=dict( + type='SegmenterMaskTransformerHead', + in_channels=768, + channels=768, + num_classes=150, + num_layers=2, + num_heads=12, + embed_dims=768, + dropout_ratio=0.0, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + ), + test_cfg=dict(mode='slide', crop_size=(512, 512), stride=(480, 480)), +) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/setr_mla.py b/Seg_All_In_One_MMSeg/configs/_base_/models/setr_mla.py new file mode 100644 index 0000000..dedf169 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/setr_mla.py @@ -0,0 +1,103 @@ +# model settings +backbone_norm_cfg = dict(type='LN', eps=1e-6, requires_grad=True) +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='pretrain/jx_vit_large_p16_384-b3be5167.pth', + backbone=dict( + type='VisionTransformer', + img_size=(768, 768), + patch_size=16, + in_channels=3, + embed_dims=1024, + num_layers=24, + num_heads=16, + out_indices=(5, 11, 17, 23), + drop_rate=0.1, + norm_cfg=backbone_norm_cfg, + with_cls_token=False, + interpolate_mode='bilinear', + ), + neck=dict( + type='MLANeck', + in_channels=[1024, 1024, 1024, 1024], + out_channels=256, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + ), + decode_head=dict( + type='SETRMLAHead', + in_channels=(256, 256, 256, 256), + channels=512, + in_index=(0, 1, 2, 3), + dropout_ratio=0, + mla_channels=128, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=256, + channels=256, + in_index=0, + dropout_ratio=0, + num_convs=0, + kernel_size=1, + concat_input=False, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='FCNHead', + in_channels=256, + channels=256, + in_index=1, + dropout_ratio=0, + num_convs=0, + kernel_size=1, + concat_input=False, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='FCNHead', + in_channels=256, + channels=256, + in_index=2, + dropout_ratio=0, + num_convs=0, + kernel_size=1, + concat_input=False, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='FCNHead', + in_channels=256, + channels=256, + in_index=3, + dropout_ratio=0, + num_convs=0, + kernel_size=1, + concat_input=False, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + ], + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/setr_naive.py b/Seg_All_In_One_MMSeg/configs/_base_/models/setr_naive.py new file mode 100644 index 0000000..ccf5b33 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/setr_naive.py @@ -0,0 +1,88 @@ +# model settings +backbone_norm_cfg = dict(type='LN', eps=1e-6, requires_grad=True) +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='pretrain/jx_vit_large_p16_384-b3be5167.pth', + backbone=dict( + type='VisionTransformer', + img_size=(768, 768), + patch_size=16, + in_channels=3, + embed_dims=1024, + num_layers=24, + num_heads=16, + out_indices=(9, 14, 19, 23), + drop_rate=0.1, + norm_cfg=backbone_norm_cfg, + with_cls_token=True, + interpolate_mode='bilinear', + ), + decode_head=dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=3, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=1, + up_scale=4, + kernel_size=1, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=0, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=1, + up_scale=4, + kernel_size=1, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=1, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=1, + up_scale=4, + kernel_size=1, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=2, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=1, + up_scale=4, + kernel_size=1, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)) + ], + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/setr_pup.py b/Seg_All_In_One_MMSeg/configs/_base_/models/setr_pup.py new file mode 100644 index 0000000..df1bc18 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/setr_pup.py @@ -0,0 +1,88 @@ +# model settings +backbone_norm_cfg = dict(type='LN', eps=1e-6, requires_grad=True) +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='pretrain/jx_vit_large_p16_384-b3be5167.pth', + backbone=dict( + type='VisionTransformer', + img_size=(768, 768), + patch_size=16, + in_channels=3, + embed_dims=1024, + num_layers=24, + num_heads=16, + out_indices=(9, 14, 19, 23), + drop_rate=0.1, + norm_cfg=backbone_norm_cfg, + with_cls_token=True, + interpolate_mode='bilinear', + ), + decode_head=dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=3, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=4, + up_scale=2, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=0, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=1, + up_scale=4, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=1, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=1, + up_scale=4, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=2, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=1, + up_scale=4, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + ], + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/stdc.py b/Seg_All_In_One_MMSeg/configs/_base_/models/stdc.py new file mode 100644 index 0000000..01bf2b9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/stdc.py @@ -0,0 +1,91 @@ +norm_cfg = dict(type='BN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='STDCContextPathNet', + backbone_cfg=dict( + type='STDCNet', + stdc_type='STDCNet1', + in_channels=3, + channels=(32, 64, 256, 512, 1024), + bottleneck_type='cat', + num_convs=4, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + with_final_conv=False), + last_in_channels=(1024, 512), + out_channels=128, + ffm_cfg=dict(in_channels=384, out_channels=256, scale_factor=4)), + decode_head=dict( + type='FCNHead', + in_channels=256, + channels=256, + num_convs=1, + num_classes=19, + in_index=3, + concat_input=False, + dropout_ratio=0.1, + norm_cfg=norm_cfg, + align_corners=True, + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000), + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=19, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000), + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000), + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='STDCHead', + in_channels=256, + channels=64, + num_convs=1, + num_classes=2, + boundary_threshold=0.1, + in_index=0, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=True, + loss_decode=[ + dict( + type='CrossEntropyLoss', + loss_name='loss_ce', + use_sigmoid=True, + loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=1.0) + ]), + ], + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/twins_pcpvt-s_fpn.py b/Seg_All_In_One_MMSeg/configs/_base_/models/twins_pcpvt-s_fpn.py new file mode 100644 index 0000000..059210b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/twins_pcpvt-s_fpn.py @@ -0,0 +1,53 @@ +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/pcpvt_small_20220308-e638c41c.pth' # noqa + +# model settings +backbone_norm_cfg = dict(type='LN') +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='PCPVT', + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + in_channels=3, + embed_dims=[64, 128, 320, 512], + num_heads=[1, 2, 5, 8], + patch_sizes=[4, 2, 2, 2], + strides=[4, 2, 2, 2], + mlp_ratios=[8, 8, 4, 4], + out_indices=(0, 1, 2, 3), + qkv_bias=True, + norm_cfg=backbone_norm_cfg, + depths=[3, 4, 6, 3], + sr_ratios=[8, 4, 2, 1], + norm_after_stage=False, + drop_rate=0.0, + attn_drop_rate=0., + drop_path_rate=0.2), + neck=dict( + type='FPN', + in_channels=[64, 128, 320, 512], + out_channels=256, + num_outs=4), + decode_head=dict( + type='FPNHead', + in_channels=[256, 256, 256, 256], + in_index=[0, 1, 2, 3], + feature_strides=[4, 8, 16, 32], + channels=128, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/twins_pcpvt-s_upernet.py b/Seg_All_In_One_MMSeg/configs/_base_/models/twins_pcpvt-s_upernet.py new file mode 100644 index 0000000..585a76f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/twins_pcpvt-s_upernet.py @@ -0,0 +1,61 @@ +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/pcpvt_small_20220308-e638c41c.pth' # noqa + +# model settings +backbone_norm_cfg = dict(type='LN') +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='PCPVT', + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + in_channels=3, + embed_dims=[64, 128, 320, 512], + num_heads=[1, 2, 5, 8], + patch_sizes=[4, 2, 2, 2], + strides=[4, 2, 2, 2], + mlp_ratios=[8, 8, 4, 4], + out_indices=(0, 1, 2, 3), + qkv_bias=True, + norm_cfg=backbone_norm_cfg, + depths=[3, 4, 6, 3], + sr_ratios=[8, 4, 2, 1], + norm_after_stage=False, + drop_rate=0.0, + attn_drop_rate=0., + drop_path_rate=0.2), + decode_head=dict( + type='UPerHead', + in_channels=[64, 128, 320, 512], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=320, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_beit.py b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_beit.py new file mode 100644 index 0000000..691e288 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_beit.py @@ -0,0 +1,58 @@ +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='BEiT', + img_size=(640, 640), + patch_size=16, + in_channels=3, + embed_dims=768, + num_layers=12, + num_heads=12, + mlp_ratio=4, + out_indices=(3, 5, 7, 11), + qv_bias=True, + attn_drop_rate=0.0, + drop_path_rate=0.1, + norm_cfg=dict(type='LN', eps=1e-6), + act_cfg=dict(type='GELU'), + norm_eval=False, + init_values=0.1), + neck=dict(type='Feature2Pyramid', embed_dim=768, rescales=[4, 2, 1, 0.5]), + decode_head=dict( + type='UPerHead', + in_channels=[768, 768, 768, 768], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=768, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=768, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_convnext.py b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_convnext.py new file mode 100644 index 0000000..958994c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_convnext.py @@ -0,0 +1,52 @@ +norm_cfg = dict(type='SyncBN', requires_grad=True) +custom_imports = dict(imports='mmpretrain.models', allow_failed_imports=False) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-base_3rdparty_32xb128-noema_in1k_20220301-2a0ee547.pth' # noqa +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='mmpretrain.ConvNeXt', + arch='base', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.4, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + decode_head=dict( + type='UPerHead', + in_channels=[128, 256, 512, 1024], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=384, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_mae.py b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_mae.py new file mode 100644 index 0000000..b833b67 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_mae.py @@ -0,0 +1,57 @@ +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='MAE', + img_size=(640, 640), + patch_size=16, + in_channels=3, + embed_dims=768, + num_layers=12, + num_heads=12, + mlp_ratio=4, + out_indices=(3, 5, 7, 11), + attn_drop_rate=0.0, + drop_path_rate=0.1, + norm_cfg=dict(type='LN', eps=1e-6), + act_cfg=dict(type='GELU'), + norm_eval=False, + init_values=0.1), + neck=dict(type='Feature2Pyramid', embed_dim=768, rescales=[4, 2, 1, 0.5]), + decode_head=dict( + type='UPerHead', + in_channels=[384, 384, 384, 384], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=384, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_r50.py b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_r50.py new file mode 100644 index 0000000..97f2eb8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_r50.py @@ -0,0 +1,52 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 1, 1), + strides=(1, 2, 2, 2), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='UPerHead', + in_channels=[256, 512, 1024, 2048], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_swin.py b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_swin.py new file mode 100644 index 0000000..61cfce0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_swin.py @@ -0,0 +1,62 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +backbone_norm_cfg = dict(type='LN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='SwinTransformer', + pretrain_img_size=224, + embed_dims=96, + patch_size=4, + window_size=7, + mlp_ratio=4, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + strides=(4, 2, 2, 2), + out_indices=(0, 1, 2, 3), + qkv_bias=True, + qk_scale=None, + patch_norm=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + use_abs_pos_embed=False, + act_cfg=dict(type='GELU'), + norm_cfg=backbone_norm_cfg), + decode_head=dict( + type='UPerHead', + in_channels=[96, 192, 384, 768], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=384, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_vit-b16_ln_mln.py b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_vit-b16_ln_mln.py new file mode 100644 index 0000000..776525a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/upernet_vit-b16_ln_mln.py @@ -0,0 +1,65 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='pretrain/jx_vit_base_p16_224-80ecf9dd.pth', + backbone=dict( + type='VisionTransformer', + img_size=(512, 512), + patch_size=16, + in_channels=3, + embed_dims=768, + num_layers=12, + num_heads=12, + mlp_ratio=4, + out_indices=(2, 5, 8, 11), + qkv_bias=True, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.0, + with_cls_token=True, + norm_cfg=dict(type='LN', eps=1e-6), + act_cfg=dict(type='GELU'), + norm_eval=False, + interpolate_mode='bicubic'), + neck=dict( + type='MultiLevelNeck', + in_channels=[768, 768, 768, 768], + out_channels=768, + scales=[4, 2, 1, 0.5]), + decode_head=dict( + type='UPerHead', + in_channels=[768, 768, 768, 768], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=768, + in_index=3, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) # yapf: disable diff --git a/Seg_All_In_One_MMSeg/configs/_base_/models/vpd_sd.py b/Seg_All_In_One_MMSeg/configs/_base_/models/vpd_sd.py new file mode 100644 index 0000000..87321e7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/models/vpd_sd.py @@ -0,0 +1,86 @@ +# model settings +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[127.5, 127.5, 127.5], + std=[127.5, 127.5, 127.5], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=0) + +# adapted from stable-diffusion/configs/stable-diffusion/v1-inference.yaml +stable_diffusion_cfg = dict( + base_learning_rate=0.0001, + target='ldm.models.diffusion.ddpm.LatentDiffusion', + checkpoint='https://download.openmmlab.com/mmsegmentation/v0.5/' + 'vpd/stable_diffusion_v1-5_pretrain_third_party.pth', + params=dict( + linear_start=0.00085, + linear_end=0.012, + num_timesteps_cond=1, + log_every_t=200, + timesteps=1000, + first_stage_key='jpg', + cond_stage_key='txt', + image_size=64, + channels=4, + cond_stage_trainable=False, + conditioning_key='crossattn', + monitor='val/loss_simple_ema', + scale_factor=0.18215, + use_ema=False, + scheduler_config=dict( + target='ldm.lr_scheduler.LambdaLinearScheduler', + params=dict( + warm_up_steps=[10000], + cycle_lengths=[10000000000000], + f_start=[1e-06], + f_max=[1.0], + f_min=[1.0])), + unet_config=dict( + target='ldm.modules.diffusionmodules.openaimodel.UNetModel', + params=dict( + image_size=32, + in_channels=4, + out_channels=4, + model_channels=320, + attention_resolutions=[4, 2, 1], + num_res_blocks=2, + channel_mult=[1, 2, 4, 4], + num_heads=8, + use_spatial_transformer=True, + transformer_depth=1, + context_dim=768, + use_checkpoint=True, + legacy=False)), + first_stage_config=dict( + target='ldm.models.autoencoder.AutoencoderKL', + params=dict( + embed_dim=4, + monitor='val/rec_loss', + ddconfig=dict( + double_z=True, + z_channels=4, + resolution=256, + in_channels=3, + out_ch=3, + ch=128, + ch_mult=[1, 2, 4, 4], + num_res_blocks=2, + attn_resolutions=[], + dropout=0.0), + lossconfig=dict(target='torch.nn.Identity'))), + cond_stage_config=dict( + target='ldm.modules.encoders.modules.AbstractEncoder'))) + +model = dict( + type='DepthEstimator', + data_preprocessor=data_preprocessor, + backbone=dict( + type='VPD', + diffusion_cfg=stable_diffusion_cfg, + ), +) + +# some of the parameters in stable-diffusion model will not be updated +# during training +find_unused_parameters = True diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k.py new file mode 100644 index 0000000..60d7bec --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=160000, + by_epoch=False) +] +# training schedule for 160k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=160000, val_interval=16000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=16000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k_check_10000.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k_check_10000.py new file mode 100644 index 0000000..1221375 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k_check_10000.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=160000, + by_epoch=False) +] +# training schedule for 160k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=160000, val_interval=10000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=10000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k_check_16000.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k_check_16000.py new file mode 100644 index 0000000..60d7bec --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_160k_check_16000.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=160000, + by_epoch=False) +] +# training schedule for 160k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=160000, val_interval=16000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=16000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_20k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_20k.py new file mode 100644 index 0000000..e809e3e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_20k.py @@ -0,0 +1,24 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=20000, + by_epoch=False) +] +# training schedule for 20k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=20000, val_interval=2000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=2000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_240k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_240k.py new file mode 100644 index 0000000..feb2ce9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_240k.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=240000, + by_epoch=False) +] +# training schedule for 240k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=240000, val_interval=24000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=24000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_25k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_25k.py new file mode 100644 index 0000000..825e141 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_25k.py @@ -0,0 +1,28 @@ +# optimizer +optimizer = dict(type='AdamW', lr=0.001, weight_decay=0.1) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=3e-2, begin=0, end=12000, + by_epoch=False), + dict( + type='PolyLRRatio', + eta_min_ratio=3e-2, + power=0.9, + begin=12000, + end=24000, + by_epoch=False), + dict(type='ConstantLR', by_epoch=False, factor=1, begin=24000, end=25000) +] +# training schedule for 25k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=25000, val_interval=1000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=2000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_300e_val1_check10.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_300e_val1_check10.py new file mode 100644 index 0000000..0806228 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_300e_val1_check10.py @@ -0,0 +1,30 @@ +# optimizer +# For SGD. +# optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +# For AdamW. +optimizer = dict(type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.01) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) + +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=300, + by_epoch=True) +] + +# training schedule for 300 epochs +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=300, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=1085, log_metric_by_epoch=True), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=True, interval=10, save_best='mIoU', rule='greater'), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_300e_val1_check10_SGD.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_300e_val1_check10_SGD.py new file mode 100644 index 0000000..6108225 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_300e_val1_check10_SGD.py @@ -0,0 +1,30 @@ +# optimizer +# For SGD. +# optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +# For AdamW. +# optimizer = dict(type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.01) +# optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) + +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=300, + by_epoch=True) +] + +# training schedule for 300 epochs +train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=300, val_interval=1) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=1085, log_metric_by_epoch=True), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=True, interval=10, save_best='mIoU', rule='greater'), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_320k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_320k.py new file mode 100644 index 0000000..70b063a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_320k.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=320000, + by_epoch=False) +] +# training schedule for 320k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=320000, val_interval=32000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=32000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_40k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_40k.py new file mode 100644 index 0000000..4b82333 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_40k.py @@ -0,0 +1,24 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=40000, + by_epoch=False) +] +# training schedule for 40k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=40000, val_interval=4000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_40k_check_4000.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_40k_check_4000.py new file mode 100644 index 0000000..641fdd5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_40k_check_4000.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=40000, + by_epoch=False) +] +# training schedule for 40k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=40000, val_interval=4000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_4k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_4k.py new file mode 100644 index 0000000..2f60573 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_4k.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=4000, + by_epoch=False) +] +# training schedule for n k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=4000, val_interval=400) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=400), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_4k_check_400.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_4k_check_400.py new file mode 100644 index 0000000..ebbaa55 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_4k_check_400.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=4000, + by_epoch=False) +] +# training schedule for 4k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=4000, val_interval=400) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=400), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_80k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_80k.py new file mode 100644 index 0000000..0dcd6c4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_80k.py @@ -0,0 +1,24 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=80000, + by_epoch=False) +] +# training schedule for 80k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=8000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=8000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_XXXk_model.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_XXXk_model.py new file mode 100644 index 0000000..7e38077 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedule_XXXk_model.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=vars_file.schedule_k_times000, + by_epoch=False) +] +# training schedule for n k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=vars_file.schedule_k_times000, val_interval=vars_file.schedule_k_times00) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=vars_file.LoggerHook_interval, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=vars_file.schedule_k_times00), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedules_4k.py b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedules_4k.py new file mode 100644 index 0000000..ebbaa55 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/_base_/schedules/schedules_4k.py @@ -0,0 +1,25 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=4000, + by_epoch=False) +] +# training schedule for 4k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=4000, val_interval=400) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=400), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/configs/ann/README.md b/Seg_All_In_One_MMSeg/configs/ann/README.md new file mode 100644 index 0000000..1281a9e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/README.md @@ -0,0 +1,68 @@ +# ANN + +> [Asymmetric Non-local Neural Networks for Semantic Segmentation](https://arxiv.org/abs/1908.07678) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +The non-local module works as a particularly useful technique for semantic segmentation while criticized for its prohibitive computation and GPU memory occupation. In this paper, we present Asymmetric Non-local Neural Network to semantic segmentation, which has two prominent components: Asymmetric Pyramid Non-local Block (APNB) and Asymmetric Fusion Non-local Block (AFNB). APNB leverages a pyramid sampling module into the non-local block to largely reduce the computation and memory consumption without sacrificing the performance. AFNB is adapted from APNB to fuse the features of different levels under a sufficient consideration of long range dependencies and thus considerably improves the performance. Extensive experiments on semantic segmentation benchmarks demonstrate the effectiveness and efficiency of our work. In particular, we report the state-of-the-art performance of 81.3 mIoU on the Cityscapes test set. For a 256x128 input, APNB is around 6 times faster than a non-local block on GPU while 28 times smaller in GPU running memory occupation. Code is available at: [this https URL](https://github.com/MendelXu/ANN). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ANN | R-50-D8 | 512x1024 | 40000 | 6 | 3.71 | V100 | 77.40 | 78.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_40k_cityscapes/ann_r50-d8_512x1024_40k_cityscapes_20200605_095211-049fc292.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_40k_cityscapes/ann_r50-d8_512x1024_40k_cityscapes_20200605_095211.log.json) | +| ANN | R-101-D8 | 512x1024 | 40000 | 9.5 | 2.55 | V100 | 76.55 | 78.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x1024_40k_cityscapes/ann_r101-d8_512x1024_40k_cityscapes_20200605_095243-adf6eece.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x1024_40k_cityscapes/ann_r101-d8_512x1024_40k_cityscapes_20200605_095243.log.json) | +| ANN | R-50-D8 | 769x769 | 40000 | 6.8 | 1.70 | V100 | 78.89 | 80.46 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_769x769_40k_cityscapes/ann_r50-d8_769x769_40k_cityscapes_20200530_025712-2b46b04d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_769x769_40k_cityscapes/ann_r50-d8_769x769_40k_cityscapes_20200530_025712.log.json) | +| ANN | R-101-D8 | 769x769 | 40000 | 10.7 | 1.15 | V100 | 79.32 | 80.94 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_769x769_40k_cityscapes/ann_r101-d8_769x769_40k_cityscapes_20200530_025720-059bff28.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_769x769_40k_cityscapes/ann_r101-d8_769x769_40k_cityscapes_20200530_025720.log.json) | +| ANN | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 77.34 | 78.65 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_80k_cityscapes/ann_r50-d8_512x1024_80k_cityscapes_20200607_101911-5a9ad545.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_80k_cityscapes/ann_r50-d8_512x1024_80k_cityscapes_20200607_101911.log.json) | +| ANN | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 77.14 | 78.81 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x1024_80k_cityscapes/ann_r101-d8_512x1024_80k_cityscapes_20200607_013728-aceccc6e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x1024_80k_cityscapes/ann_r101-d8_512x1024_80k_cityscapes_20200607_013728.log.json) | +| ANN | R-50-D8 | 769x769 | 80000 | - | - | V100 | 78.88 | 80.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_769x769_80k_cityscapes/ann_r50-d8_769x769_80k_cityscapes_20200607_044426-cc7ff323.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_769x769_80k_cityscapes/ann_r50-d8_769x769_80k_cityscapes_20200607_044426.log.json) | +| ANN | R-101-D8 | 769x769 | 80000 | - | - | V100 | 78.80 | 80.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_769x769_80k_cityscapes/ann_r101-d8_769x769_80k_cityscapes_20200607_013713-a9d4be8d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_769x769_80k_cityscapes/ann_r101-d8_769x769_80k_cityscapes_20200607_013713.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ANN | R-50-D8 | 512x512 | 80000 | 9.1 | 21.01 | V100 | 41.01 | 42.30 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_80k_ade20k/ann_r50-d8_512x512_80k_ade20k_20200615_014818-26f75e11.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_80k_ade20k/ann_r50-d8_512x512_80k_ade20k_20200615_014818.log.json) | +| ANN | R-101-D8 | 512x512 | 80000 | 12.5 | 14.12 | V100 | 42.94 | 44.18 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_80k_ade20k/ann_r101-d8_512x512_80k_ade20k_20200615_014818-c0153543.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_80k_ade20k/ann_r101-d8_512x512_80k_ade20k_20200615_014818.log.json) | +| ANN | R-50-D8 | 512x512 | 160000 | - | - | V100 | 41.74 | 42.62 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_160k_ade20k/ann_r50-d8_512x512_160k_ade20k_20200615_231733-892247bc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_160k_ade20k/ann_r50-d8_512x512_160k_ade20k_20200615_231733.log.json) | +| ANN | R-101-D8 | 512x512 | 160000 | - | - | V100 | 42.94 | 44.06 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_160k_ade20k/ann_r101-d8_512x512_160k_ade20k_20200615_231733-955eb1ec.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_160k_ade20k/ann_r101-d8_512x512_160k_ade20k_20200615_231733.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ANN | R-50-D8 | 512x512 | 20000 | 6 | 20.92 | V100 | 74.86 | 76.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_20k_voc12aug/ann_r50-d8_512x512_20k_voc12aug_20200617_222246-dfcb1c62.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_20k_voc12aug/ann_r50-d8_512x512_20k_voc12aug_20200617_222246.log.json) | +| ANN | R-101-D8 | 512x512 | 20000 | 9.5 | 13.94 | V100 | 77.47 | 78.70 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_20k_voc12aug/ann_r101-d8_512x512_20k_voc12aug_20200617_222246-2fad0042.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_20k_voc12aug/ann_r101-d8_512x512_20k_voc12aug_20200617_222246.log.json) | +| ANN | R-50-D8 | 512x512 | 40000 | - | - | V100 | 76.56 | 77.51 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_40k_voc12aug/ann_r50-d8_512x512_40k_voc12aug_20200613_231314-b5dac322.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_40k_voc12aug/ann_r50-d8_512x512_40k_voc12aug_20200613_231314.log.json) | +| ANN | R-101-D8 | 512x512 | 40000 | - | - | V100 | 76.70 | 78.06 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann/ann_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_40k_voc12aug/ann_r101-d8_512x512_40k_voc12aug_20200613_231314-bd205bbe.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_40k_voc12aug/ann_r101-d8_512x512_40k_voc12aug_20200613_231314.log.json) | + +## Citation + +```bibtex +@inproceedings{zhu2019asymmetric, + title={Asymmetric non-local neural networks for semantic segmentation}, + author={Zhu, Zhen and Xu, Mengde and Bai, Song and Huang, Tengteng and Bai, Xiang}, + booktitle={Proceedings of the IEEE/CVF International Conference on Computer Vision}, + pages={593--602}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..0da7e0b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './ann_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..08459c0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './ann_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..46781fa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './ann_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..c951d87 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './ann_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..9f14327 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './ann_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..c3c1a3f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './ann_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..c3c1a3f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './ann_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..3cc5b8e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './ann_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..119eb76 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..3152b92 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..793437f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..4e392ca --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..900381d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..6921218 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', '../_base_/datasets/pascal_voc12_aug.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..e1c2360 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', '../_base_/datasets/pascal_voc12_aug.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..9fb26ef --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/ann_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/ann/metafile.yaml b/Seg_All_In_One_MMSeg/configs/ann/metafile.yaml new file mode 100644 index 0000000..0d11868 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/metafile.yaml @@ -0,0 +1,391 @@ +Collections: +- Name: ANN + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + README: configs/ann/README.md + Frameworks: + - PyTorch +Models: +- Name: ann_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.4 + mIoU(ms+flip): 78.57 + Config: configs/ann/ann_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ANN + Training Resources: 4x V100 GPUS + Memory (GB): 6.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_40k_cityscapes/ann_r50-d8_512x1024_40k_cityscapes_20200605_095211-049fc292.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_40k_cityscapes/ann_r50-d8_512x1024_40k_cityscapes_20200605_095211.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.55 + mIoU(ms+flip): 78.85 + Config: configs/ann/ann_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ANN + Training Resources: 4x V100 GPUS + Memory (GB): 9.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x1024_40k_cityscapes/ann_r101-d8_512x1024_40k_cityscapes_20200605_095243-adf6eece.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x1024_40k_cityscapes/ann_r101-d8_512x1024_40k_cityscapes_20200605_095243.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.89 + mIoU(ms+flip): 80.46 + Config: configs/ann/ann_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ANN + Training Resources: 4x V100 GPUS + Memory (GB): 6.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_769x769_40k_cityscapes/ann_r50-d8_769x769_40k_cityscapes_20200530_025712-2b46b04d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_769x769_40k_cityscapes/ann_r50-d8_769x769_40k_cityscapes_20200530_025712.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.32 + mIoU(ms+flip): 80.94 + Config: configs/ann/ann_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ANN + Training Resources: 4x V100 GPUS + Memory (GB): 10.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_769x769_40k_cityscapes/ann_r101-d8_769x769_40k_cityscapes_20200530_025720-059bff28.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_769x769_40k_cityscapes/ann_r101-d8_769x769_40k_cityscapes_20200530_025720.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.34 + mIoU(ms+flip): 78.65 + Config: configs/ann/ann_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ANN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_80k_cityscapes/ann_r50-d8_512x1024_80k_cityscapes_20200607_101911-5a9ad545.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_80k_cityscapes/ann_r50-d8_512x1024_80k_cityscapes_20200607_101911.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.14 + mIoU(ms+flip): 78.81 + Config: configs/ann/ann_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ANN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x1024_80k_cityscapes/ann_r101-d8_512x1024_80k_cityscapes_20200607_013728-aceccc6e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x1024_80k_cityscapes/ann_r101-d8_512x1024_80k_cityscapes_20200607_013728.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.88 + mIoU(ms+flip): 80.57 + Config: configs/ann/ann_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ANN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_769x769_80k_cityscapes/ann_r50-d8_769x769_80k_cityscapes_20200607_044426-cc7ff323.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_769x769_80k_cityscapes/ann_r50-d8_769x769_80k_cityscapes_20200607_044426.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.8 + mIoU(ms+flip): 80.34 + Config: configs/ann/ann_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ANN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_769x769_80k_cityscapes/ann_r101-d8_769x769_80k_cityscapes_20200607_013713-a9d4be8d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_769x769_80k_cityscapes/ann_r101-d8_769x769_80k_cityscapes_20200607_013713.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.01 + mIoU(ms+flip): 42.3 + Config: configs/ann/ann_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - ANN + Training Resources: 4x V100 GPUS + Memory (GB): 9.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_80k_ade20k/ann_r50-d8_512x512_80k_ade20k_20200615_014818-26f75e11.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_80k_ade20k/ann_r50-d8_512x512_80k_ade20k_20200615_014818.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.94 + mIoU(ms+flip): 44.18 + Config: configs/ann/ann_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - ANN + Training Resources: 4x V100 GPUS + Memory (GB): 12.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_80k_ade20k/ann_r101-d8_512x512_80k_ade20k_20200615_014818-c0153543.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_80k_ade20k/ann_r101-d8_512x512_80k_ade20k_20200615_014818.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.74 + mIoU(ms+flip): 42.62 + Config: configs/ann/ann_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - ANN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_160k_ade20k/ann_r50-d8_512x512_160k_ade20k_20200615_231733-892247bc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_160k_ade20k/ann_r50-d8_512x512_160k_ade20k_20200615_231733.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.94 + mIoU(ms+flip): 44.06 + Config: configs/ann/ann_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - ANN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_160k_ade20k/ann_r101-d8_512x512_160k_ade20k_20200615_231733-955eb1ec.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_160k_ade20k/ann_r101-d8_512x512_160k_ade20k_20200615_231733.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 74.86 + mIoU(ms+flip): 76.13 + Config: configs/ann/ann_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - ANN + Training Resources: 4x V100 GPUS + Memory (GB): 6.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_20k_voc12aug/ann_r50-d8_512x512_20k_voc12aug_20200617_222246-dfcb1c62.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_20k_voc12aug/ann_r50-d8_512x512_20k_voc12aug_20200617_222246.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.47 + mIoU(ms+flip): 78.7 + Config: configs/ann/ann_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - ANN + Training Resources: 4x V100 GPUS + Memory (GB): 9.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_20k_voc12aug/ann_r101-d8_512x512_20k_voc12aug_20200617_222246-2fad0042.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_20k_voc12aug/ann_r101-d8_512x512_20k_voc12aug_20200617_222246.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.56 + mIoU(ms+flip): 77.51 + Config: configs/ann/ann_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - ANN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_40k_voc12aug/ann_r50-d8_512x512_40k_voc12aug_20200613_231314-b5dac322.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x512_40k_voc12aug/ann_r50-d8_512x512_40k_voc12aug_20200613_231314.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch +- Name: ann_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: ANN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.7 + mIoU(ms+flip): 78.06 + Config: configs/ann/ann_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - ANN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_40k_voc12aug/ann_r101-d8_512x512_40k_voc12aug_20200613_231314-bd205bbe.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r101-d8_512x512_40k_voc12aug/ann_r101-d8_512x512_40k_voc12aug_20200613_231314.log.json + Paper: + Title: Asymmetric Non-local Neural Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1908.07678 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ann_head.py#L185 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/ann/my_ann.py b/Seg_All_In_One_MMSeg/configs/ann/my_ann.py new file mode 100644 index 0000000..2a3ee82 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/my_ann.py @@ -0,0 +1,41 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_4k_check_400.py', +] + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + diff --git a/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_10000_my_dataset_model-512x1024-testslide.py b/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_10000_my_dataset_model-512x1024-testslide.py new file mode 100644 index 0000000..85978cf --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_10000_my_dataset_model-512x1024-testslide.py @@ -0,0 +1,101 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k_check_10000.py', +] + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=True, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 1024), + stride=(341, 682), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_10000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_10000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..3004134 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_10000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k_check_10000.py', +] + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_16000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_16000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..3608181 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/my_ann_r101_g1-160k_check_16000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k_check_16000.py', +] + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ann/my_ann_r50-d8_b4-4k_my_dataset_model-512x512.py b/Seg_All_In_One_MMSeg/configs/ann/my_ann_r50-d8_b4-4k_my_dataset_model-512x512.py new file mode 100644 index 0000000..d48d4f2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/my_ann_r50-d8_b4-4k_my_dataset_model-512x512.py @@ -0,0 +1,25 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', #换成自己定义的数据集 + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_4k_check_400.py' +] +# 算法名称解析:ann【Alg】 _ r50【pretrained模型深度】 - d8【_base_/models】 _ 4【GPU数量】 x b2【Batch Size大小】 - 40k【schedule】 _ cityscapes【数据集】 - 512x1024【crop_size】.py +crop_size = (512, 512) # 裁剪大小 +data_preprocessor = dict(size=crop_size) +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', #'open-mmlab://resnet50_v1c',# './My_Local_Model/open_mmlab/resnest50.pth', + backbone=dict(depth=50), + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=36, # TODO 设置不同分类种类 + loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0), # TODO 设置不同分类种类,它根据预测结果和真实标签的重叠区域来度量相似性 + align_corners=False, # 在test_cfg不用slide时【默认】 + ), + auxiliary_head=dict( + num_classes=36, # TODO 设置不同分类种类 + loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=0.4), # TODO 设置不同分类种类,它根据预测结果和真实标签的重叠区域来度量相似性 + align_corners=False, # 在test_cfg不用slide时【默认】 + ), + +) diff --git a/Seg_All_In_One_MMSeg/configs/ann/my_apcnet_r101_g1-160k_check_16000_my_dataset_model-769x769-no_testslide.py b/Seg_All_In_One_MMSeg/configs/ann/my_apcnet_r101_g1-160k_check_16000_my_dataset_model-769x769-no_testslide.py new file mode 100644 index 0000000..89c22b4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/my_apcnet_r101_g1-160k_check_16000_my_dataset_model-769x769-no_testslide.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k_check_16000.py', +] + +crop_size = (769, 769) + +data_preprocessor = dict( + size=(769, 769), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(769, 769), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(769, 769), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ann/my_apcnet_r50_g1-160k_check_16000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/ann/my_apcnet_r50_g1-160k_check_16000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..5615f70 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ann/my_apcnet_r50_g1-160k_check_16000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/ann_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k_check_16000.py', +] + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/README.md b/Seg_All_In_One_MMSeg/configs/apcnet/README.md new file mode 100644 index 0000000..9104f3c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/README.md @@ -0,0 +1,59 @@ +# APCNet + +> [Adaptive Pyramid Context Network for Semantic Segmentation](https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Recent studies witnessed that context features can significantly improve the performance of deep semantic segmentation networks. Current context based segmentation methods differ with each other in how to construct context features and perform differently in practice. This paper firstly introduces three desirable properties of context features in segmentation task. Specially, we find that Global-guided Local Affinity (GLA) can play a vital role in constructing effective context features, while this property has been largely ignored in previous works. Based on this analysis, this paper proposes Adaptive Pyramid Context Network (APCNet)for semantic segmentation. APCNet adaptively constructs multi-scale contextual representations with multiple welldesigned Adaptive Context Modules (ACMs). Specifically, each ACM leverages a global image representation as a guidance to estimate the local affinity coefficients for each sub-region, and then calculates a context vector with these affinities. We empirically evaluate our APCNet on three semantic segmentation and scene parsing datasets, including PASCAL VOC 2012, Pascal-Context, and ADE20K dataset. Experimental results show that APCNet achieves state-ofthe-art performance on all three benchmarks, and obtains a new record 84.2% on PASCAL VOC 2012 test set without MS COCO pre-trained and any post-processing. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| APCNet | R-50-D8 | 512x1024 | 40000 | 7.7 | 3.57 | V100 | 78.02 | 79.26 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x1024_40k_cityscapes/apcnet_r50-d8_512x1024_40k_cityscapes_20201214_115717-5e88fa33.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x1024_40k_cityscapes/apcnet_r50-d8_512x1024_40k_cityscapes-20201214_115717.log.json) | +| APCNet | R-101-D8 | 512x1024 | 40000 | 11.2 | 2.15 | V100 | 79.08 | 80.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x1024_40k_cityscapes/apcnet_r101-d8_512x1024_40k_cityscapes_20201214_115716-abc9d111.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x1024_40k_cityscapes/apcnet_r101-d8_512x1024_40k_cityscapes-20201214_115716.log.json) | +| APCNet | R-50-D8 | 769x769 | 40000 | 8.7 | 1.52 | V100 | 77.89 | 79.75 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_769x769_40k_cityscapes/apcnet_r50-d8_769x769_40k_cityscapes_20201214_115717-2a2628d7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_769x769_40k_cityscapes/apcnet_r50-d8_769x769_40k_cityscapes-20201214_115717.log.json) | +| APCNet | R-101-D8 | 769x769 | 40000 | 12.7 | 1.03 | V100 | 77.96 | 79.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_769x769_40k_cityscapes/apcnet_r101-d8_769x769_40k_cityscapes_20201214_115718-b650de90.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_769x769_40k_cityscapes/apcnet_r101-d8_769x769_40k_cityscapes-20201214_115718.log.json) | +| APCNet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 78.96 | 79.94 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x1024_80k_cityscapes/apcnet_r50-d8_512x1024_80k_cityscapes_20201214_115716-987f51e3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x1024_80k_cityscapes/apcnet_r50-d8_512x1024_80k_cityscapes-20201214_115716.log.json) | +| APCNet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 79.64 | 80.61 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x1024_80k_cityscapes/apcnet_r101-d8_512x1024_80k_cityscapes_20201214_115705-b1ff208a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x1024_80k_cityscapes/apcnet_r101-d8_512x1024_80k_cityscapes-20201214_115705.log.json) | +| APCNet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 78.79 | 80.35 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_769x769_80k_cityscapes/apcnet_r50-d8_769x769_80k_cityscapes_20201214_115718-7ea9fa12.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_769x769_80k_cityscapes/apcnet_r50-d8_769x769_80k_cityscapes-20201214_115718.log.json) | +| APCNet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 78.45 | 79.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_769x769_80k_cityscapes/apcnet_r101-d8_769x769_80k_cityscapes_20201214_115716-a7fbc2ab.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_769x769_80k_cityscapes/apcnet_r101-d8_769x769_80k_cityscapes-20201214_115716.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| APCNet | R-50-D8 | 512x512 | 80000 | 10.1 | 19.61 | V100 | 42.20 | 43.30 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x512_80k_ade20k/apcnet_r50-d8_512x512_80k_ade20k_20201214_115705-a8626293.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x512_80k_ade20k/apcnet_r50-d8_512x512_80k_ade20k-20201214_115705.log.json) | +| APCNet | R-101-D8 | 512x512 | 80000 | 13.6 | 13.10 | V100 | 45.54 | 46.65 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x512_80k_ade20k/apcnet_r101-d8_512x512_80k_ade20k_20201214_115704-c656c3fb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x512_80k_ade20k/apcnet_r101-d8_512x512_80k_ade20k-20201214_115704.log.json) | +| APCNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 43.40 | 43.94 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x512_160k_ade20k/apcnet_r50-d8_512x512_160k_ade20k_20201214_115706-25fb92c2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x512_160k_ade20k/apcnet_r50-d8_512x512_160k_ade20k-20201214_115706.log.json) | +| APCNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 45.41 | 46.63 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet/apcnet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x512_160k_ade20k/apcnet_r101-d8_512x512_160k_ade20k_20201214_115705-73f9a8d7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x512_160k_ade20k/apcnet_r101-d8_512x512_160k_ade20k-20201214_115705.log.json) | + +## Citation + +```bibtex +@InProceedings{He_2019_CVPR, +author = {He, Junjun and Deng, Zhongying and Zhou, Lei and Wang, Yali and Qiao, Yu}, +title = {Adaptive Pyramid Context Network for Semantic Segmentation}, +booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, +month = {June}, +year = {2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..754b2d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..d2b5fe1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './apcnet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..03b018d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './apcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..0cbbfad --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './apcnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..f0aacc0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './apcnet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..219d07a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './apcnet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..b440771 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..9ff897c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..6de1033 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..d6ec898 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..37b23d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..b0fbe27 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/apcnet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/apcnet/metafile.yaml new file mode 100644 index 0000000..3f4072c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/metafile.yaml @@ -0,0 +1,296 @@ +Collections: +- Name: APCNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + README: configs/apcnet/README.md + Frameworks: + - PyTorch +Models: +- Name: apcnet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.02 + mIoU(ms+flip): 79.26 + Config: configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - APCNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x1024_40k_cityscapes/apcnet_r50-d8_512x1024_40k_cityscapes_20201214_115717-5e88fa33.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x1024_40k_cityscapes/apcnet_r50-d8_512x1024_40k_cityscapes-20201214_115717.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.08 + mIoU(ms+flip): 80.34 + Config: configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - APCNet + Training Resources: 4x V100 GPUS + Memory (GB): 11.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x1024_40k_cityscapes/apcnet_r101-d8_512x1024_40k_cityscapes_20201214_115716-abc9d111.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x1024_40k_cityscapes/apcnet_r101-d8_512x1024_40k_cityscapes-20201214_115716.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.89 + mIoU(ms+flip): 79.75 + Config: configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - APCNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_769x769_40k_cityscapes/apcnet_r50-d8_769x769_40k_cityscapes_20201214_115717-2a2628d7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_769x769_40k_cityscapes/apcnet_r50-d8_769x769_40k_cityscapes-20201214_115717.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.96 + mIoU(ms+flip): 79.24 + Config: configs/apcnet/apcnet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - APCNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_769x769_40k_cityscapes/apcnet_r101-d8_769x769_40k_cityscapes_20201214_115718-b650de90.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_769x769_40k_cityscapes/apcnet_r101-d8_769x769_40k_cityscapes-20201214_115718.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.96 + mIoU(ms+flip): 79.94 + Config: configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - APCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x1024_80k_cityscapes/apcnet_r50-d8_512x1024_80k_cityscapes_20201214_115716-987f51e3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x1024_80k_cityscapes/apcnet_r50-d8_512x1024_80k_cityscapes-20201214_115716.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.64 + mIoU(ms+flip): 80.61 + Config: configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - APCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x1024_80k_cityscapes/apcnet_r101-d8_512x1024_80k_cityscapes_20201214_115705-b1ff208a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x1024_80k_cityscapes/apcnet_r101-d8_512x1024_80k_cityscapes-20201214_115705.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.79 + mIoU(ms+flip): 80.35 + Config: configs/apcnet/apcnet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - APCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_769x769_80k_cityscapes/apcnet_r50-d8_769x769_80k_cityscapes_20201214_115718-7ea9fa12.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_769x769_80k_cityscapes/apcnet_r50-d8_769x769_80k_cityscapes-20201214_115718.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.45 + mIoU(ms+flip): 79.91 + Config: configs/apcnet/apcnet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - APCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_769x769_80k_cityscapes/apcnet_r101-d8_769x769_80k_cityscapes_20201214_115716-a7fbc2ab.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_769x769_80k_cityscapes/apcnet_r101-d8_769x769_80k_cityscapes-20201214_115716.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.2 + mIoU(ms+flip): 43.3 + Config: configs/apcnet/apcnet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - APCNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x512_80k_ade20k/apcnet_r50-d8_512x512_80k_ade20k_20201214_115705-a8626293.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x512_80k_ade20k/apcnet_r50-d8_512x512_80k_ade20k-20201214_115705.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.54 + mIoU(ms+flip): 46.65 + Config: configs/apcnet/apcnet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - APCNet + Training Resources: 4x V100 GPUS + Memory (GB): 13.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x512_80k_ade20k/apcnet_r101-d8_512x512_80k_ade20k_20201214_115704-c656c3fb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x512_80k_ade20k/apcnet_r101-d8_512x512_80k_ade20k-20201214_115704.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.4 + mIoU(ms+flip): 43.94 + Config: configs/apcnet/apcnet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - APCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x512_160k_ade20k/apcnet_r50-d8_512x512_160k_ade20k_20201214_115706-25fb92c2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r50-d8_512x512_160k_ade20k/apcnet_r50-d8_512x512_160k_ade20k-20201214_115706.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: apcnet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: APCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.41 + mIoU(ms+flip): 46.63 + Config: configs/apcnet/apcnet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - APCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x512_160k_ade20k/apcnet_r101-d8_512x512_160k_ade20k_20201214_115705-73f9a8d7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/apcnet/apcnet_r101-d8_512x512_160k_ade20k/apcnet_r101-d8_512x512_160k_ade20k-20201214_115705.log.json + Paper: + Title: Adaptive Pyramid Context Network for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_CVPR_2019/html/He_Adaptive_Pyramid_Context_Network_for_Semantic_Segmentation_CVPR_2019_paper.html + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..ce6ff90 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=40000, + eta_min=0.0, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r101_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r101_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..ce6ff90 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r101_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=40000, + eta_min=0.0, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..8af254b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/apcnet/my_apcnet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,103 @@ +_base_ = [ + '../_base_/models/apcnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/beit/README.md b/Seg_All_In_One_MMSeg/configs/beit/README.md new file mode 100644 index 0000000..b005c88 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/README.md @@ -0,0 +1,85 @@ +# BEiT + +> [BEiT: BERT Pre-Training of Image Transformers](https://arxiv.org/abs/2106.08254) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +We introduce a self-supervised vision representation model BEiT, which stands for Bidirectional Encoder representation from Image Transformers. Following BERT developed in the natural language processing area, we propose a masked image modeling task to pretrain vision Transformers. Specifically, each image has two views in our pre-training, i.e, image patches (such as 16x16 pixels), and visual tokens (i.e., discrete tokens). We first "tokenize" the original image into visual tokens. Then we randomly mask some image patches and fed them into the backbone Transformer. The pre-training objective is to recover the original visual tokens based on the corrupted image patches. After pre-training BEiT, we directly fine-tune the model parameters on downstream tasks by appending task layers upon the pretrained encoder. Experimental results on image classification and semantic segmentation show that our model achieves competitive results with previous pre-training methods. For example, base-size BEiT achieves 83.2% top-1 accuracy on ImageNet-1K, significantly outperforming from-scratch DeiT training (81.8%) with the same setup. Moreover, large-size BEiT obtains 86.3% only using ImageNet-1K, even outperforming ViT-L with supervised pre-training on ImageNet-22K (85.2%). The code and pretrained models are available at [this https URL](https://github.com/microsoft/unilm/tree/master/beit). + + + +
+ +
+ +## Usage + +To use other repositories' pre-trained models, it is necessary to convert keys. + +We provide a script [`beit2mmseg.py`](../../tools/model_converters/beit2mmseg.py) in the tools directory to convert the key of models from [the official repo](https://github.com/microsoft/unilm/tree/master/beit/semantic_segmentation) to MMSegmentation style. + +```shell +python tools/model_converters/beit2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +E.g. + +```shell +python tools/model_converters/beit2mmseg.py https://conversationhub.blob.core.windows.net/beit-share-public/beit/beit_base_patch16_224_pt22k_ft22k.pth pretrain/beit_base_patch16_224_pt22k_ft22k.pth +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +In our default setting, pretrained models could be defined below: + +| pretrained models | original models | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------- | +| BEiT_base.pth | ['BEiT_base'](https://conversationhub.blob.core.windows.net/beit-share-public/beit/beit_base_patch16_224_pt22k_ft22k.pth) | +| BEiT_large.pth | ['BEiT_large'](https://conversationhub.blob.core.windows.net/beit-share-public/beit/beit_large_patch16_224_pt22k_ft22k.pth) | + +Verify the single-scale results of the model: + +```shell +sh tools/dist_test.sh \ +configs/beit/upernet_beit-large_fp16_8x1_640x640_160k_ade20k.py \ +upernet_beit-large_fp16_8x1_640x640_160k_ade20k-8fc0dd5d.pth $GPUS --eval mIoU +``` + +Since relative position embedding requires the input length and width to be equal, the sliding window is adopted for multi-scale inference. So we set min_size=640, that is, the shortest edge is 640. So the multi-scale inference of config is performed separately, instead of '--aug-test'. For multi-scale inference: + +```shell +sh tools/dist_test.sh \ +configs/beit/upernet_beit-large_fp16_640x640_160k_ade20k_ms.py \ +upernet_beit-large_fp16_8x1_640x640_160k_ade20k-8fc0dd5d.pth $GPUS --eval mIoU +``` + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | pretrain | pretrain img size | Batch Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | -------- | --------- | ------------ | ----------------- | ---------- | ------- | -------- | -------------- | ------ | ----- | ------------: | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UPerNet | BEiT-B | 640x640 | ImageNet-22K | 224x224 | 16 | 160000 | 15.88 | 2.00 | V100 | 53.08 | 53.84 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/beit/upernet_beit-base_8x2_640x640_160k_ade20k/upernet_beit-base_8x2_640x640_160k_ade20k-eead221d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/beit/upernet_beit-base_8x2_640x640_160k_ade20k/upernet_beit-base_8x2_640x640_160k_ade20k.log.json) | +| UPerNet | BEiT-L | 640x640 | ImageNet-22K | 224x224 | 8 | 320000 | 22.64 | 0.96 | V100 | 56.33 | 56.84 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/beit/upernet_beit-large_fp16_8x1_640x640_160k_ade20k/upernet_beit-large_fp16_8x1_640x640_160k_ade20k-8fc0dd5d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/beit/upernet_beit-large_fp16_8x1_640x640_160k_ade20k/upernet_beit-large_fp16_8x1_640x640_160k_ade20k.log.json) | + +## Citation + +```bibtex +@inproceedings{beit, + title={{BEiT}: {BERT} Pre-Training of Image Transformers}, + author={Hangbo Bao and Li Dong and Songhao Piao and Furu Wei}, + booktitle={International Conference on Learning Representations}, + year={2022}, + url={https://openreview.net/forum?id=p-BhZSz59o4} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640.py new file mode 100644 index 0000000..1cd7d0e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640.py @@ -0,0 +1,36 @@ +_base_ = [ + '../_base_/models/upernet_beit.py', '../_base_/datasets/ade20k_640x640.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (640, 640) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='pretrain/beit_base_patch16_224_pt22k_ft22k.pth', + test_cfg=dict(mode='slide', crop_size=(640, 640), stride=(426, 426))) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=3e-5, betas=(0.9, 0.999), weight_decay=0.05), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict(num_layers=12, layer_decay_rate=0.9)) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640_ms.py b/Seg_All_In_One_MMSeg/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640_ms.py new file mode 100644 index 0000000..0248022 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640_ms.py @@ -0,0 +1,16 @@ +_base_ = './beit-base_upernet_8xb2-160k_ade20k-640x640.py' + +test_pipeline = [ + dict(type='LoadImageFromFile'), + # TODO: Refactor 'MultiScaleFlipAug' which supports + # `min_size` feature in `Resize` class + # img_ratios is [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] + # original image scale is (2560, 640) + dict(type='Resize', scale=(2560, 640), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs'), +] +val_dataloader = dict(batch_size=1, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640.py new file mode 100644 index 0000000..4fd5cd2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640.py @@ -0,0 +1,50 @@ +_base_ = [ + '../_base_/models/upernet_beit.py', '../_base_/datasets/ade20k_640x640.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_320k.py' +] +crop_size = (640, 640) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='pretrain/beit_large_patch16_224_pt22k_ft22k.pth', + backbone=dict( + type='BEiT', + embed_dims=1024, + num_layers=24, + num_heads=16, + mlp_ratio=4, + qv_bias=True, + init_values=1e-6, + drop_path_rate=0.2, + out_indices=[7, 11, 15, 23]), + neck=dict(embed_dim=1024, rescales=[4, 2, 1, 0.5]), + decode_head=dict( + in_channels=[1024, 1024, 1024, 1024], num_classes=150, channels=1024), + auxiliary_head=dict(in_channels=1024, num_classes=150), + test_cfg=dict(mode='slide', crop_size=(640, 640), stride=(426, 426))) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=2e-5, betas=(0.9, 0.999), weight_decay=0.05), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict(num_layers=24, layer_decay_rate=0.95), + accumulative_counts=2) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=3000), + dict( + type='PolyLR', + power=1.0, + begin=3000, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +train_dataloader = dict(batch_size=1) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640_ms.py b/Seg_All_In_One_MMSeg/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640_ms.py new file mode 100644 index 0000000..fc6f049 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640_ms.py @@ -0,0 +1,16 @@ +_base_ = './beit-large_upernet_8xb1-amp-160k_ade20k-640x640.py' + +test_pipeline = [ + dict(type='LoadImageFromFile'), + # TODO: Refactor 'MultiScaleFlipAug' which supports + # `min_size` feature in `Resize` class + # img_ratios is [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] + # original image scale is (2560, 640) + dict(type='Resize', scale=(2560, 640), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs'), +] +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/beit/metafile.yaml b/Seg_All_In_One_MMSeg/configs/beit/metafile.yaml new file mode 100644 index 0000000..ef6124e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/metafile.yaml @@ -0,0 +1,49 @@ +Models: +- Name: beit-base_upernet_8xb2-160k_ade20k-640x640 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 53.08 + mIoU(ms+flip): 53.84 + Config: configs/beit/beit-base_upernet_8xb2-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - BEiT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 15.88 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/beit/upernet_beit-base_8x2_640x640_160k_ade20k/upernet_beit-base_8x2_640x640_160k_ade20k-eead221d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/beit/upernet_beit-base_8x2_640x640_160k_ade20k/upernet_beit-base_8x2_640x640_160k_ade20k.log.json + Paper: + Title: 'BEiT: BERT Pre-Training of Image Transformers' + URL: https://arxiv.org/abs/2106.08254 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/backbones/beit.py#1404 + Framework: PyTorch +- Name: beit-large_upernet_8xb1-amp-160k_ade20k-640x640 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 56.33 + mIoU(ms+flip): 56.84 + Config: configs/beit/beit-large_upernet_8xb1-amp-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 8 + Architecture: + - BEiT-L + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 22.64 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/beit/upernet_beit-large_fp16_8x1_640x640_160k_ade20k/upernet_beit-large_fp16_8x1_640x640_160k_ade20k-8fc0dd5d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/beit/upernet_beit-large_fp16_8x1_640x640_160k_ade20k/upernet_beit-large_fp16_8x1_640x640_160k_ade20k.log.json + Paper: + Title: 'BEiT: BERT Pre-Training of Image Transformers' + URL: https://arxiv.org/abs/2106.08254 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/backbones/beit.py#1404 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-base_b2_g1-40k_check_4000_my_dataset_model-640x640.py b/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-base_b2_g1-40k_check_4000_my_dataset_model-640x640.py new file mode 100644 index 0000000..4e9697e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-base_b2_g1-40k_check_4000_my_dataset_model-640x640.py @@ -0,0 +1,134 @@ +_base_ = [ + '../_base_/models/upernet_beit.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (640, 640) + +data_preprocessor = dict( + size=(640, 640), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + type='BEiT', + img_size=(640, 640), + embed_dims=768, + num_layers=12, + num_heads=12, + out_indices=(3, 5, 7, 11), + drop_path_rate=0.1, + init_values=0.1, + ), + data_preprocessor=dict( + size=(640, 640), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/beit_base_patch16_224_pt22k_ft22k.pth', + neck=dict( + type='Feature2Pyramid', + embed_dim=768, + rescales=[ + 4, + 2, + 1, + 0.5, + ], + ), + decode_head=dict( + in_channels=[ + 768, + 768, + 768, + 768, + ], + num_classes=36, + channels=768, + norm_cfg=dict( + type='BN', + ), + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + norm_cfg=dict( + type='BN', + ), + in_channels=768, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', + lr=3e-05, + betas=(0.9, 0.999), + weight_decay=0.05, + ), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict( + num_layers=12, + layer_decay_rate=0.9, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=2, +) + diff --git a/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-base_g1-40k_check_4000_my_dataset_model-640x640.py b/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-base_g1-40k_check_4000_my_dataset_model-640x640.py new file mode 100644 index 0000000..9d4972d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-base_g1-40k_check_4000_my_dataset_model-640x640.py @@ -0,0 +1,132 @@ +_base_ = [ + '../_base_/models/upernet_beit.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (640, 640) + +data_preprocessor = dict( + size=(640, 640), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + type='BEiT', + img_size=(640, 640), + embed_dims=768, + num_layers=12, + num_heads=12, + out_indices=(3, 5, 7, 11), + drop_path_rate=0.1, + init_values=0.1, + ), + data_preprocessor=dict( + size=(640, 640), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/beit_base_patch16_224_pt22k_ft22k.pth', + neck=dict( + type='Feature2Pyramid', + embed_dim=1024, + rescales=[ + 4, + 2, + 1, + 0.5, + ], + ), + decode_head=dict( + in_channels=[ + 768, + 768, + 768, + 768, + ], + num_classes=36, + channels=768, + norm_cfg=dict( + type='BN', + ), + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + norm_cfg=dict( + type='BN', + ), + in_channels=768, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', + lr=3e-05, + betas=(0.9, 0.999), + weight_decay=0.05, + ), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict( + num_layers=12, + layer_decay_rate=0.9, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = ({'batch_size': 2}, 2) + diff --git a/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-large_b2_g1-40k_check_4000_my_dataset_model-512x512.py b/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-large_b2_g1-40k_check_4000_my_dataset_model-512x512.py new file mode 100644 index 0000000..0ac8aad --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-large_b2_g1-40k_check_4000_my_dataset_model-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/upernet_beit.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + type='BEiT', + img_size=(512, 512), + embed_dims=1024, + num_layers=24, + num_heads=16, + init_values=1e-06, + drop_path_rate=0.2, + out_indices=[ + 7, + 11, + 15, + 23, + ], + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/beit_large_patch16_224_pt22k_ft22k.pth', + neck=dict( + type='Feature2Pyramid', + embed_dim=1024, + rescales=[ + 4, + 2, + 1, + 0.5, + ], + ), + decode_head=dict( + in_channels=[ + 1024, + 1024, + 1024, + 1024, + ], + num_classes=36, + channels=1024, + norm_cfg=dict( + type='BN', + ), + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + norm_cfg=dict( + type='BN', + ), + in_channels=1024, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', + lr=2e-05, + betas=(0.9, 0.999), + weight_decay=0.05, + ), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict( + num_layers=24, + layer_decay_rate=0.95, + ), + accumulative_counts=2, +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=2, +) + diff --git a/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-large_b2_g1-40k_check_4000_my_dataset_model-640x640.py b/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-large_b2_g1-40k_check_4000_my_dataset_model-640x640.py new file mode 100644 index 0000000..cab0d1c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/my_beit_upernet-large_b2_g1-40k_check_4000_my_dataset_model-640x640.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/upernet_beit.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (640, 640) + +data_preprocessor = dict( + size=(640, 640), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + type='BEiT', + img_size=(640, 640), + embed_dims=1024, + num_layers=24, + num_heads=16, + init_values=1e-06, + drop_path_rate=0.2, + out_indices=[ + 7, + 11, + 15, + 23, + ], + ), + data_preprocessor=dict( + size=(640, 640), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/beit_large_patch16_224_pt22k_ft22k.pth', + neck=dict( + type='Feature2Pyramid', + embed_dim=1024, + rescales=[ + 4, + 2, + 1, + 0.5, + ], + ), + decode_head=dict( + in_channels=[ + 1024, + 1024, + 1024, + 1024, + ], + num_classes=36, + channels=1024, + norm_cfg=dict( + type='BN', + ), + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + norm_cfg=dict( + type='BN', + ), + in_channels=1024, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', + lr=2e-05, + betas=(0.9, 0.999), + weight_decay=0.05, + ), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict( + num_layers=24, + layer_decay_rate=0.95, + ), + accumulative_counts=2, +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=2, +) + diff --git a/Seg_All_In_One_MMSeg/configs/beit/my_mae_upernet-base_b2_g1-40k_check_4000_my_dataset_model-512x512-testslide.py b/Seg_All_In_One_MMSeg/configs/beit/my_mae_upernet-base_b2_g1-40k_check_4000_my_dataset_model-512x512-testslide.py new file mode 100644 index 0000000..d9792a8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/beit/my_mae_upernet-base_b2_g1-40k_check_4000_my_dataset_model-512x512-testslide.py @@ -0,0 +1,115 @@ +_base_ = [ + '../_base_/models/upernet_mae.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + type='MAE', + img_size=(512, 512), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/mae_pretrain_vit_base_mmcls.pth', + decode_head=dict( + num_classes=36, + norm_cfg=dict( + type='BN', + ), + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + norm_cfg=dict( + type='BN', + ), + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + test_cfg=dict( + mode='slide', + crop_size=(512, 512), + stride=1.5, + ), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', + lr=3e-05, + betas=(0.9, 0.999), + weight_decay=0.05, + ), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict( + num_layers=12, + layer_decay_rate=0.9, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=2, +) + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/README.md b/Seg_All_In_One_MMSeg/configs/bisenetv1/README.md new file mode 100644 index 0000000..a505895 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/README.md @@ -0,0 +1,64 @@ +# BiSeNetV1 + +> [BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation](https://arxiv.org/abs/1808.00897) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Semantic segmentation requires both rich spatial information and sizeable receptive field. However, modern approaches usually compromise spatial resolution to achieve real-time inference speed, which leads to poor performance. In this paper, we address this dilemma with a novel Bilateral Segmentation Network (BiSeNet). We first design a Spatial Path with a small stride to preserve the spatial information and generate high-resolution features. Meanwhile, a Context Path with a fast downsampling strategy is employed to obtain sufficient receptive field. On top of the two paths, we introduce a new Feature Fusion Module to combine features efficiently. The proposed architecture makes a right balance between the speed and segmentation performance on Cityscapes, CamVid, and COCO-Stuff datasets. Specifically, for a 2048x1024 input, we achieve 68.4% Mean IOU on the Cityscapes test dataset with speed of 105 FPS on one NVIDIA Titan XP card, which is significantly faster than the existing methods with comparable performance. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | ---------------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| BiSeNetV1 | R-18-D32 (No Pretrain) | 1024x1024 | 160000 | 5.69 | 31.77 | V100 | 74.44 | 77.05 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_4x4_1024x1024_160k_cityscapes/bisenetv1_r18-d32_4x4_1024x1024_160k_cityscapes_20210922_172239-c55e78e2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_4x4_1024x1024_160k_cityscapes/bisenetv1_r18-d32_4x4_1024x1024_160k_cityscapes_20210922_172239.log.json) | +| BiSeNetV1 | R-18-D32 | 1024x1024 | 160000 | 5.69 | 31.77 | V100 | 74.37 | 76.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_4x4_1024x1024_160k_cityscapes/bisenetv1_r18-d32_in1k-pre_4x4_1024x1024_160k_cityscapes_20210905_220251-8ba80eff.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_4x4_1024x1024_160k_cityscapes/bisenetv1_r18-d32_in1k-pre_4x4_1024x1024_160k_cityscapes_20210905_220251.log.json) | +| BiSeNetV1 | R-18-D32 (4x8) | 1024x1024 | 160000 | 11.17 | 31.77 | V100 | 75.16 | 77.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb8-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_4x8_1024x1024_160k_cityscapes/bisenetv1_r18-d32_in1k-pre_4x8_1024x1024_160k_cityscapes_20210905_220322-bb8db75f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_4x8_1024x1024_160k_cityscapes/bisenetv1_r18-d32_in1k-pre_4x8_1024x1024_160k_cityscapes_20210905_220322.log.json) | +| BiSeNetV1 | R-50-D32 (No Pretrain) | 1024x1024 | 160000 | 15.39 | 7.71 | V100 | 76.92 | 78.87 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_4x4_1024x1024_160k_cityscapes/bisenetv1_r50-d32_4x4_1024x1024_160k_cityscapes_20210923_222639-7b28a2a6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_4x4_1024x1024_160k_cityscapes/bisenetv1_r50-d32_4x4_1024x1024_160k_cityscapes_20210923_222639.log.json) | +| BiSeNetV1 | R-50-D32 | 1024x1024 | 160000 | 15.39 | 7.71 | V100 | 77.68 | 79.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_in1k-pre_4x4_1024x1024_160k_cityscapes/bisenetv1_r50-d32_in1k-pre_4x4_1024x1024_160k_cityscapes_20210917_234628-8b304447.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_in1k-pre_4x4_1024x1024_160k_cityscapes/bisenetv1_r50-d32_in1k-pre_4x4_1024x1024_160k_cityscapes_20210917_234628.log.json) | + +### COCO-Stuff 164k + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | ----------------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| BiSeNetV1 | R-18-D32 (No Pretrain) | 512x512 | 160000 | - | - | V100 | 25.45 | 26.15 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r18-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211022_054328-046aa2f2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r18-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211022_054328.log.json) | +| BiSeNetV1 | R-18-D32 | 512x512 | 160000 | 6.33 | 74.24 | V100 | 28.55 | 29.26 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r18-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211023_013100-f700dbf7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r18-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211023_013100.log.json) | +| BiSeNetV1 | R-50-D32 (No Pretrain) | 512x512 | 160000 | - | - | V100 | 29.82 | 30.33 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r50-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_040616-d2bb0df4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r50-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_040616.log.json) | +| BiSeNetV1 | R-50-D32 | 512x512 | 160000 | 9.28 | 32.60 | V100 | 34.88 | 35.37 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r50-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_181932-66747911.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r50-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_181932.log.json) | +| BiSeNetV1 | R-101-D32 (No Pretrain) | 512x512 | 160000 | - | - | V100 | 31.14 | 31.76 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r101-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r101-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211102_164147-c6b32c3b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r101-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r101-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211102_164147.log.json) | +| BiSeNetV1 | R-101-D32 | 512x512 | 160000 | 10.36 | 25.25 | V100 | 37.38 | 37.99 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1/bisenetv1_r101-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r101-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r101-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_225220-28c8f092.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r101-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r101-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_225220.log.json) | + +Note: + +- `4x8`: Using 4 GPUs with 8 samples per GPU in training. +- For BiSeNetV1 on Cityscapes dataset, default setting is 4 GPUs with 4 samples per GPU in training. +- `No Pretrain` means the model is trained from scratch. + +## Citation + +```bibtex +@inproceedings{yu2018bisenet, + title={Bisenet: Bilateral segmentation network for real-time semantic segmentation}, + author={Yu, Changqian and Wang, Jingbo and Peng, Chao and Gao, Changxin and Yu, Gang and Sang, Nong}, + booktitle={Proceedings of the European conference on computer vision (ECCV)}, + pages={325--341}, + year={2018} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r101-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r101-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..ac63447 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r101-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,6 @@ +_base_ = './bisenetv1_r101-d32_4xb4-160k_coco-stuff164k-512x512.py' +model = dict( + backbone=dict( + backbone_cfg=dict( + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet101_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r101-d32_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r101-d32_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..02e4e9b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r101-d32_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,58 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + context_channels=(512, 1024, 2048), + spatial_channels=(256, 256, 256, 512), + out_channels=1024, + backbone_cfg=dict(type='ResNet', depth=101)), + decode_head=dict(in_channels=1024, channels=1024, num_classes=171), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=171, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=171, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ]) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', lr=0.005, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..da3e598 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py @@ -0,0 +1,29 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (1024, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + backbone_cfg=dict( + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet18_v1c')))) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', lr=0.025, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..9de889f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,10 @@ +_base_ = './bisenetv1_r18-d32_4xb4-160k_coco-stuff164k-512x512.py' +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + backbone_cfg=dict( + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet18_v1c'))), +) diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb8-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb8-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..0580ce1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb8-160k_cityscapes-1024x1024.py @@ -0,0 +1,4 @@ +_base_ = './bisenetv1_r18-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py' +train_dataloader = dict(batch_size=8, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..6c3e12b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_cityscapes-1024x1024.py @@ -0,0 +1,24 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (1024, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', lr=0.025, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..2109d68 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,53 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=171, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=171, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ]) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', lr=0.005, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..013c4ff --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py @@ -0,0 +1,7 @@ +_base_ = './bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py' +model = dict( + type='EncoderDecoder', + backbone=dict( + backbone_cfg=dict( + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet50_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..b35259c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,7 @@ +_base_ = './bisenetv1_r50-d32_4xb4-160k_coco-stuff164k-512x512.py' + +model = dict( + backbone=dict( + backbone_cfg=dict( + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet50_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..9753c10 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py @@ -0,0 +1,55 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +norm_cfg = dict(type='SyncBN', requires_grad=True) +crop_size = (1024, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='BiSeNetV1', + context_channels=(512, 1024, 2048), + spatial_channels=(256, 256, 256, 512), + out_channels=1024, + backbone_cfg=dict(type='ResNet', depth=50)), + decode_head=dict( + type='FCNHead', in_channels=1024, in_index=0, channels=1024), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False), + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=19, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False), + ]) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..8b6ef74 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,58 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + context_channels=(512, 1024, 2048), + spatial_channels=(256, 256, 256, 512), + out_channels=1024, + backbone_cfg=dict(type='ResNet', depth=50)), + decode_head=dict(in_channels=1024, channels=1024, num_classes=171), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=171, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=171, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ]) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', lr=0.005, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/metafile.yaml b/Seg_All_In_One_MMSeg/configs/bisenetv1/metafile.yaml new file mode 100644 index 0000000..e37f632 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/metafile.yaml @@ -0,0 +1,275 @@ +Collections: +- Name: BiSeNetV1 + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - COCO-Stuff 164k + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + README: configs/bisenetv1/README.md + Frameworks: + - PyTorch +Models: +- Name: bisenetv1_r18-d32_4xb4-160k_cityscapes-1024x1024 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.44 + mIoU(ms+flip): 77.05 + Config: configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - R-18-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Memory (GB): 5.69 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_4x4_1024x1024_160k_cityscapes/bisenetv1_r18-d32_4x4_1024x1024_160k_cityscapes_20210922_172239-c55e78e2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_4x4_1024x1024_160k_cityscapes/bisenetv1_r18-d32_4x4_1024x1024_160k_cityscapes_20210922_172239.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r18-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.37 + mIoU(ms+flip): 76.91 + Config: configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - R-18-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Memory (GB): 5.69 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_4x4_1024x1024_160k_cityscapes/bisenetv1_r18-d32_in1k-pre_4x4_1024x1024_160k_cityscapes_20210905_220251-8ba80eff.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_4x4_1024x1024_160k_cityscapes/bisenetv1_r18-d32_in1k-pre_4x4_1024x1024_160k_cityscapes_20210905_220251.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r18-d32-in1k-pre_4xb8-160k_cityscapes-1024x1024 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.16 + mIoU(ms+flip): 77.24 + Config: configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb8-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 32 + Architecture: + - R-18-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Memory (GB): 11.17 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_4x8_1024x1024_160k_cityscapes/bisenetv1_r18-d32_in1k-pre_4x8_1024x1024_160k_cityscapes_20210905_220322-bb8db75f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_4x8_1024x1024_160k_cityscapes/bisenetv1_r18-d32_in1k-pre_4x8_1024x1024_160k_cityscapes_20210905_220322.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.92 + mIoU(ms+flip): 78.87 + Config: configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - R-50-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Memory (GB): 15.39 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_4x4_1024x1024_160k_cityscapes/bisenetv1_r50-d32_4x4_1024x1024_160k_cityscapes_20210923_222639-7b28a2a6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_4x4_1024x1024_160k_cityscapes/bisenetv1_r50-d32_4x4_1024x1024_160k_cityscapes_20210923_222639.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r50-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.68 + mIoU(ms+flip): 79.57 + Config: configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - R-50-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Memory (GB): 15.39 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_in1k-pre_4x4_1024x1024_160k_cityscapes/bisenetv1_r50-d32_in1k-pre_4x4_1024x1024_160k_cityscapes_20210917_234628-8b304447.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_in1k-pre_4x4_1024x1024_160k_cityscapes/bisenetv1_r50-d32_in1k-pre_4x4_1024x1024_160k_cityscapes_20210917_234628.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r18-d32_4xb4-160k_coco-stuff164k-512x512 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 25.45 + mIoU(ms+flip): 26.15 + Config: configs/bisenetv1/bisenetv1_r18-d32_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-18-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r18-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211022_054328-046aa2f2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r18-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211022_054328.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r18-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 28.55 + mIoU(ms+flip): 29.26 + Config: configs/bisenetv1/bisenetv1_r18-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-18-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Memory (GB): 6.33 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r18-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211023_013100-f700dbf7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r18-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r18-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211023_013100.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r50-d32_4xb4-160k_coco-stuff164k-512x512 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 29.82 + mIoU(ms+flip): 30.33 + Config: configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-50-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r50-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_040616-d2bb0df4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r50-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_040616.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 34.88 + mIoU(ms+flip): 35.37 + Config: configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-50-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Memory (GB): 9.28 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r50-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_181932-66747911.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r50-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r50-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_181932.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 31.14 + mIoU(ms+flip): 31.76 + Config: configs/bisenetv1/bisenetv1_r50-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-101-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r101-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r101-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211102_164147-c6b32c3b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r101-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r101-d32_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211102_164147.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch +- Name: bisenetv1_r101-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512 + In Collection: BiSeNetV1 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 37.38 + mIoU(ms+flip): 37.99 + Config: configs/bisenetv1/bisenetv1_r101-d32-in1k-pre_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-101-D32 + - BiSeNetV1 + Training Resources: 4x V100 GPUS + Memory (GB): 10.36 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r101-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r101-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_225220-28c8f092.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv1/bisenetv1_r101-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k/bisenetv1_r101-d32_in1k-pre_lr5e-3_4x4_512x512_160k_coco-stuff164k_20211101_225220.log.json + Paper: + Title: 'BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation' + URL: https://arxiv.org/abs/1808.00897 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv1.py#L266 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..437efdd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(128, 256, 512), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(64, 64, 64, 128), + out_channels=256, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=256, + channels=256, + num_classes=10, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=10, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..ecfc816 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(128, 256, 512), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(64, 64, 64, 128), + out_channels=256, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=256, + channels=256, + num_classes=13, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=13, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..4cc0c09 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(128, 256, 512), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(64, 64, 64, 128), + out_channels=256, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=256, + channels=256, + num_classes=11, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=11, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..b82c27c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(128, 256, 512), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(64, 64, 64, 128), + out_channels=256, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=256, + channels=256, + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..8c65906 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(128, 256, 512), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(64, 64, 64, 128), + out_channels=256, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=256, + channels=256, + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=1, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..b0c2366 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(512, 1024, 2048), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(256, 256, 256, 512), + out_channels=1024, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=1024, + channels=1024, + num_classes=10, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=10, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..9611320 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(512, 1024, 2048), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(256, 256, 256, 512), + out_channels=1024, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=1024, + channels=1024, + num_classes=13, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=13, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..8fd0753 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(512, 1024, 2048), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(256, 256, 256, 512), + out_channels=1024, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=1024, + channels=1024, + num_classes=11, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=11, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..a99bcbb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(512, 1024, 2048), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(256, 256, 256, 512), + out_channels=1024, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=1024, + channels=1024, + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..14e948f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv1/my_bisenetv1_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,140 @@ +_base_ = [ + '../_base_/models/bisenetv1_r18-d32.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + context_channels=(512, 1024, 2048), + norm_cfg=dict( + type='BN', + ), + spatial_channels=(256, 256, 256, 512), + out_channels=1024, + backbone_cfg=dict( + type='ResNet', + norm_cfg=dict( + type='BN', + ), + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + in_channels=1024, + channels=1024, + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + dict( + type='FCNHead', + in_channels=512, + channels=256, + num_convs=1, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/README.md b/Seg_All_In_One_MMSeg/configs/bisenetv2/README.md new file mode 100644 index 0000000..a5871df --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/README.md @@ -0,0 +1,53 @@ +# BiSeNetV2 + +> [Bisenet v2: Bilateral Network with Guided Aggregation for Real-time Semantic Segmentation](https://arxiv.org/abs/2004.02147) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +The low-level details and high-level semantics are both essential to the semantic segmentation task. However, to speed up the model inference, current approaches almost always sacrifice the low-level details, which leads to a considerable accuracy decrease. We propose to treat these spatial details and categorical semantics separately to achieve high accuracy and high efficiency for realtime semantic segmentation. To this end, we propose an efficient and effective architecture with a good trade-off between speed and accuracy, termed Bilateral Segmentation Network (BiSeNet V2). This architecture involves: (i) a Detail Branch, with wide channels and shallow layers to capture low-level details and generate high-resolution feature representation; (ii) a Semantic Branch, with narrow channels and deep layers to obtain high-level semantic context. The Semantic Branch is lightweight due to reducing the channel capacity and a fast-downsampling strategy. Furthermore, we design a Guided Aggregation Layer to enhance mutual connections and fuse both types of feature representation. Besides, a booster training strategy is designed to improve the segmentation performance without any extra inference cost. Extensive quantitative and qualitative evaluations demonstrate that the proposed architecture performs favourably against a few state-of-the-art real-time semantic segmentation approaches. Specifically, for a 2,048x1,024 input, we achieve 72.6% Mean IoU on the Cityscapes test set with a speed of 156 FPS on one NVIDIA GeForce GTX 1080 Ti card, which is significantly faster than existing methods, yet we achieve better segmentation accuracy. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | ---------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| BiSeNetV2 | BiSeNetV2 | 1024x1024 | 160000 | 7.64 | 31.77 | V100 | 73.21 | 75.74 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv2/bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_4x4_1024x1024_160k_cityscapes_20210902_015551-bcf10f09.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_4x4_1024x1024_160k_cityscapes_20210902_015551.log.json) | +| BiSeNetV2 | BiSeNetV2 (OHEM) | 1024x1024 | 160000 | 7.64 | - | V100 | 73.57 | 75.80 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv2/bisenetv2_fcn_4xb4-ohem-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_ohem_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_ohem_4x4_1024x1024_160k_cityscapes_20210902_112947-5f8103b4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_ohem_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_ohem_4x4_1024x1024_160k_cityscapes_20210902_112947.log.json) | +| BiSeNetV2 | BiSeNetV2 (4x8) | 1024x1024 | 160000 | 15.05 | - | V100 | 75.76 | 77.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv2/bisenetv2_fcn_4xb8-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_4x8_1024x1024_160k_cityscapes/bisenetv2_fcn_4x8_1024x1024_160k_cityscapes_20210903_000032-e1a2eed6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_4x8_1024x1024_160k_cityscapes/bisenetv2_fcn_4x8_1024x1024_160k_cityscapes_20210903_000032.log.json) | +| BiSeNetV2 | BiSeNetV2 (FP16) | 1024x1024 | 160000 | 5.77 | 36.65 | V100 | 73.07 | 75.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv2/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes_20210902_045942-b979777b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes_20210902_045942.log.json) | + +Note: + +- `OHEM` means Online Hard Example Mining (OHEM) is adopted in training. +- `FP16` means Mixed Precision (FP16) is adopted in training. +- `4x8` means 4 GPUs with 8 samples per GPU in training. + +## Citation + +```bibtex +@article{yu2021bisenet, + title={Bisenet v2: Bilateral network with guided aggregation for real-time semantic segmentation}, + author={Yu, Changqian and Gao, Changxin and Wang, Jingbo and Yu, Gang and Shen, Chunhua and Sang, Nong}, + journal={International Journal of Computer Vision}, + pages={1--18}, + year={2021}, + publisher={Springer} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..970a170 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024.py @@ -0,0 +1,24 @@ +_base_ = [ + '../_base_/models/bisenetv2.py', + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (1024, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', _delete_=True, lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..8ed338c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024.py @@ -0,0 +1,6 @@ +_base_ = './bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024.py' +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005), + loss_scale=512.) diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-ohem-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-ohem-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..8d5cbcb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb4-ohem-160k_cityscapes-1024x1024.py @@ -0,0 +1,83 @@ +_base_ = [ + '../_base_/models/bisenetv2.py', + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (1024, 1024) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +models = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000)), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=19, + in_index=1, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000), + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=19, + in_index=2, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000), + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=19, + in_index=3, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000), + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=19, + in_index=4, + norm_cfg=norm_cfg, + concat_input=False, + align_corners=False, + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=10000), + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ], +) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb8-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb8-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..8fcba64 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/bisenetv2_fcn_4xb8-160k_cityscapes-1024x1024.py @@ -0,0 +1,24 @@ +_base_ = [ + '../_base_/models/bisenetv2.py', + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (1024, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict(batch_size=8, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/metafile.yaml b/Seg_All_In_One_MMSeg/configs/bisenetv2/metafile.yaml new file mode 100644 index 0000000..5430ec3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/metafile.yaml @@ -0,0 +1,114 @@ +Collections: +- Name: BiSeNetV2 + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: 'Bisenet v2: Bilateral Network with Guided Aggregation for Real-time Semantic + Segmentation' + URL: https://arxiv.org/abs/2004.02147 + README: configs/bisenetv2/README.md + Frameworks: + - PyTorch +Models: +- Name: bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024 + In Collection: BiSeNetV2 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.21 + mIoU(ms+flip): 75.74 + Config: configs/bisenetv2/bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - BiSeNetV2 + - BiSeNetV2 + Training Resources: 4x V100 GPUS + Memory (GB): 7.64 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_4x4_1024x1024_160k_cityscapes_20210902_015551-bcf10f09.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_4x4_1024x1024_160k_cityscapes_20210902_015551.log.json + Paper: + Title: 'Bisenet v2: Bilateral Network with Guided Aggregation for Real-time Semantic + Segmentation' + URL: https://arxiv.org/abs/2004.02147 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv2.py#L545 + Framework: PyTorch +- Name: bisenetv2_fcn_4xb4-ohem-160k_cityscapes-1024x1024 + In Collection: BiSeNetV2 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.57 + mIoU(ms+flip): 75.8 + Config: configs/bisenetv2/bisenetv2_fcn_4xb4-ohem-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - BiSeNetV2 + - BiSeNetV2 + Training Resources: 4x V100 GPUS + Memory (GB): 7.64 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_ohem_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_ohem_4x4_1024x1024_160k_cityscapes_20210902_112947-5f8103b4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_ohem_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_ohem_4x4_1024x1024_160k_cityscapes_20210902_112947.log.json + Paper: + Title: 'Bisenet v2: Bilateral Network with Guided Aggregation for Real-time Semantic + Segmentation' + URL: https://arxiv.org/abs/2004.02147 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv2.py#L545 + Framework: PyTorch +- Name: bisenetv2_fcn_4xb8-160k_cityscapes-1024x1024 + In Collection: BiSeNetV2 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.76 + mIoU(ms+flip): 77.79 + Config: configs/bisenetv2/bisenetv2_fcn_4xb8-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 32 + Architecture: + - BiSeNetV2 + - BiSeNetV2 + Training Resources: 4x V100 GPUS + Memory (GB): 15.05 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_4x8_1024x1024_160k_cityscapes/bisenetv2_fcn_4x8_1024x1024_160k_cityscapes_20210903_000032-e1a2eed6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_4x8_1024x1024_160k_cityscapes/bisenetv2_fcn_4x8_1024x1024_160k_cityscapes_20210903_000032.log.json + Paper: + Title: 'Bisenet v2: Bilateral Network with Guided Aggregation for Real-time Semantic + Segmentation' + URL: https://arxiv.org/abs/2004.02147 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv2.py#L545 + Framework: PyTorch +- Name: bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024 + In Collection: BiSeNetV2 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.07 + mIoU(ms+flip): 75.13 + Config: configs/bisenetv2/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - BiSeNetV2 + - BiSeNetV2 + Training Resources: 4x V100 GPUS + Memory (GB): 5.77 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes_20210902_045942-b979777b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/bisenetv2/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes_20210902_045942.log.json + Paper: + Title: 'Bisenet v2: Bilateral Network with Guided Aggregation for Real-time Semantic + Segmentation' + URL: https://arxiv.org/abs/2004.02147 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/backbones/bisenetv2.py#L545 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..8f4fe14 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,155 @@ +_base_ = [ + '../_base_/models/bisenetv2.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=10, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=10, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=10, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=10, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..c555713 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,155 @@ +_base_ = [ + '../_base_/models/bisenetv2.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py',# TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=13, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=13, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=13, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=13, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..e74aa47 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,154 @@ +_base_ = [ + '../_base_/models/bisenetv2.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=11, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=11, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=11, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=11, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..5e015c1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,154 @@ +_base_ = [ + '../_base_/models/bisenetv2.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..bb8b479 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,154 @@ +_base_ = [ + '../_base_/models/bisenetv2.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..aa2fbe8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,155 @@ +_base_ = [ + '../_base_/models/bisenetv2_large.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=10, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=32, + channels=16, + num_convs=2, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=64, + num_convs=2, + num_classes=10, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=10, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=256, + channels=1024, + num_convs=2, + num_classes=10, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..f8fccb8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,155 @@ +_base_ = [ + '../_base_/models/bisenetv2_large.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py',# TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=13, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=32, + channels=16, + num_convs=2, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=64, + num_convs=2, + num_classes=13, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=13, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=256, + channels=1024, + num_convs=2, + num_classes=13, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..fea5d5f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,154 @@ +_base_ = [ + '../_base_/models/bisenetv2_large.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=11, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=32, + channels=16, + num_convs=2, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=64, + num_convs=2, + num_classes=11, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=11, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=256, + channels=1024, + num_convs=2, + num_classes=11, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..9628965 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,154 @@ +_base_ = [ + '../_base_/models/bisenetv2_large.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=32, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=256, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..a6c8675 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_bisenetv2_large_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,154 @@ +_base_ = [ + '../_base_/models/bisenetv2_large.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=32, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=256, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..84bb367 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,164 @@ +_base_ = [ + '../_base_/models/en_bisenetv2.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=10, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=64, + channels=16, + num_convs=2, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=2, + num_classes=10, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=10, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=10, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..f5445e0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,164 @@ +_base_ = [ + '../_base_/models/en_bisenetv2.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=13, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=64, # <-- FIX: Was 16. Corresponds to semantic_out_s3 (in_index=1) + channels=16, + num_convs=2, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, # <-- FIX: Was 32. Corresponds to semantic_out_s4 (in_index=2) + channels=64, + num_convs=2, + num_classes=13, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, # <-- FIX: Was 64. Corresponds to semantic_fused (in_index=3) + channels=256, + num_convs=2, + num_classes=13, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=13, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..0a51ae3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,164 @@ +_base_ = [ + '../_base_/models/en_bisenetv2.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=11, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=64, + channels=16, + num_convs=2, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=2, + num_classes=11, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=11, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=11, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..fdacd5f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,164 @@ +_base_ = [ + '../_base_/models/en_bisenetv2.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=64, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..f49a508 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/bisenetv2/my_en_bisenetv2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,164 @@ +_base_ = [ + '../_base_/models/en_bisenetv2.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=64, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/README.md b/Seg_All_In_One_MMSeg/configs/ccnet/README.md new file mode 100644 index 0000000..64dd5f0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/README.md @@ -0,0 +1,67 @@ +# CCNet + +> [CCNet: Criss-Cross Attention for Semantic Segmentation](https://arxiv.org/abs/1811.11721) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Contextual information is vital in visual understanding problems, such as semantic segmentation and object detection. We propose a Criss-Cross Network (CCNet) for obtaining full-image contextual information in a very effective and efficient way. Concretely, for each pixel, a novel criss-cross attention module harvests the contextual information of all the pixels on its criss-cross path. By taking a further recurrent operation, each pixel can finally capture the full-image dependencies. Besides, a category consistent loss is proposed to enforce the criss-cross attention module to produce more discriminative features. Overall, CCNet is with the following merits: 1) GPU memory friendly. Compared with the non-local block, the proposed recurrent criss-cross attention module requires 11x less GPU memory usage. 2) High computational efficiency. The recurrent criss-cross attention significantly reduces FLOPs by about 85% of the non-local block. 3) The state-of-the-art performance. We conduct extensive experiments on semantic segmentation benchmarks including Cityscapes, ADE20K, human parsing benchmark LIP, instance segmentation benchmark COCO, video segmentation benchmark CamVid. In particular, our CCNet achieves the mIoU scores of 81.9%, 45.76% and 55.47% on the Cityscapes test set, the ADE20K validation set and the LIP validation set respectively, which are the new state-of-the-art results. The source codes are available at [this https URL](https://github.com/speedinghzl/CCNet). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| CCNet | R-50-D8 | 512x1024 | 40000 | 6 | 3.32 | V100 | 77.76 | 78.87 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x1024_40k_cityscapes/ccnet_r50-d8_512x1024_40k_cityscapes_20200616_142517-4123f401.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x1024_40k_cityscapes/ccnet_r50-d8_512x1024_40k_cityscapes_20200616_142517.log.json) | +| CCNet | R-101-D8 | 512x1024 | 40000 | 9.5 | 2.31 | V100 | 76.35 | 78.19 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x1024_40k_cityscapes/ccnet_r101-d8_512x1024_40k_cityscapes_20200616_142540-a3b84ba6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x1024_40k_cityscapes/ccnet_r101-d8_512x1024_40k_cityscapes_20200616_142540.log.json) | +| CCNet | R-50-D8 | 769x769 | 40000 | 6.8 | 1.43 | V100 | 78.46 | 79.93 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_769x769_40k_cityscapes/ccnet_r50-d8_769x769_40k_cityscapes_20200616_145125-76d11884.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_769x769_40k_cityscapes/ccnet_r50-d8_769x769_40k_cityscapes_20200616_145125.log.json) | +| CCNet | R-101-D8 | 769x769 | 40000 | 10.7 | 1.01 | V100 | 76.94 | 78.62 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_769x769_40k_cityscapes/ccnet_r101-d8_769x769_40k_cityscapes_20200617_101428-4f57c8d0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_769x769_40k_cityscapes/ccnet_r101-d8_769x769_40k_cityscapes_20200617_101428.log.json) | +| CCNet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 79.03 | 80.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x1024_80k_cityscapes/ccnet_r50-d8_512x1024_80k_cityscapes_20200617_010421-869a3423.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x1024_80k_cityscapes/ccnet_r50-d8_512x1024_80k_cityscapes_20200617_010421.log.json) | +| CCNet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 78.87 | 79.90 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x1024_80k_cityscapes/ccnet_r101-d8_512x1024_80k_cityscapes_20200617_203935-ffae8917.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x1024_80k_cityscapes/ccnet_r101-d8_512x1024_80k_cityscapes_20200617_203935.log.json) | +| CCNet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.29 | 81.08 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_769x769_80k_cityscapes/ccnet_r50-d8_769x769_80k_cityscapes_20200617_010421-73eed8ca.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_769x769_80k_cityscapes/ccnet_r50-d8_769x769_80k_cityscapes_20200617_010421.log.json) | +| CCNet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 79.45 | 80.66 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_769x769_80k_cityscapes/ccnet_r101-d8_769x769_80k_cityscapes_20200618_011502-ad3cd481.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_769x769_80k_cityscapes/ccnet_r101-d8_769x769_80k_cityscapes_20200618_011502.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| CCNet | R-50-D8 | 512x512 | 80000 | 8.8 | 20.89 | V100 | 41.78 | 42.98 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_80k_ade20k/ccnet_r50-d8_512x512_80k_ade20k_20200615_014848-aa37f61e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_80k_ade20k/ccnet_r50-d8_512x512_80k_ade20k_20200615_014848.log.json) | +| CCNet | R-101-D8 | 512x512 | 80000 | 12.2 | 14.11 | V100 | 43.97 | 45.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_80k_ade20k/ccnet_r101-d8_512x512_80k_ade20k_20200615_014848-1f4929a3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_80k_ade20k/ccnet_r101-d8_512x512_80k_ade20k_20200615_014848.log.json) | +| CCNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 42.08 | 43.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_160k_ade20k/ccnet_r50-d8_512x512_160k_ade20k_20200616_084435-7c97193b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_160k_ade20k/ccnet_r50-d8_512x512_160k_ade20k_20200616_084435.log.json) | +| CCNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 43.71 | 45.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_160k_ade20k/ccnet_r101-d8_512x512_160k_ade20k_20200616_000644-e849e007.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_160k_ade20k/ccnet_r101-d8_512x512_160k_ade20k_20200616_000644.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| CCNet | R-50-D8 | 512x512 | 20000 | 6 | 20.45 | V100 | 76.17 | 77.51 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_20k_voc12aug/ccnet_r50-d8_512x512_20k_voc12aug_20200617_193212-fad81784.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_20k_voc12aug/ccnet_r50-d8_512x512_20k_voc12aug_20200617_193212.log.json) | +| CCNet | R-101-D8 | 512x512 | 20000 | 9.5 | 13.64 | V100 | 77.27 | 79.02 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_20k_voc12aug/ccnet_r101-d8_512x512_20k_voc12aug_20200617_193212-0007b61d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_20k_voc12aug/ccnet_r101-d8_512x512_20k_voc12aug_20200617_193212.log.json) | +| CCNet | R-50-D8 | 512x512 | 40000 | - | - | V100 | 75.96 | 77.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_40k_voc12aug/ccnet_r50-d8_512x512_40k_voc12aug_20200613_232127-c2a15f02.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_40k_voc12aug/ccnet_r50-d8_512x512_40k_voc12aug_20200613_232127.log.json) | +| CCNet | R-101-D8 | 512x512 | 40000 | - | - | V100 | 77.87 | 78.90 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet/ccnet_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_40k_voc12aug/ccnet_r101-d8_512x512_40k_voc12aug_20200613_232127-c30da577.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_40k_voc12aug/ccnet_r101-d8_512x512_40k_voc12aug_20200613_232127.log.json) | + +## Citation + +```bibtex +@article{huang2018ccnet, + title={CCNet: Criss-Cross Attention for Semantic Segmentation}, + author={Huang, Zilong and Wang, Xinggang and Huang, Lichao and Huang, Chang and Wei, Yunchao and Liu, Wenyu}, + booktitle={ICCV}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..0c49e1e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './ccnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..f24f5a7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './ccnet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..b358e12 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './ccnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..7575076 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './ccnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..a29d118 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './ccnet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..fd421a2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './ccnet_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..425dfcf --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './ccnet_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..f6dcb9c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './ccnet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..84fc51a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..a930794 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..89bfe81 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..5f7c954 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..cee810c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..76a90d9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..a8aeb85 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..f7fced0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/ccnet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/ccnet/metafile.yaml new file mode 100644 index 0000000..62e5694 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/metafile.yaml @@ -0,0 +1,391 @@ +Collections: +- Name: CCNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + README: configs/ccnet/README.md + Frameworks: + - PyTorch +Models: +- Name: ccnet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.76 + mIoU(ms+flip): 78.87 + Config: configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - CCNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x1024_40k_cityscapes/ccnet_r50-d8_512x1024_40k_cityscapes_20200616_142517-4123f401.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x1024_40k_cityscapes/ccnet_r50-d8_512x1024_40k_cityscapes_20200616_142517.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.35 + mIoU(ms+flip): 78.19 + Config: configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - CCNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x1024_40k_cityscapes/ccnet_r101-d8_512x1024_40k_cityscapes_20200616_142540-a3b84ba6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x1024_40k_cityscapes/ccnet_r101-d8_512x1024_40k_cityscapes_20200616_142540.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.46 + mIoU(ms+flip): 79.93 + Config: configs/ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - CCNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_769x769_40k_cityscapes/ccnet_r50-d8_769x769_40k_cityscapes_20200616_145125-76d11884.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_769x769_40k_cityscapes/ccnet_r50-d8_769x769_40k_cityscapes_20200616_145125.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.94 + mIoU(ms+flip): 78.62 + Config: configs/ccnet/ccnet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - CCNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_769x769_40k_cityscapes/ccnet_r101-d8_769x769_40k_cityscapes_20200617_101428-4f57c8d0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_769x769_40k_cityscapes/ccnet_r101-d8_769x769_40k_cityscapes_20200617_101428.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.03 + mIoU(ms+flip): 80.16 + Config: configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - CCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x1024_80k_cityscapes/ccnet_r50-d8_512x1024_80k_cityscapes_20200617_010421-869a3423.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x1024_80k_cityscapes/ccnet_r50-d8_512x1024_80k_cityscapes_20200617_010421.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.87 + mIoU(ms+flip): 79.9 + Config: configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - CCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x1024_80k_cityscapes/ccnet_r101-d8_512x1024_80k_cityscapes_20200617_203935-ffae8917.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x1024_80k_cityscapes/ccnet_r101-d8_512x1024_80k_cityscapes_20200617_203935.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.29 + mIoU(ms+flip): 81.08 + Config: configs/ccnet/ccnet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - CCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_769x769_80k_cityscapes/ccnet_r50-d8_769x769_80k_cityscapes_20200617_010421-73eed8ca.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_769x769_80k_cityscapes/ccnet_r50-d8_769x769_80k_cityscapes_20200617_010421.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.45 + mIoU(ms+flip): 80.66 + Config: configs/ccnet/ccnet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - CCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_769x769_80k_cityscapes/ccnet_r101-d8_769x769_80k_cityscapes_20200618_011502-ad3cd481.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_769x769_80k_cityscapes/ccnet_r101-d8_769x769_80k_cityscapes_20200618_011502.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.78 + mIoU(ms+flip): 42.98 + Config: configs/ccnet/ccnet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - CCNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_80k_ade20k/ccnet_r50-d8_512x512_80k_ade20k_20200615_014848-aa37f61e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_80k_ade20k/ccnet_r50-d8_512x512_80k_ade20k_20200615_014848.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.97 + mIoU(ms+flip): 45.13 + Config: configs/ccnet/ccnet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - CCNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_80k_ade20k/ccnet_r101-d8_512x512_80k_ade20k_20200615_014848-1f4929a3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_80k_ade20k/ccnet_r101-d8_512x512_80k_ade20k_20200615_014848.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.08 + mIoU(ms+flip): 43.13 + Config: configs/ccnet/ccnet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - CCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_160k_ade20k/ccnet_r50-d8_512x512_160k_ade20k_20200616_084435-7c97193b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_160k_ade20k/ccnet_r50-d8_512x512_160k_ade20k_20200616_084435.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.71 + mIoU(ms+flip): 45.04 + Config: configs/ccnet/ccnet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - CCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_160k_ade20k/ccnet_r101-d8_512x512_160k_ade20k_20200616_000644-e849e007.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_160k_ade20k/ccnet_r101-d8_512x512_160k_ade20k_20200616_000644.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.17 + mIoU(ms+flip): 77.51 + Config: configs/ccnet/ccnet_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - CCNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_20k_voc12aug/ccnet_r50-d8_512x512_20k_voc12aug_20200617_193212-fad81784.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_20k_voc12aug/ccnet_r50-d8_512x512_20k_voc12aug_20200617_193212.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.27 + mIoU(ms+flip): 79.02 + Config: configs/ccnet/ccnet_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - CCNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_20k_voc12aug/ccnet_r101-d8_512x512_20k_voc12aug_20200617_193212-0007b61d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_20k_voc12aug/ccnet_r101-d8_512x512_20k_voc12aug_20200617_193212.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 75.96 + mIoU(ms+flip): 77.04 + Config: configs/ccnet/ccnet_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - CCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_40k_voc12aug/ccnet_r50-d8_512x512_40k_voc12aug_20200613_232127-c2a15f02.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r50-d8_512x512_40k_voc12aug/ccnet_r50-d8_512x512_40k_voc12aug_20200613_232127.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch +- Name: ccnet_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: CCNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.87 + mIoU(ms+flip): 78.9 + Config: configs/ccnet/ccnet_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - CCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_40k_voc12aug/ccnet_r101-d8_512x512_40k_voc12aug_20200613_232127-c30da577.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ccnet/ccnet_r101-d8_512x512_40k_voc12aug/ccnet_r101-d8_512x512_40k_voc12aug_20200613_232127.log.json + Paper: + Title: 'CCNet: Criss-Cross Attention for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.11721 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/apc_head.py#L111 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/my_ccnet_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py b/Seg_All_In_One_MMSeg/configs/ccnet/my_ccnet_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py new file mode 100644 index 0000000..85f08fd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/my_ccnet_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py @@ -0,0 +1,105 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=True, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 512), + stride=(341, 341), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ccnet/my_ccnet_r50_Pre_g1-40k_check_4000_my_dataset_model-680x680.py b/Seg_All_In_One_MMSeg/configs/ccnet/my_ccnet_r50_Pre_g1-40k_check_4000_my_dataset_model-680x680.py new file mode 100644 index 0000000..98fa3fc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ccnet/my_ccnet_r50_Pre_g1-40k_check_4000_my_dataset_model-680x680.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/ccnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (680, 680) + +data_preprocessor = dict( + size=(680, 680), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + ), + data_preprocessor=dict( + size=(680, 680), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/cgnet/README.md b/Seg_All_In_One_MMSeg/configs/cgnet/README.md new file mode 100644 index 0000000..96c9fcf --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/cgnet/README.md @@ -0,0 +1,46 @@ +# CGNet + +> [CGNet: A Light-weight Context Guided Network for Semantic Segmentation](https://arxiv.org/abs/1811.08201) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +The demand of applying semantic segmentation model on mobile devices has been increasing rapidly. Current state-of-the-art networks have enormous amount of parameters hence unsuitable for mobile devices, while other small memory footprint models follow the spirit of classification network and ignore the inherent characteristic of semantic segmentation. To tackle this problem, we propose a novel Context Guided Network (CGNet), which is a light-weight and efficient network for semantic segmentation. We first propose the Context Guided (CG) block, which learns the joint feature of both local feature and surrounding context, and further improves the joint feature with the global context. Based on the CG block, we develop CGNet which captures contextual information in all stages of the network and is specially tailored for increasing segmentation accuracy. CGNet is also elaborately designed to reduce the number of parameters and save memory footprint. Under an equivalent number of parameters, the proposed CGNet significantly outperforms existing segmentation networks. Extensive experiments on Cityscapes and CamVid datasets verify the effectiveness of the proposed approach. Specifically, without any post-processing and multi-scale testing, the proposed CGNet achieves 64.8% mean IoU on Cityscapes with less than 0.5 M parameters. The source code for the complete system can be found at [this https URL](https://github.com/wutianyiRosun/CGNet). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| CGNet | M3N21 | 680x680 | 60000 | 7.5 | 30.51 | V100 | 65.63 | 68.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/cgnet/cgnet_fcn_4xb4-60k_cityscapes-680x680.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/cgnet/cgnet_680x680_60k_cityscapes/cgnet_680x680_60k_cityscapes_20201101_110253-4c0b2f2d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/cgnet/cgnet_680x680_60k_cityscapes/cgnet_680x680_60k_cityscapes-20201101_110253.log.json) | +| CGNet | M3N21 | 512x1024 | 60000 | 8.3 | 31.14 | V100 | 68.27 | 70.33 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/cgnet/cgnet_fcn_4xb8-60k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/cgnet/cgnet_512x1024_60k_cityscapes/cgnet_512x1024_60k_cityscapes_20201101_110254-124ea03b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/cgnet/cgnet_512x1024_60k_cityscapes/cgnet_512x1024_60k_cityscapes-20201101_110254.log.json) | + +## Citation + +```bibtext +@article{wu2020cgnet, + title={Cgnet: A light-weight context guided network for semantic segmentation}, + author={Wu, Tianyi and Tang, Sheng and Zhang, Rui and Cao, Juan and Zhang, Yongdong}, + journal={IEEE Transactions on Image Processing}, + volume={30}, + pages={1169--1179}, + year={2020}, + publisher={IEEE} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/cgnet/cgnet_fcn_4xb4-60k_cityscapes-680x680.py b/Seg_All_In_One_MMSeg/configs/cgnet/cgnet_fcn_4xb4-60k_cityscapes-680x680.py new file mode 100644 index 0000000..6a2c0ed --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/cgnet/cgnet_fcn_4xb4-60k_cityscapes-680x680.py @@ -0,0 +1,59 @@ +_base_ = [ + '../_base_/models/cgnet.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py' +] + +# optimizer +optimizer = dict(type='Adam', lr=0.001, eps=1e-08, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + by_epoch=False, + begin=0, + end=60000) +] +# runtime settings +total_iters = 60000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=total_iters, val_interval=4000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), + sampler_seed=dict(type='DistSamplerSeedHook')) + +crop_size = (680, 680) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size), + dict(type='RandomFlip', prob=0.5), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=8, num_workers=4, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, num_workers=4, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/cgnet/cgnet_fcn_4xb8-60k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/cgnet/cgnet_fcn_4xb8-60k_cityscapes-512x1024.py new file mode 100644 index 0000000..8be29de --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/cgnet/cgnet_fcn_4xb8-60k_cityscapes-512x1024.py @@ -0,0 +1,38 @@ +_base_ = [ + '../_base_/models/cgnet.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py' +] + +# optimizer +optimizer = dict(type='Adam', lr=0.001, eps=1e-08, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + by_epoch=False, + begin=0, + end=60000) +] +# runtime settings +total_iters = 60000 +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=total_iters, val_interval=4000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), + sampler_seed=dict(type='DistSamplerSeedHook')) + +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) + +train_dataloader = dict(batch_size=8) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/cgnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/cgnet/metafile.yaml new file mode 100644 index 0000000..063fc8b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/cgnet/metafile.yaml @@ -0,0 +1,61 @@ +Collections: +- Name: CGNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: 'CGNet: A Light-weight Context Guided Network for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.08201 + README: configs/cgnet/README.md + Frameworks: + - PyTorch +Models: +- Name: cgnet_fcn_4xb4-60k_cityscapes-680x680 + In Collection: CGNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 65.63 + mIoU(ms+flip): 68.04 + Config: configs/cgnet/cgnet_fcn_4xb4-60k_cityscapes-680x680.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - M3N21 + - CGNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/cgnet/cgnet_680x680_60k_cityscapes/cgnet_680x680_60k_cityscapes_20201101_110253-4c0b2f2d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/cgnet/cgnet_680x680_60k_cityscapes/cgnet_680x680_60k_cityscapes-20201101_110253.log.json + Paper: + Title: 'CGNet: A Light-weight Context Guided Network for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.08201 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/cgnet.py#L187 + Framework: PyTorch +- Name: cgnet_fcn_4xb8-60k_cityscapes-512x1024 + In Collection: CGNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 68.27 + mIoU(ms+flip): 70.33 + Config: configs/cgnet/cgnet_fcn_4xb8-60k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 32 + Architecture: + - M3N21 + - CGNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/cgnet/cgnet_512x1024_60k_cityscapes/cgnet_512x1024_60k_cityscapes_20201101_110254-124ea03b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/cgnet/cgnet_512x1024_60k_cityscapes/cgnet_512x1024_60k_cityscapes-20201101_110254.log.json + Paper: + Title: 'CGNet: A Light-weight Context Guided Network for Semantic Segmentation' + URL: https://arxiv.org/abs/1811.08201 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/cgnet.py#L187 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_fcn_fcn_g1-40k_check_4000_my_dataset_model-680x680.py b/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_fcn_fcn_g1-40k_check_4000_my_dataset_model-680x680.py new file mode 100644 index 0000000..c68f6ae --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_fcn_fcn_g1-40k_check_4000_my_dataset_model-680x680.py @@ -0,0 +1,99 @@ +_base_ = [ + '../_base_/models/cgnet.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (680, 680) + +data_preprocessor = dict( + size=(680, 680), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(680, 680), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + + type='FCNHead', + in_channels=256, + in_index=2, + channels=256, + num_convs=0, + concat_input=False, + dropout_ratio=0, + num_classes=19, + norm_cfg=dict( + type='SyncBN', + eps=0.001, + requires_grad=True, + ), + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + _delete_=True, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_fcn_g1-40k_check_4000_my_dataset_model-680x680.py b/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_fcn_g1-40k_check_4000_my_dataset_model-680x680.py new file mode 100644 index 0000000..055e5d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_fcn_g1-40k_check_4000_my_dataset_model-680x680.py @@ -0,0 +1,84 @@ +_base_ = [ + '../_base_/models/cgnet.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (680, 680) + +data_preprocessor = dict( + size=(680, 680), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(680, 680), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_r50_Pre_g1-40k_check_4000_my_dataset_model-680x680.py b/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_r50_Pre_g1-40k_check_4000_my_dataset_model-680x680.py new file mode 100644 index 0000000..3ca6176 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/cgnet/my_cgnet_r50_Pre_g1-40k_check_4000_my_dataset_model-680x680.py @@ -0,0 +1,100 @@ +_base_ = [ + '../_base_/models/cgnet.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (680, 680) + +data_preprocessor = dict( + size=(680, 680), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + ), + data_preprocessor=dict( + size=(680, 680), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + _delete_ = True, + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/convnext/README.md b/Seg_All_In_One_MMSeg/configs/convnext/README.md new file mode 100644 index 0000000..d78fe6e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/convnext/README.md @@ -0,0 +1,74 @@ +# ConvNeXt + +> [A ConvNet for the 2020s](https://arxiv.org/abs/2201.03545) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +The "Roaring 20s" of visual recognition began with the introduction of Vision Transformers (ViTs), which quickly superseded ConvNets as the state-of-the-art image classification model. A vanilla ViT, on the other hand, faces difficulties when applied to general computer vision tasks such as object detection and semantic segmentation. It is the hierarchical Transformers (e.g., Swin Transformers) that reintroduced several ConvNet priors, making Transformers practically viable as a generic vision backbone and demonstrating remarkable performance on a wide variety of vision tasks. However, the effectiveness of such hybrid approaches is still largely credited to the intrinsic superiority of Transformers, rather than the inherent inductive biases of convolutions. In this work, we reexamine the design spaces and test the limits of what a pure ConvNet can achieve. We gradually "modernize" a standard ResNet toward the design of a vision Transformer, and discover several key components that contribute to the performance difference along the way. The outcome of this exploration is a family of pure ConvNet models dubbed ConvNeXt. Constructed entirely from standard ConvNet modules, ConvNeXts compete favorably with Transformers in terms of accuracy and scalability, achieving 87.8% ImageNet top-1 accuracy and outperforming Swin Transformers on COCO detection and ADE20K segmentation, while maintaining the simplicity and efficiency of standard ConvNets. + + + +
+ +
+ +### Usage + +- ConvNeXt backbone needs to install [MMClassification](https://github.com/open-mmlab/mmclassification) first, which has abundant backbones for downstream tasks. + +```shell +pip install mmpretrain>=1.0.0rc7 +``` + +### Pre-trained Models + +The pre-trained models on ImageNet-1k or ImageNet-21k are used to fine-tune on the downstream tasks. + +| Model | Training Data | Params(M) | Flops(G) | Download | +| :-----------: | :-----------: | :-------: | :------: | :----------------------------------------------------------------------------------------------------------------------------------------------: | +| ConvNeXt-T\* | ImageNet-1k | 28.59 | 4.46 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-tiny_3rdparty_32xb128-noema_in1k_20220301-795e9634.pth) | +| ConvNeXt-S\* | ImageNet-1k | 50.22 | 8.69 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-small_3rdparty_32xb128-noema_in1k_20220301-303e75e3.pth) | +| ConvNeXt-B\* | ImageNet-1k | 88.59 | 15.36 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-base_3rdparty_32xb128-noema_in1k_20220301-2a0ee547.pth) | +| ConvNeXt-B\* | ImageNet-21k | 88.59 | 15.36 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-base_3rdparty_in21k_20220301-262fd037.pth) | +| ConvNeXt-L\* | ImageNet-21k | 197.77 | 34.37 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-large_3rdparty_in21k_20220301-e6e0ea0a.pth) | +| ConvNeXt-XL\* | ImageNet-21k | 350.20 | 60.93 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-xlarge_3rdparty_in21k_20220301-08aa5ddc.pth) | + +*Models with* are converted from the [official repo](https://github.com/facebookresearch/ConvNeXt/tree/main/semantic_segmentation#results-and-fine-tuned-models).\* + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | ----------- | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UPerNet | ConvNeXt-T | 512x512 | 160000 | 4.23 | 19.90 | V100 | 46.11 | 46.62 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/convnext/convnext-tiny_upernet_8xb2-amp-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_tiny_fp16_512x512_160k_ade20k/upernet_convnext_tiny_fp16_512x512_160k_ade20k_20220227_124553-cad485de.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_tiny_fp16_512x512_160k_ade20k/upernet_convnext_tiny_fp16_512x512_160k_ade20k_20220227_124553.log.json) | +| UPerNet | ConvNeXt-S | 512x512 | 160000 | 5.16 | 15.18 | V100 | 48.56 | 49.02 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/convnext/convnext-small_upernet_8xb2-amp-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_small_fp16_512x512_160k_ade20k/upernet_convnext_small_fp16_512x512_160k_ade20k_20220227_131208-1b1e394f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_small_fp16_512x512_160k_ade20k/upernet_convnext_small_fp16_512x512_160k_ade20k_20220227_131208.log.json) | +| UPerNet | ConvNeXt-B | 512x512 | 160000 | 6.33 | 14.41 | V100 | 48.71 | 49.54 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_base_fp16_512x512_160k_ade20k/upernet_convnext_base_fp16_512x512_160k_ade20k_20220227_181227-02a24fc6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_base_fp16_512x512_160k_ade20k/upernet_convnext_base_fp16_512x512_160k_ade20k_20220227_181227.log.json) | +| UPerNet | ConvNeXt-B | 640x640 | 160000 | 8.53 | 10.88 | V100 | 52.13 | 52.66 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_base_fp16_640x640_160k_ade20k/upernet_convnext_base_fp16_640x640_160k_ade20k_20220227_182859-9280e39b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_base_fp16_640x640_160k_ade20k/upernet_convnext_base_fp16_640x640_160k_ade20k_20220227_182859.log.json) | +| UPerNet | ConvNeXt-L | 640x640 | 160000 | 12.08 | 7.69 | V100 | 53.16 | 53.38 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/convnext/convnext-large_upernet_8xb2-amp-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_large_fp16_640x640_160k_ade20k/upernet_convnext_large_fp16_640x640_160k_ade20k_20220226_040532-e57aa54d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_large_fp16_640x640_160k_ade20k/upernet_convnext_large_fp16_640x640_160k_ade20k_20220226_040532.log.json) | +| UPerNet | ConvNeXt-XL | 640x640 | 160000 | 26.16\* | 6.33 | V100 | 53.58 | 54.11 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/convnext/convnext-xlarge_upernet_8xb2-amp-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_xlarge_fp16_640x640_160k_ade20k/upernet_convnext_xlarge_fp16_640x640_160k_ade20k_20220226_080344-95fc38c2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_xlarge_fp16_640x640_160k_ade20k/upernet_convnext_xlarge_fp16_640x640_160k_ade20k_20220226_080344.log.json) | + +Note: + +- `Mem (GB)` with * is collected when `cudnn_benchmark=True`, and hardware is V100. + +## Citation + +```bibtex +@article{liu2022convnet, + title={A ConvNet for the 2020s}, + author={Liu, Zhuang and Mao, Hanzi and Wu, Chao-Yuan and Feichtenhofer, Christoph and Darrell, Trevor and Xie, Saining}, + journal={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, + year={2022} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-512x512.py new file mode 100644 index 0000000..09c2aa6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-512x512.py @@ -0,0 +1,43 @@ +_base_ = [ + '../_base_/models/upernet_convnext.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(in_channels=[128, 256, 512, 1024], num_classes=150), + auxiliary_head=dict(in_channels=512, num_classes=150), + test_cfg=dict(mode='slide', crop_size=crop_size, stride=(341, 341)), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg={ + 'decay_rate': 0.9, + 'decay_type': 'stage_wise', + 'num_layers': 12 + }, + constructor='LearningRateDecayOptimizerConstructor', + loss_scale='dynamic') + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-640x640.py new file mode 100644 index 0000000..06a8643 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-640x640.py @@ -0,0 +1,58 @@ +_base_ = [ + '../_base_/models/upernet_convnext.py', + '../_base_/datasets/ade20k_640x640.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (640, 640) +data_preprocessor = dict(size=crop_size) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-base_3rdparty_in21k_20220301-262fd037.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + type='mmpretrain.ConvNeXt', + arch='base', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.4, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + decode_head=dict( + in_channels=[128, 256, 512, 1024], + num_classes=150, + ), + auxiliary_head=dict(in_channels=512, num_classes=150), + test_cfg=dict(mode='slide', crop_size=crop_size, stride=(426, 426)), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg={ + 'decay_rate': 0.9, + 'decay_type': 'stage_wise', + 'num_layers': 12 + }, + constructor='LearningRateDecayOptimizerConstructor', + loss_scale='dynamic') + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/convnext/convnext-large_upernet_8xb2-amp-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/convnext/convnext-large_upernet_8xb2-amp-160k_ade20k-640x640.py new file mode 100644 index 0000000..2956e86 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/convnext/convnext-large_upernet_8xb2-amp-160k_ade20k-640x640.py @@ -0,0 +1,58 @@ +_base_ = [ + '../_base_/models/upernet_convnext.py', + '../_base_/datasets/ade20k_640x640.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (640, 640) +data_preprocessor = dict(size=crop_size) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-large_3rdparty_in21k_20220301-e6e0ea0a.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + type='mmpretrain.ConvNeXt', + arch='large', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.4, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + decode_head=dict( + in_channels=[192, 384, 768, 1536], + num_classes=150, + ), + auxiliary_head=dict(in_channels=768, num_classes=150), + test_cfg=dict(mode='slide', crop_size=crop_size, stride=(426, 426)), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg={ + 'decay_rate': 0.9, + 'decay_type': 'stage_wise', + 'num_layers': 12 + }, + constructor='LearningRateDecayOptimizerConstructor', + loss_scale='dynamic') + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/convnext/convnext-small_upernet_8xb2-amp-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/convnext/convnext-small_upernet_8xb2-amp-160k_ade20k-512x512.py new file mode 100644 index 0000000..dbe45f1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/convnext/convnext-small_upernet_8xb2-amp-160k_ade20k-512x512.py @@ -0,0 +1,57 @@ +_base_ = [ + '../_base_/models/upernet_convnext.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-small_3rdparty_32xb128-noema_in1k_20220301-303e75e3.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + type='mmpretrain.ConvNeXt', + arch='small', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.3, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + decode_head=dict( + in_channels=[96, 192, 384, 768], + num_classes=150, + ), + auxiliary_head=dict(in_channels=384, num_classes=150), + test_cfg=dict(mode='slide', crop_size=crop_size, stride=(341, 341)), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg={ + 'decay_rate': 0.9, + 'decay_type': 'stage_wise', + 'num_layers': 12 + }, + constructor='LearningRateDecayOptimizerConstructor', + loss_scale='dynamic') + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/convnext/convnext-tiny_upernet_8xb2-amp-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/convnext/convnext-tiny_upernet_8xb2-amp-160k_ade20k-512x512.py new file mode 100644 index 0000000..d2e545a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/convnext/convnext-tiny_upernet_8xb2-amp-160k_ade20k-512x512.py @@ -0,0 +1,57 @@ +_base_ = [ + '../_base_/models/upernet_convnext.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-tiny_3rdparty_32xb128-noema_in1k_20220301-795e9634.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + type='mmpretrain.ConvNeXt', + arch='tiny', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.4, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + decode_head=dict( + in_channels=[96, 192, 384, 768], + num_classes=150, + ), + auxiliary_head=dict(in_channels=384, num_classes=150), + test_cfg=dict(mode='slide', crop_size=crop_size, stride=(341, 341)), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg={ + 'decay_rate': 0.9, + 'decay_type': 'stage_wise', + 'num_layers': 6 + }, + constructor='LearningRateDecayOptimizerConstructor', + loss_scale='dynamic') + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/convnext/convnext-xlarge_upernet_8xb2-amp-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/convnext/convnext-xlarge_upernet_8xb2-amp-160k_ade20k-640x640.py new file mode 100644 index 0000000..dfad734 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/convnext/convnext-xlarge_upernet_8xb2-amp-160k_ade20k-640x640.py @@ -0,0 +1,58 @@ +_base_ = [ + '../_base_/models/upernet_convnext.py', + '../_base_/datasets/ade20k_640x640.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (640, 640) +data_preprocessor = dict(size=crop_size) +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/convnext/downstream/convnext-xlarge_3rdparty_in21k_20220301-08aa5ddc.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + type='mmpretrain.ConvNeXt', + arch='xlarge', + out_indices=[0, 1, 2, 3], + drop_path_rate=0.4, + layer_scale_init_value=1.0, + gap_before_final_norm=False, + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + decode_head=dict( + in_channels=[256, 512, 1024, 2048], + num_classes=150, + ), + auxiliary_head=dict(in_channels=1024, num_classes=150), + test_cfg=dict(mode='slide', crop_size=crop_size, stride=(426, 426)), +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00008, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg={ + 'decay_rate': 0.9, + 'decay_type': 'stage_wise', + 'num_layers': 12 + }, + constructor='LearningRateDecayOptimizerConstructor', + loss_scale='dynamic') + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/convnext/metafile.yaml b/Seg_All_In_One_MMSeg/configs/convnext/metafile.yaml new file mode 100644 index 0000000..8340a37 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/convnext/metafile.yaml @@ -0,0 +1,145 @@ +Models: +- Name: convnext-tiny_upernet_8xb2-amp-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.11 + mIoU(ms+flip): 46.62 + Config: configs/convnext/convnext-tiny_upernet_8xb2-amp-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ConvNeXt-T + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 4.23 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_tiny_fp16_512x512_160k_ade20k/upernet_convnext_tiny_fp16_512x512_160k_ade20k_20220227_124553-cad485de.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_tiny_fp16_512x512_160k_ade20k/upernet_convnext_tiny_fp16_512x512_160k_ade20k_20220227_124553.log.json + Paper: + Title: A ConvNet for the 2020s + URL: https://arxiv.org/abs/2201.03545 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/convnext.py#L133 + Framework: PyTorch +- Name: convnext-small_upernet_8xb2-amp-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.56 + mIoU(ms+flip): 49.02 + Config: configs/convnext/convnext-small_upernet_8xb2-amp-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ConvNeXt-S + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 5.16 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_small_fp16_512x512_160k_ade20k/upernet_convnext_small_fp16_512x512_160k_ade20k_20220227_131208-1b1e394f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_small_fp16_512x512_160k_ade20k/upernet_convnext_small_fp16_512x512_160k_ade20k_20220227_131208.log.json + Paper: + Title: A ConvNet for the 2020s + URL: https://arxiv.org/abs/2201.03545 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/convnext.py#L133 + Framework: PyTorch +- Name: convnext-base_upernet_8xb2-amp-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.71 + mIoU(ms+flip): 49.54 + Config: configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ConvNeXt-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 6.33 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_base_fp16_512x512_160k_ade20k/upernet_convnext_base_fp16_512x512_160k_ade20k_20220227_181227-02a24fc6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_base_fp16_512x512_160k_ade20k/upernet_convnext_base_fp16_512x512_160k_ade20k_20220227_181227.log.json + Paper: + Title: A ConvNet for the 2020s + URL: https://arxiv.org/abs/2201.03545 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/convnext.py#L133 + Framework: PyTorch +- Name: convnext-base_upernet_8xb2-amp-160k_ade20k-640x640 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 52.13 + mIoU(ms+flip): 52.66 + Config: configs/convnext/convnext-base_upernet_8xb2-amp-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ConvNeXt-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 8.53 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_base_fp16_640x640_160k_ade20k/upernet_convnext_base_fp16_640x640_160k_ade20k_20220227_182859-9280e39b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_base_fp16_640x640_160k_ade20k/upernet_convnext_base_fp16_640x640_160k_ade20k_20220227_182859.log.json + Paper: + Title: A ConvNet for the 2020s + URL: https://arxiv.org/abs/2201.03545 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/convnext.py#L133 + Framework: PyTorch +- Name: convnext-large_upernet_8xb2-amp-160k_ade20k-640x640 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 53.16 + mIoU(ms+flip): 53.38 + Config: configs/convnext/convnext-large_upernet_8xb2-amp-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ConvNeXt-L + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 12.08 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_large_fp16_640x640_160k_ade20k/upernet_convnext_large_fp16_640x640_160k_ade20k_20220226_040532-e57aa54d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_large_fp16_640x640_160k_ade20k/upernet_convnext_large_fp16_640x640_160k_ade20k_20220226_040532.log.json + Paper: + Title: A ConvNet for the 2020s + URL: https://arxiv.org/abs/2201.03545 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/convnext.py#L133 + Framework: PyTorch +- Name: convnext-xlarge_upernet_8xb2-amp-160k_ade20k-640x640 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 53.58 + mIoU(ms+flip): 54.11 + Config: configs/convnext/convnext-xlarge_upernet_8xb2-amp-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ConvNeXt-XL + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 26.16 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_xlarge_fp16_640x640_160k_ade20k/upernet_convnext_xlarge_fp16_640x640_160k_ade20k_20220226_080344-95fc38c2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/convnext/upernet_convnext_xlarge_fp16_640x640_160k_ade20k/upernet_convnext_xlarge_fp16_640x640_160k_ade20k_20220226_080344.log.json + Paper: + Title: A ConvNet for the 2020s + URL: https://arxiv.org/abs/2201.03545 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/convnext.py#L133 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/danet/README.md b/Seg_All_In_One_MMSeg/configs/danet/README.md new file mode 100644 index 0000000..90194f3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/README.md @@ -0,0 +1,67 @@ +# DANet + +> [Dual Attention Network for Scene Segmentation](https://arxiv.org/abs/1809.02983) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +In this paper, we address the scene segmentation task by capturing rich contextual dependencies based on the selfattention mechanism. Unlike previous works that capture contexts by multi-scale features fusion, we propose a Dual Attention Networks (DANet) to adaptively integrate local features with their global dependencies. Specifically, we append two types of attention modules on top of traditional dilated FCN, which model the semantic interdependencies in spatial and channel dimensions respectively. The position attention module selectively aggregates the features at each position by a weighted sum of the features at all positions. Similar features would be related to each other regardless of their distances. Meanwhile, the channel attention module selectively emphasizes interdependent channel maps by integrating associated features among all channel maps. We sum the outputs of the two attention modules to further improve feature representation which contributes to more precise segmentation results. We achieve new state-of-the-art segmentation performance on three challenging scene segmentation datasets, i.e., Cityscapes, PASCAL Context and COCO Stuff dataset. In particular, a Mean IoU score of 81.5% on Cityscapes test set is achieved without using coarse data. We make the code and trained model publicly available at [this https URL](https://github.com/junfu1115/DANet). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DANet | R-50-D8 | 512x1024 | 40000 | 7.4 | 2.66 | V100 | 78.74 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x1024_40k_cityscapes/danet_r50-d8_512x1024_40k_cityscapes_20200605_191324-c0dbfa5f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x1024_40k_cityscapes/danet_r50-d8_512x1024_40k_cityscapes_20200605_191324.log.json) | +| DANet | R-101-D8 | 512x1024 | 40000 | 10.9 | 1.99 | V100 | 80.52 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x1024_40k_cityscapes/danet_r101-d8_512x1024_40k_cityscapes_20200605_200831-c57a7157.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x1024_40k_cityscapes/danet_r101-d8_512x1024_40k_cityscapes_20200605_200831.log.json) | +| DANet | R-50-D8 | 769x769 | 40000 | 8.8 | 1.56 | V100 | 78.88 | 80.62 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_769x769_40k_cityscapes/danet_r50-d8_769x769_40k_cityscapes_20200530_025703-76681c60.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_769x769_40k_cityscapes/danet_r50-d8_769x769_40k_cityscapes_20200530_025703.log.json) | +| DANet | R-101-D8 | 769x769 | 40000 | 12.8 | 1.07 | V100 | 79.88 | 81.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_769x769_40k_cityscapes/danet_r101-d8_769x769_40k_cityscapes_20200530_025717-dcb7fd4e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_769x769_40k_cityscapes/danet_r101-d8_769x769_40k_cityscapes_20200530_025717.log.json) | +| DANet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 79.34 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x1024_80k_cityscapes/danet_r50-d8_512x1024_80k_cityscapes_20200607_133029-2bfa2293.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x1024_80k_cityscapes/danet_r50-d8_512x1024_80k_cityscapes_20200607_133029.log.json) | +| DANet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 80.41 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x1024_80k_cityscapes/danet_r101-d8_512x1024_80k_cityscapes_20200607_132918-955e6350.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x1024_80k_cityscapes/danet_r101-d8_512x1024_80k_cityscapes_20200607_132918.log.json) | +| DANet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.27 | 80.96 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_769x769_80k_cityscapes/danet_r50-d8_769x769_80k_cityscapes_20200607_132954-495689b4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_769x769_80k_cityscapes/danet_r50-d8_769x769_80k_cityscapes_20200607_132954.log.json) | +| DANet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 80.47 | 82.02 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_769x769_80k_cityscapes/danet_r101-d8_769x769_80k_cityscapes_20200607_132918-f3a929e7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_769x769_80k_cityscapes/danet_r101-d8_769x769_80k_cityscapes_20200607_132918.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DANet | R-50-D8 | 512x512 | 80000 | 11.5 | 21.20 | V100 | 41.66 | 42.90 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_80k_ade20k/danet_r50-d8_512x512_80k_ade20k_20200615_015125-edb18e08.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_80k_ade20k/danet_r50-d8_512x512_80k_ade20k_20200615_015125.log.json) | +| DANet | R-101-D8 | 512x512 | 80000 | 15 | 14.18 | V100 | 43.64 | 45.19 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_80k_ade20k/danet_r101-d8_512x512_80k_ade20k_20200615_015126-d0357c73.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_80k_ade20k/danet_r101-d8_512x512_80k_ade20k_20200615_015126.log.json) | +| DANet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 42.45 | 43.25 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_160k_ade20k/danet_r50-d8_512x512_160k_ade20k_20200616_082340-9cb35dcd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_160k_ade20k/danet_r50-d8_512x512_160k_ade20k_20200616_082340.log.json) | +| DANet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 44.17 | 45.02 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_160k_ade20k/danet_r101-d8_512x512_160k_ade20k_20200616_082348-23bf12f9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_160k_ade20k/danet_r101-d8_512x512_160k_ade20k_20200616_082348.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DANet | R-50-D8 | 512x512 | 20000 | 6.5 | 20.94 | V100 | 74.45 | 75.69 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_20k_voc12aug/danet_r50-d8_512x512_20k_voc12aug_20200618_070026-9e9e3ab3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_20k_voc12aug/danet_r50-d8_512x512_20k_voc12aug_20200618_070026.log.json) | +| DANet | R-101-D8 | 512x512 | 20000 | 9.9 | 13.76 | V100 | 76.02 | 77.23 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_20k_voc12aug/danet_r101-d8_512x512_20k_voc12aug_20200618_070026-d48d23b2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_20k_voc12aug/danet_r101-d8_512x512_20k_voc12aug_20200618_070026.log.json) | +| DANet | R-50-D8 | 512x512 | 40000 | - | - | V100 | 76.37 | 77.29 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_40k_voc12aug/danet_r50-d8_512x512_40k_voc12aug_20200613_235526-426e3a64.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_40k_voc12aug/danet_r50-d8_512x512_40k_voc12aug_20200613_235526.log.json) | +| DANet | R-101-D8 | 512x512 | 40000 | - | - | V100 | 76.51 | 77.32 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet/danet_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_40k_voc12aug/danet_r101-d8_512x512_40k_voc12aug_20200613_223031-788e232a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_40k_voc12aug/danet_r101-d8_512x512_40k_voc12aug_20200613_223031.log.json) | + +## Citation + +```bibtex +@article{fu2018dual, + title={Dual Attention Network for Scene Segmentation}, + author={Jun Fu, Jing Liu, Haijie Tian, Yong Li, Yongjun Bao, Zhiwei Fang,and Hanqing Lu}, + booktitle={The IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..4602f33 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './danet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..a08c18e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './danet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..98b1c64 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './danet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..9affe30 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './danet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..0079ad6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './danet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..4844451 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './danet_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..2f2df7a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './danet_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..dd75bc1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './danet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..3bc2a77 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..3a01fb9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..95d5df0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..4255716 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..a8f082d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..fab574f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..148fa39 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..efbd908 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/danet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/danet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/danet/metafile.yaml new file mode 100644 index 0000000..daff925 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/metafile.yaml @@ -0,0 +1,387 @@ +Collections: +- Name: DANet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + README: configs/danet/README.md + Frameworks: + - PyTorch +Models: +- Name: danet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.74 + Config: configs/danet/danet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DANet + Training Resources: 4x V100 GPUS + Memory (GB): 7.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x1024_40k_cityscapes/danet_r50-d8_512x1024_40k_cityscapes_20200605_191324-c0dbfa5f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x1024_40k_cityscapes/danet_r50-d8_512x1024_40k_cityscapes_20200605_191324.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.52 + Config: configs/danet/danet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DANet + Training Resources: 4x V100 GPUS + Memory (GB): 10.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x1024_40k_cityscapes/danet_r101-d8_512x1024_40k_cityscapes_20200605_200831-c57a7157.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x1024_40k_cityscapes/danet_r101-d8_512x1024_40k_cityscapes_20200605_200831.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.88 + mIoU(ms+flip): 80.62 + Config: configs/danet/danet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DANet + Training Resources: 4x V100 GPUS + Memory (GB): 8.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_769x769_40k_cityscapes/danet_r50-d8_769x769_40k_cityscapes_20200530_025703-76681c60.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_769x769_40k_cityscapes/danet_r50-d8_769x769_40k_cityscapes_20200530_025703.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.88 + mIoU(ms+flip): 81.47 + Config: configs/danet/danet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DANet + Training Resources: 4x V100 GPUS + Memory (GB): 12.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_769x769_40k_cityscapes/danet_r101-d8_769x769_40k_cityscapes_20200530_025717-dcb7fd4e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_769x769_40k_cityscapes/danet_r101-d8_769x769_40k_cityscapes_20200530_025717.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.34 + Config: configs/danet/danet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x1024_80k_cityscapes/danet_r50-d8_512x1024_80k_cityscapes_20200607_133029-2bfa2293.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x1024_80k_cityscapes/danet_r50-d8_512x1024_80k_cityscapes_20200607_133029.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.41 + Config: configs/danet/danet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x1024_80k_cityscapes/danet_r101-d8_512x1024_80k_cityscapes_20200607_132918-955e6350.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x1024_80k_cityscapes/danet_r101-d8_512x1024_80k_cityscapes_20200607_132918.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.27 + mIoU(ms+flip): 80.96 + Config: configs/danet/danet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_769x769_80k_cityscapes/danet_r50-d8_769x769_80k_cityscapes_20200607_132954-495689b4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_769x769_80k_cityscapes/danet_r50-d8_769x769_80k_cityscapes_20200607_132954.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.47 + mIoU(ms+flip): 82.02 + Config: configs/danet/danet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_769x769_80k_cityscapes/danet_r101-d8_769x769_80k_cityscapes_20200607_132918-f3a929e7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_769x769_80k_cityscapes/danet_r101-d8_769x769_80k_cityscapes_20200607_132918.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.66 + mIoU(ms+flip): 42.9 + Config: configs/danet/danet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DANet + Training Resources: 4x V100 GPUS + Memory (GB): 11.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_80k_ade20k/danet_r50-d8_512x512_80k_ade20k_20200615_015125-edb18e08.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_80k_ade20k/danet_r50-d8_512x512_80k_ade20k_20200615_015125.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.64 + mIoU(ms+flip): 45.19 + Config: configs/danet/danet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DANet + Training Resources: 4x V100 GPUS + Memory (GB): 15.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_80k_ade20k/danet_r101-d8_512x512_80k_ade20k_20200615_015126-d0357c73.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_80k_ade20k/danet_r101-d8_512x512_80k_ade20k_20200615_015126.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.45 + mIoU(ms+flip): 43.25 + Config: configs/danet/danet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_160k_ade20k/danet_r50-d8_512x512_160k_ade20k_20200616_082340-9cb35dcd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_160k_ade20k/danet_r50-d8_512x512_160k_ade20k_20200616_082340.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.17 + mIoU(ms+flip): 45.02 + Config: configs/danet/danet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_160k_ade20k/danet_r101-d8_512x512_160k_ade20k_20200616_082348-23bf12f9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_160k_ade20k/danet_r101-d8_512x512_160k_ade20k_20200616_082348.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 74.45 + mIoU(ms+flip): 75.69 + Config: configs/danet/danet_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - DANet + Training Resources: 4x V100 GPUS + Memory (GB): 6.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_20k_voc12aug/danet_r50-d8_512x512_20k_voc12aug_20200618_070026-9e9e3ab3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_20k_voc12aug/danet_r50-d8_512x512_20k_voc12aug_20200618_070026.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.02 + mIoU(ms+flip): 77.23 + Config: configs/danet/danet_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - DANet + Training Resources: 4x V100 GPUS + Memory (GB): 9.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_20k_voc12aug/danet_r101-d8_512x512_20k_voc12aug_20200618_070026-d48d23b2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_20k_voc12aug/danet_r101-d8_512x512_20k_voc12aug_20200618_070026.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.37 + mIoU(ms+flip): 77.29 + Config: configs/danet/danet_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - DANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_40k_voc12aug/danet_r50-d8_512x512_40k_voc12aug_20200613_235526-426e3a64.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r50-d8_512x512_40k_voc12aug/danet_r50-d8_512x512_40k_voc12aug_20200613_235526.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch +- Name: danet_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: DANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.51 + mIoU(ms+flip): 77.32 + Config: configs/danet/danet_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - DANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_40k_voc12aug/danet_r101-d8_512x512_40k_voc12aug_20200613_223031-788e232a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/danet/danet_r101-d8_512x512_40k_voc12aug/danet_r101-d8_512x512_40k_voc12aug_20200613_223031.log.json + Paper: + Title: Dual Attention Network for Scene Segmentation + URL: https://arxiv.org/abs/1809.02983 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/da_head.py#L76 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/danet/my_danet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/danet/my_danet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..c0b832d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/danet/my_danet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,103 @@ +_base_ = [ + '../_base_/models/danet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/README.md b/Seg_All_In_One_MMSeg/configs/ddrnet/README.md new file mode 100644 index 0000000..ccbfcdf --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/README.md @@ -0,0 +1,46 @@ +# DDRNet + +> [Deep Dual-resolution Networks for Real-time and Accurate Semantic Segmentation of Road Scenes](http://arxiv.org/abs/2101.06085) + +## Introduction + + + +Official Repo + +## Abstract + + + +Semantic segmentation is a key technology for autonomous vehicles to understand the surrounding scenes. The appealing performances of contemporary models usually come at the expense of heavy computations and lengthy inference time, which is intolerable for self-driving. Using light-weight architectures (encoder-decoder or two-pathway) or reasoning on low-resolution images, recent methods realize very fast scene parsing, even running at more than 100 FPS on a single 1080Ti GPU. However, there is still a significant gap in performance between these real-time methods and the models based on dilation backbones. To tackle this problem, we proposed a family of efficient backbones specially designed for real-time semantic segmentation. The proposed deep dual-resolution networks (DDRNets) are composed of two deep branches between which multiple bilateral fusions are performed. Additionally, we design a new contextual information extractor named Deep Aggregation Pyramid Pooling Module (DAPPM) to enlarge effective receptive fields and fuse multi-scale context based on low-resolution feature maps. Our method achieves a new state-of-the-art trade-off between accuracy and speed on both Cityscapes and CamVid dataset. In particular, on a single 2080Ti GPU, DDRNet-23-slim yields 77.4% mIoU at 102 FPS on Cityscapes test set and 74.7% mIoU at 230 FPS on CamVid test set. With widely used test augmentation, our method is superior to most state-of-the-art models and requires much less computation. Codes and trained models are available online. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------- | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DDRNet | DDRNet23-slim | 1024x1024 | 120000 | 1.70 | 85.85 | A100 | 77.84 | 80.15 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024_20230426_145312-6a5e5174.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024_20230426_145312.json) | +| DDRNet | DDRNet23 | 1024x1024 | 120000 | 7.26 | 33.41 | A100 | 79.99 | 81.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024_20230425_162633-81601db0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024_20230425_162633.json) | + +## Notes + +The pretrained weights in config files are converted from [the official repo](https://github.com/ydhongHIT/DDRNet#pretrained-models). + +## Citation + +```bibtex +@article{pan2022deep, + title={Deep Dual-Resolution Networks for Real-Time and Accurate Semantic Segmentation of Traffic Scenes}, + author={Pan, Huihui and Hong, Yuanduo and Sun, Weichao and Jia, Yisong}, + journal={IEEE Transactions on Intelligent Transportation Systems}, + year={2022}, + publisher={IEEE} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024.py new file mode 100644 index 0000000..65b0ead --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024.py @@ -0,0 +1,93 @@ +_base_ = [ + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', +] + +# The class_weight is borrowed from https://github.com/openseg-group/OCNet.pytorch/issues/14 # noqa +# Licensed under the MIT License +class_weight = [ + 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, 1.0489, 0.8786, + 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, 1.0865, 1.0955, 1.0865, 1.1529, + 1.0507 +] +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/pretrain/ddrnet23s-in1kpre_3rdparty-1ccac5b1.pth' # noqa +crop_size = (1024, 1024) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=crop_size, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='DDRNet', + in_channels=3, + channels=32, + ppm_channels=128, + norm_cfg=norm_cfg, + align_corners=False, + init_cfg=dict(type='Pretrained', checkpoint=checkpoint)), + decode_head=dict( + type='DDRHead', + in_channels=32 * 4, + channels=64, + dropout_ratio=0., + num_classes=19, + align_corners=False, + norm_cfg=norm_cfg, + loss_decode=[ + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + class_weight=class_weight, + loss_weight=1.0), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + class_weight=class_weight, + loss_weight=0.4), + ]), + + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +train_dataloader = dict(batch_size=6, num_workers=4) + +iters = 120000 +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0, + power=0.9, + begin=0, + end=iters, + by_epoch=False) +] + +# training schedule for 120k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=iters, val_interval=iters // 10) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', by_epoch=False, interval=iters // 10), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) + +randomness = dict(seed=304) diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024.py new file mode 100644 index 0000000..444efe2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024.py @@ -0,0 +1,93 @@ +_base_ = [ + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', +] + +# The class_weight is borrowed from https://github.com/openseg-group/OCNet.pytorch/issues/14 # noqa +# Licensed under the MIT License +class_weight = [ + 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, 1.0489, 0.8786, + 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, 1.0865, 1.0955, 1.0865, 1.1529, + 1.0507 +] +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/pretrain/ddrnet23-in1kpre_3rdparty-9ca29f62.pth' # noqa +crop_size = (1024, 1024) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=crop_size, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='DDRNet', + in_channels=3, + channels=64, + ppm_channels=128, + norm_cfg=norm_cfg, + align_corners=False, + init_cfg=dict(type='Pretrained', checkpoint=checkpoint)), + decode_head=dict( + type='DDRHead', + in_channels=64 * 4, + channels=128, + dropout_ratio=0., + num_classes=19, + align_corners=False, + norm_cfg=norm_cfg, + loss_decode=[ + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + class_weight=class_weight, + loss_weight=1.0), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + class_weight=class_weight, + loss_weight=0.4), + ]), + + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +train_dataloader = dict(batch_size=6, num_workers=4) + +iters = 120000 +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0, + power=0.9, + begin=0, + end=iters, + by_epoch=False) +] + +# training schedule for 120k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=iters, val_interval=iters // 10) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', by_epoch=False, interval=iters // 10), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) + +randomness = dict(seed=304) diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/ddrnet/metafile.yaml new file mode 100644 index 0000000..0707470 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/metafile.yaml @@ -0,0 +1,64 @@ +Collections: +- Name: DDRNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: Deep Dual-resolution Networks for Real-time and Accurate Semantic Segmentation + of Road Scenes + URL: http://arxiv.org/abs/2101.06085 + README: configs/ddrnet/README.md + Frameworks: + - PyTorch +Models: +- Name: ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024 + In Collection: DDRNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.84 + mIoU(ms+flip): 80.15 + Config: configs/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 12 + Architecture: + - DDRNet23-slim + - DDRNet + Training Resources: 2x A100 GPUS + Memory (GB): 1.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024_20230426_145312-6a5e5174.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024/ddrnet_23-slim_in1k-pre_2xb6-120k_cityscapes-1024x1024_20230426_145312.json + Paper: + Title: Deep Dual-resolution Networks for Real-time and Accurate Semantic Segmentation + of Road Scenes + URL: http://arxiv.org/abs/2101.06085 + Code: '' + Framework: PyTorch +- Name: ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024 + In Collection: DDRNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.99 + mIoU(ms+flip): 81.71 + Config: configs/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 12 + Architecture: + - DDRNet23 + - DDRNet + Training Resources: 2x A100 GPUS + Memory (GB): 7.26 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024_20230425_162633-81601db0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ddrnet/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024/ddrnet_23_in1k-pre_2xb6-120k_cityscapes-1024x1024_20230425_162633.json + Paper: + Title: Deep Dual-resolution Networks for Real-time and Accurate Semantic Segmentation + of Road Scenes + URL: http://arxiv.org/abs/2101.06085 + Code: '' + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..955564e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23.pth', + ), + ), + decode_head=dict( + in_channels=256, + channels=128, + num_classes=10, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..6092096 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23.pth', + ), + ), + decode_head=dict( + in_channels=256, + channels=128, + num_classes=13, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..0dbcdcb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23.pth', + ), + ), + decode_head=dict( + in_channels=256, + channels=128, + num_classes=11, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..1f91f66 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23.pth', + ), + ), + decode_head=dict( + in_channels=256, + channels=128, + num_classes=8, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..ab85816 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_normal_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23.pth', + ), + ), + decode_head=dict( + in_channels=256, + channels=128, + num_classes=8, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..34afd0a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23-s.pth', + ), + ), + decode_head=dict( + in_channels=128, + channels=64, + num_classes=10, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..ddab8a5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23-s.pth', + ), + ), + decode_head=dict( + in_channels=128, + channels=64, + num_classes=13, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..24fde44 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23-s.pth', + ), + ), + decode_head=dict( + in_channels=128, + channels=64, + num_classes=11, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..a8e72ff --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23-s.pth', + ), + ), + decode_head=dict( + in_channels=128, + channels=64, + num_classes=8, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..68228bb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ddrnet/my_ddrnet_small_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,89 @@ +_base_ = [ + '../_base_/models/ddrnet.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/ddrnet23-s.pth', + ), + ), + decode_head=dict( + in_channels=128, + channels=64, + num_classes=8, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/README.md b/Seg_All_In_One_MMSeg/configs/deeplabv3/README.md new file mode 100644 index 0000000..df50b7f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/README.md @@ -0,0 +1,118 @@ +# DeepLabV3 + +> [Rethinking atrous convolution for semantic image segmentation](https://arxiv.org/abs/1706.05587) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +In this work, we revisit atrous convolution, a powerful tool to explicitly adjust filter's field-of-view as well as control the resolution of feature responses computed by Deep Convolutional Neural Networks, in the application of semantic image segmentation. To handle the problem of segmenting objects at multiple scales, we design modules which employ atrous convolution in cascade or in parallel to capture multi-scale context by adopting multiple atrous rates. Furthermore, we propose to augment our previously proposed Atrous Spatial Pyramid Pooling module, which probes convolutional features at multiple scales, with image-level features encoding global context and further boost performance. We also elaborate on implementation details and share our experience on training our system. The proposed \`DeepLabv3' system significantly improves over our previous DeepLab versions without DenseCRF post-processing and attains comparable performance with other state-of-art models on the PASCAL VOC 2012 semantic image segmentation benchmark. + + + +
+DEEPLABv3_ResNet-D8 +DEEPLABv3_ResNet-D8 model structure +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------------- | --------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| DeepLabV3 | R-50-D8 | 512x1024 | 40000 | 6.1 | 2.57 | V100 | 79.09 | 80.45 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_40k_cityscapes/deeplabv3_r50-d8_512x1024_40k_cityscapes_20200605_022449-acadc2f8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_40k_cityscapes/deeplabv3_r50-d8_512x1024_40k_cityscapes_20200605_022449.log.json) | +| DeepLabV3 | R-101-D8 | 512x1024 | 40000 | 9.6 | 1.92 | V100 | 77.12 | 79.61 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x1024_40k_cityscapes/deeplabv3_r101-d8_512x1024_40k_cityscapes_20200605_012241-7fd3f799.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x1024_40k_cityscapes/deeplabv3_r101-d8_512x1024_40k_cityscapes_20200605_012241.log.json) | +| DeepLabV3 | R-50-D8 | 769x769 | 40000 | 6.9 | 1.11 | V100 | 78.58 | 79.89 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_769x769_40k_cityscapes/deeplabv3_r50-d8_769x769_40k_cityscapes_20200606_113723-7eda553c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_769x769_40k_cityscapes/deeplabv3_r50-d8_769x769_40k_cityscapes_20200606_113723.log.json) | +| DeepLabV3 | R-101-D8 | 769x769 | 40000 | 10.9 | 0.83 | V100 | 79.27 | 80.11 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_769x769_40k_cityscapes/deeplabv3_r101-d8_769x769_40k_cityscapes_20200606_113809-c64f889f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_769x769_40k_cityscapes/deeplabv3_r101-d8_769x769_40k_cityscapes_20200606_113809.log.json) | +| DeepLabV3 | R-18-D8 | 512x1024 | 80000 | 1.7 | 13.78 | V100 | 76.70 | 78.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18-d8_512x1024_80k_cityscapes/deeplabv3_r18-d8_512x1024_80k_cityscapes_20201225_021506-23dffbe2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18-d8_512x1024_80k_cityscapes/deeplabv3_r18-d8_512x1024_80k_cityscapes-20201225_021506.log.json) | +| DeepLabV3 | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 79.32 | 80.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_80k_cityscapes/deeplabv3_r50-d8_512x1024_80k_cityscapes_20200606_113404-b92cfdd4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_80k_cityscapes/deeplabv3_r50-d8_512x1024_80k_cityscapes_20200606_113404.log.json) | +| DeepLabV3 | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 80.20 | 81.21 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x1024_80k_cityscapes/deeplabv3_r101-d8_512x1024_80k_cityscapes_20200606_113503-9e428899.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x1024_80k_cityscapes/deeplabv3_r101-d8_512x1024_80k_cityscapes_20200606_113503.log.json) | +| DeepLabV3 (FP16) | R-101-D8 | 512x1024 | 80000 | 5.75 | 3.86 | V100 | 80.48 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_fp16_512x1024_80k_cityscapes/deeplabv3_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230920-774d9cec.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_fp16_512x1024_80k_cityscapes/deeplabv3_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230920.log.json) | +| DeepLabV3 | R-18-D8 | 769x769 | 80000 | 1.9 | 5.55 | V100 | 76.60 | 78.26 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18-d8_769x769_80k_cityscapes/deeplabv3_r18-d8_769x769_80k_cityscapes_20201225_021506-6452126a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18-d8_769x769_80k_cityscapes/deeplabv3_r18-d8_769x769_80k_cityscapes-20201225_021506.log.json) | +| DeepLabV3 | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.89 | 81.06 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_769x769_80k_cityscapes/deeplabv3_r50-d8_769x769_80k_cityscapes_20200606_221338-788d6228.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_769x769_80k_cityscapes/deeplabv3_r50-d8_769x769_80k_cityscapes_20200606_221338.log.json) | +| DeepLabV3 | R-101-D8 | 769x769 | 80000 | - | - | V100 | 79.67 | 80.81 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_769x769_80k_cityscapes/deeplabv3_r101-d8_769x769_80k_cityscapes_20200607_013353-60e95418.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_769x769_80k_cityscapes/deeplabv3_r101-d8_769x769_80k_cityscapes_20200607_013353.log.json) | +| DeepLabV3 | R-101-D16-MG124 | 512x1024 | 40000 | 4.7 | 6.96 | V100 | 76.71 | 78.63 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d16-mg124_512x1024_40k_cityscapes/deeplabv3_r101-d16-mg124_512x1024_40k_cityscapes_20200908_005644-67b0c992.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d16-mg124_512x1024_40k_cityscapes/deeplabv3_r101-d16-mg124_512x1024_40k_cityscapes-20200908_005644.log.json) | +| DeepLabV3 | R-101-D16-MG124 | 512x1024 | 80000 | - | - | V100 | 78.36 | 79.84 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d16-mg124_512x1024_80k_cityscapes/deeplabv3_r101-d16-mg124_512x1024_80k_cityscapes_20200908_005644-57bb8425.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d16-mg124_512x1024_80k_cityscapes/deeplabv3_r101-d16-mg124_512x1024_80k_cityscapes-20200908_005644.log.json) | +| DeepLabV3 | R-18b-D8 | 512x1024 | 80000 | 1.6 | 13.93 | V100 | 76.26 | 77.88 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18b-d8_512x1024_80k_cityscapes/deeplabv3_r18b-d8_512x1024_80k_cityscapes_20201225_094144-46040cef.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18b-d8_512x1024_80k_cityscapes/deeplabv3_r18b-d8_512x1024_80k_cityscapes-20201225_094144.log.json) | +| DeepLabV3 | R-50b-D8 | 512x1024 | 80000 | 6.0 | 2.74 | V100 | 79.63 | 80.98 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50b-d8_512x1024_80k_cityscapes/deeplabv3_r50b-d8_512x1024_80k_cityscapes_20201225_155148-ec368954.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50b-d8_512x1024_80k_cityscapes/deeplabv3_r50b-d8_512x1024_80k_cityscapes-20201225_155148.log.json) | +| DeepLabV3 | R-101b-D8 | 512x1024 | 80000 | 9.5 | 1.81 | V100 | 80.01 | 81.21 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101b-d8_512x1024_80k_cityscapes/deeplabv3_r101b-d8_512x1024_80k_cityscapes_20201226_171821-8fd49503.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101b-d8_512x1024_80k_cityscapes/deeplabv3_r101b-d8_512x1024_80k_cityscapes-20201226_171821.log.json) | +| DeepLabV3 | R-18b-D8 | 769x769 | 80000 | 1.8 | 5.79 | V100 | 75.63 | 77.51 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18b-d8_769x769_80k_cityscapes/deeplabv3_r18b-d8_769x769_80k_cityscapes_20201225_094144-fdc985d9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18b-d8_769x769_80k_cityscapes/deeplabv3_r18b-d8_769x769_80k_cityscapes-20201225_094144.log.json) | +| DeepLabV3 | R-50b-D8 | 769x769 | 80000 | 6.8 | 1.16 | V100 | 78.80 | 80.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50b-d8_769x769_80k_cityscapes/deeplabv3_r50b-d8_769x769_80k_cityscapes_20201225_155404-87fb0cf4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50b-d8_769x769_80k_cityscapes/deeplabv3_r50b-d8_769x769_80k_cityscapes-20201225_155404.log.json) | +| DeepLabV3 | R-101b-D8 | 769x769 | 80000 | 10.7 | 0.82 | V100 | 79.41 | 80.73 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101b-d8_769x769_80k_cityscapes/deeplabv3_r101b-d8_769x769_80k_cityscapes_20201226_190843-9142ee57.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101b-d8_769x769_80k_cityscapes/deeplabv3_r101b-d8_769x769_80k_cityscapes-20201226_190843.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3 | R-50-D8 | 512x512 | 80000 | 8.9 | 14.76 | V100 | 42.42 | 43.28 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_80k_ade20k/deeplabv3_r50-d8_512x512_80k_ade20k_20200614_185028-0bb3f844.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_80k_ade20k/deeplabv3_r50-d8_512x512_80k_ade20k_20200614_185028.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 80000 | 12.4 | 10.14 | V100 | 44.08 | 45.19 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_80k_ade20k/deeplabv3_r101-d8_512x512_80k_ade20k_20200615_021256-d89c7fa4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_80k_ade20k/deeplabv3_r101-d8_512x512_80k_ade20k_20200615_021256.log.json) | +| DeepLabV3 | R-50-D8 | 512x512 | 160000 | - | - | V100 | 42.66 | 44.09 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_160k_ade20k/deeplabv3_r50-d8_512x512_160k_ade20k_20200615_123227-5d0ee427.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_160k_ade20k/deeplabv3_r50-d8_512x512_160k_ade20k_20200615_123227.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 160000 | - | - | V100 | 45.00 | 46.66 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_160k_ade20k/deeplabv3_r101-d8_512x512_160k_ade20k_20200615_105816-b1f72b3b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_160k_ade20k/deeplabv3_r101-d8_512x512_160k_ade20k_20200615_105816.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3 | R-50-D8 | 512x512 | 20000 | 6.1 | 13.88 | V100 | 76.17 | 77.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_20k_voc12aug/deeplabv3_r50-d8_512x512_20k_voc12aug_20200617_010906-596905ef.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_20k_voc12aug/deeplabv3_r50-d8_512x512_20k_voc12aug_20200617_010906.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 20000 | 9.6 | 9.81 | V100 | 78.70 | 79.95 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_20k_voc12aug/deeplabv3_r101-d8_512x512_20k_voc12aug_20200617_010932-8d13832f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_20k_voc12aug/deeplabv3_r101-d8_512x512_20k_voc12aug_20200617_010932.log.json) | +| DeepLabV3 | R-50-D8 | 512x512 | 40000 | - | - | V100 | 77.68 | 78.78 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_40k_voc12aug/deeplabv3_r50-d8_512x512_40k_voc12aug_20200613_161546-2ae96e7e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_40k_voc12aug/deeplabv3_r50-d8_512x512_40k_voc12aug_20200613_161546.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 40000 | - | - | V100 | 77.92 | 79.18 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_40k_voc12aug/deeplabv3_r101-d8_512x512_40k_voc12aug_20200613_161432-0017d784.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_40k_voc12aug/deeplabv3_r101-d8_512x512_40k_voc12aug_20200613_161432.log.json) | + +### Pascal Context + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3 | R-101-D8 | 480x480 | 40000 | 9.2 | 7.09 | V100 | 46.55 | 47.81 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_40k_pascal_context/deeplabv3_r101-d8_480x480_40k_pascal_context_20200911_204118-1aa27336.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_40k_pascal_context/deeplabv3_r101-d8_480x480_40k_pascal_context-20200911_204118.log.json) | +| DeepLabV3 | R-101-D8 | 480x480 | 80000 | - | - | V100 | 46.42 | 47.53 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_80k_pascal_context/deeplabv3_r101-d8_480x480_80k_pascal_context_20200911_170155-2a21fff3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_80k_pascal_context/deeplabv3_r101-d8_480x480_80k_pascal_context-20200911_170155.log.json) | + +### Pascal Context 59 + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3 | R-101-D8 | 480x480 | 40000 | - | - | V100 | 52.61 | 54.28 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_40k_pascal_context_59/deeplabv3_r101-d8_480x480_40k_pascal_context_59_20210416_110332-cb08ea46.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_40k_pascal_context_59/deeplabv3_r101-d8_480x480_40k_pascal_context_59-20210416_110332.log.json) | +| DeepLabV3 | R-101-D8 | 480x480 | 80000 | - | - | V100 | 52.46 | 54.09 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_80k_pascal_context_59/deeplabv3_r101-d8_480x480_80k_pascal_context_59_20210416_113002-26303993.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_80k_pascal_context_59/deeplabv3_r101-d8_480x480_80k_pascal_context_59-20210416_113002.log.json) | + +### COCO-Stuff 10k + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3 | R-50-D8 | 512x512 | 20000 | 9.6 | 10.8 | V100 | 34.66 | 36.08 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_coco-stuff10k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_20k_coco-stuff10k/deeplabv3_r50-d8_512x512_4x4_20k_coco-stuff10k_20210821_043025-b35f789d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_20k_coco-stuff10k/deeplabv3_r50-d8_512x512_4x4_20k_coco-stuff10k_20210821_043025.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 20000 | 13.2 | 8.7 | V100 | 37.30 | 38.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_coco-stuff10k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_20k_coco-stuff10k/deeplabv3_r101-d8_512x512_4x4_20k_coco-stuff10k_20210821_043025-c49752cb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_20k_coco-stuff10k/deeplabv3_r101-d8_512x512_4x4_20k_coco-stuff10k_20210821_043025.log.json) | +| DeepLabV3 | R-50-D8 | 512x512 | 40000 | - | - | V100 | 35.73 | 37.09 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_coco-stuff10k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_40k_coco-stuff10k/deeplabv3_r50-d8_512x512_4x4_40k_coco-stuff10k_20210821_043305-dc76f3ff.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_40k_coco-stuff10k/deeplabv3_r50-d8_512x512_4x4_40k_coco-stuff10k_20210821_043305.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 40000 | - | - | V100 | 37.81 | 38.80 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_coco-stuff10k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_40k_coco-stuff10k/deeplabv3_r101-d8_512x512_4x4_40k_coco-stuff10k_20210821_043305-636cb433.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_40k_coco-stuff10k/deeplabv3_r101-d8_512x512_4x4_40k_coco-stuff10k_20210821_043305.log.json) | + +### COCO-Stuff 164k + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3 | R-50-D8 | 512x512 | 80000 | 9.6 | 10.8 | V100 | 39.38 | 40.03 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_80k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_80k_coco-stuff164k_20210709_163016-88675c24.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_80k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_80k_coco-stuff164k_20210709_163016.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 80000 | 13.2 | 8.7 | V100 | 40.87 | 41.50 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_80k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_80k_coco-stuff164k_20210709_201252-13600dc2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_80k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_80k_coco-stuff164k_20210709_201252.log.json) | +| DeepLabV3 | R-50-D8 | 512x512 | 160000 | - | - | V100 | 41.09 | 41.69 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_160k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_160k_coco-stuff164k_20210709_163016-49f2812b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_160k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_160k_coco-stuff164k_20210709_163016.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 160000 | - | - | V100 | 41.82 | 42.49 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_160k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_160k_coco-stuff164k_20210709_155402-f035acfd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_160k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_160k_coco-stuff164k_20210709_155402.log.json) | +| DeepLabV3 | R-50-D8 | 512x512 | 320000 | - | - | V100 | 41.37 | 42.22 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r50-d8_4xb4-320k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_320k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_320k_coco-stuff164k_20210709_155403-51b21115.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_320k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_320k_coco-stuff164k_20210709_155403.log.json) | +| DeepLabV3 | R-101-D8 | 512x512 | 320000 | - | - | V100 | 42.61 | 43.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3/deeplabv3_r101-d8_4xb4-320k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_320k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_320k_coco-stuff164k_20210709_155402-3cbca14d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_320k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_320k_coco-stuff164k_20210709_155402.log.json) | + +Note: + +- `D-8` here corresponding to the output stride 8 setting for DeepLab series. +- `FP16` means Mixed Precision (FP16) is adopted in training. + +## Citation + +```bibtext +@article{chen2017rethinking, + title={Rethinking atrous convolution for semantic image segmentation}, + author={Chen, Liang-Chieh and Papandreou, George and Schroff, Florian and Adam, Hartwig}, + journal={arXiv preprint arXiv:1706.05587}, + year={2017} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..b9f3c17 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet101_v1c', + backbone=dict( + depth=101, + dilations=(1, 1, 1, 2), + strides=(1, 2, 2, 1), + multi_grid=(1, 2, 4)), + decode_head=dict( + dilations=(1, 6, 12, 18), + sampler=dict(type='OHEMPixelSampler', min_kept=100000))) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..da3a88f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet101_v1c', + backbone=dict( + depth=101, + dilations=(1, 1, 1, 2), + strides=(1, 2, 2, 1), + multi_grid=(1, 2, 4)), + decode_head=dict( + dilations=(1, 6, 12, 18), + sampler=dict(type='OHEMPixelSampler', min_kept=100000))) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..d01803c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..7964b51 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..1d1a620 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..7820546 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..8417416 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = './deeplabv3_r101-d8_4xb2-40k_cityscapes-512x1024.py' +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=optimizer, + loss_scale=512.) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..0ed6eee --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..add0083 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-160k_coco-stuff164k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_coco-stuff10k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_coco-stuff10k-512x512.py new file mode 100644 index 0000000..349cc88 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_coco-stuff10k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-20k_coco-stuff10k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..1c527e0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-320k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-320k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..ea27bed --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-320k_coco-stuff164k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-320k_coco-stuff164k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_coco-stuff10k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_coco-stuff10k-512x512.py new file mode 100644 index 0000000..a43a786 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_coco-stuff10k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-40k_coco-stuff10k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..8879d53 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-40k_pascal-context-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..54671d4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-40k_pascal-context-59-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..1b2635d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..b7bb0b6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..2d4f6f7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_coco-stuff164k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-80k_coco-stuff164k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..9d64ca2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-80k_pascal-context-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..54671d4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb4-40k_pascal-context-59-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..708932d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,4 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..a0f634d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,4 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..bc353bb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..021c98c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,9 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..c747cd7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet18', + backbone=dict(type='ResNet', depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..6506abf --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,9 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet18', + backbone=dict(type='ResNet', depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..4a2a971 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..a52f29e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..1bd29b9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..27f0fc4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..04e15f0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..ba76a59 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_coco-stuff10k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_coco-stuff10k-512x512.py new file mode 100644 index 0000000..d0559c8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_coco-stuff10k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/coco-stuff10k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..c5458d9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-320k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-320k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..c3b4f94 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-320k_coco-stuff164k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_320k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_coco-stuff10k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_coco-stuff10k-512x512.py new file mode 100644 index 0000000..40dbffa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_coco-stuff10k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/coco-stuff10k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..3c4e753 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/pascal_context.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + auxiliary_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..e3b6c36 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + auxiliary_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..8333cc6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..0bcdab5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..519df5a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_coco-stuff164k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..ba8c7de --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/pascal_context.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + auxiliary_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..d34bd89 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + auxiliary_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..818519f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..07a234b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/metafile.yaml b/Seg_All_In_One_MMSeg/configs/deeplabv3/metafile.yaml new file mode 100644 index 0000000..650f7d6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/metafile.yaml @@ -0,0 +1,985 @@ +Collections: +- Name: DeepLabV3 + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + - Pascal Context + - Pascal Context 59 + - COCO-Stuff 10k + - COCO-Stuff 164k + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + README: configs/deeplabv3/README.md + Frameworks: + - PyTorch +Models: +- Name: deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.09 + mIoU(ms+flip): 80.45 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 6.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_40k_cityscapes/deeplabv3_r50-d8_512x1024_40k_cityscapes_20200605_022449-acadc2f8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_40k_cityscapes/deeplabv3_r50-d8_512x1024_40k_cityscapes_20200605_022449.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.12 + mIoU(ms+flip): 79.61 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 9.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x1024_40k_cityscapes/deeplabv3_r101-d8_512x1024_40k_cityscapes_20200605_012241-7fd3f799.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x1024_40k_cityscapes/deeplabv3_r101-d8_512x1024_40k_cityscapes_20200605_012241.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.58 + mIoU(ms+flip): 79.89 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 6.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_769x769_40k_cityscapes/deeplabv3_r50-d8_769x769_40k_cityscapes_20200606_113723-7eda553c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_769x769_40k_cityscapes/deeplabv3_r50-d8_769x769_40k_cityscapes_20200606_113723.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.27 + mIoU(ms+flip): 80.11 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 10.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_769x769_40k_cityscapes/deeplabv3_r101-d8_769x769_40k_cityscapes_20200606_113809-c64f889f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_769x769_40k_cityscapes/deeplabv3_r101-d8_769x769_40k_cityscapes_20200606_113809.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r18-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.7 + mIoU(ms+flip): 78.27 + Config: configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 1.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18-d8_512x1024_80k_cityscapes/deeplabv3_r18-d8_512x1024_80k_cityscapes_20201225_021506-23dffbe2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18-d8_512x1024_80k_cityscapes/deeplabv3_r18-d8_512x1024_80k_cityscapes-20201225_021506.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.32 + mIoU(ms+flip): 80.57 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_80k_cityscapes/deeplabv3_r50-d8_512x1024_80k_cityscapes_20200606_113404-b92cfdd4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_80k_cityscapes/deeplabv3_r50-d8_512x1024_80k_cityscapes_20200606_113404.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.2 + mIoU(ms+flip): 81.21 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x1024_80k_cityscapes/deeplabv3_r101-d8_512x1024_80k_cityscapes_20200606_113503-9e428899.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x1024_80k_cityscapes/deeplabv3_r101-d8_512x1024_80k_cityscapes_20200606_113503.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb2-amp-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.48 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3 + - (FP16) + Training Resources: 4x V100 GPUS + Memory (GB): 5.75 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_fp16_512x1024_80k_cityscapes/deeplabv3_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230920-774d9cec.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_fp16_512x1024_80k_cityscapes/deeplabv3_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230920.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r18-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.6 + mIoU(ms+flip): 78.26 + Config: configs/deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 1.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18-d8_769x769_80k_cityscapes/deeplabv3_r18-d8_769x769_80k_cityscapes_20201225_021506-6452126a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18-d8_769x769_80k_cityscapes/deeplabv3_r18-d8_769x769_80k_cityscapes-20201225_021506.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.89 + mIoU(ms+flip): 81.06 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_769x769_80k_cityscapes/deeplabv3_r50-d8_769x769_80k_cityscapes_20200606_221338-788d6228.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_769x769_80k_cityscapes/deeplabv3_r50-d8_769x769_80k_cityscapes_20200606_221338.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.67 + mIoU(ms+flip): 80.81 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_769x769_80k_cityscapes/deeplabv3_r101-d8_769x769_80k_cityscapes_20200607_013353-60e95418.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_769x769_80k_cityscapes/deeplabv3_r101-d8_769x769_80k_cityscapes_20200607_013353.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d16-mg124_4xb2-40k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.71 + mIoU(ms+flip): 78.63 + Config: configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D16-MG124 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 4.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d16-mg124_512x1024_40k_cityscapes/deeplabv3_r101-d16-mg124_512x1024_40k_cityscapes_20200908_005644-67b0c992.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d16-mg124_512x1024_40k_cityscapes/deeplabv3_r101-d16-mg124_512x1024_40k_cityscapes-20200908_005644.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d16-mg124_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.36 + mIoU(ms+flip): 79.84 + Config: configs/deeplabv3/deeplabv3_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D16-MG124 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d16-mg124_512x1024_80k_cityscapes/deeplabv3_r101-d16-mg124_512x1024_80k_cityscapes_20200908_005644-57bb8425.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d16-mg124_512x1024_80k_cityscapes/deeplabv3_r101-d16-mg124_512x1024_80k_cityscapes-20200908_005644.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r18b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.26 + mIoU(ms+flip): 77.88 + Config: configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18b-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 1.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18b-d8_512x1024_80k_cityscapes/deeplabv3_r18b-d8_512x1024_80k_cityscapes_20201225_094144-46040cef.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18b-d8_512x1024_80k_cityscapes/deeplabv3_r18b-d8_512x1024_80k_cityscapes-20201225_094144.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.63 + mIoU(ms+flip): 80.98 + Config: configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 6.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50b-d8_512x1024_80k_cityscapes/deeplabv3_r50b-d8_512x1024_80k_cityscapes_20201225_155148-ec368954.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50b-d8_512x1024_80k_cityscapes/deeplabv3_r50b-d8_512x1024_80k_cityscapes-20201225_155148.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.01 + mIoU(ms+flip): 81.21 + Config: configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 9.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101b-d8_512x1024_80k_cityscapes/deeplabv3_r101b-d8_512x1024_80k_cityscapes_20201226_171821-8fd49503.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101b-d8_512x1024_80k_cityscapes/deeplabv3_r101b-d8_512x1024_80k_cityscapes-20201226_171821.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r18b-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.63 + mIoU(ms+flip): 77.51 + Config: configs/deeplabv3/deeplabv3_r18b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18b-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 1.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18b-d8_769x769_80k_cityscapes/deeplabv3_r18b-d8_769x769_80k_cityscapes_20201225_094144-fdc985d9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r18b-d8_769x769_80k_cityscapes/deeplabv3_r18b-d8_769x769_80k_cityscapes-20201225_094144.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50b-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.8 + mIoU(ms+flip): 80.27 + Config: configs/deeplabv3/deeplabv3_r50b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 6.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50b-d8_769x769_80k_cityscapes/deeplabv3_r50b-d8_769x769_80k_cityscapes_20201225_155404-87fb0cf4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50b-d8_769x769_80k_cityscapes/deeplabv3_r50b-d8_769x769_80k_cityscapes-20201225_155404.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101b-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.41 + mIoU(ms+flip): 80.73 + Config: configs/deeplabv3/deeplabv3_r101b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 10.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101b-d8_769x769_80k_cityscapes/deeplabv3_r101b-d8_769x769_80k_cityscapes_20201226_190843-9142ee57.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101b-d8_769x769_80k_cityscapes/deeplabv3_r101b-d8_769x769_80k_cityscapes-20201226_190843.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.42 + mIoU(ms+flip): 43.28 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 8.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_80k_ade20k/deeplabv3_r50-d8_512x512_80k_ade20k_20200614_185028-0bb3f844.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_80k_ade20k/deeplabv3_r50-d8_512x512_80k_ade20k_20200614_185028.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.08 + mIoU(ms+flip): 45.19 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 12.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_80k_ade20k/deeplabv3_r101-d8_512x512_80k_ade20k_20200615_021256-d89c7fa4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_80k_ade20k/deeplabv3_r101-d8_512x512_80k_ade20k_20200615_021256.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.66 + mIoU(ms+flip): 44.09 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_160k_ade20k/deeplabv3_r50-d8_512x512_160k_ade20k_20200615_123227-5d0ee427.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_160k_ade20k/deeplabv3_r50-d8_512x512_160k_ade20k_20200615_123227.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.0 + mIoU(ms+flip): 46.66 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_160k_ade20k/deeplabv3_r101-d8_512x512_160k_ade20k_20200615_105816-b1f72b3b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_160k_ade20k/deeplabv3_r101-d8_512x512_160k_ade20k_20200615_105816.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.17 + mIoU(ms+flip): 77.42 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 6.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_20k_voc12aug/deeplabv3_r50-d8_512x512_20k_voc12aug_20200617_010906-596905ef.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_20k_voc12aug/deeplabv3_r50-d8_512x512_20k_voc12aug_20200617_010906.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 78.7 + mIoU(ms+flip): 79.95 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 9.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_20k_voc12aug/deeplabv3_r101-d8_512x512_20k_voc12aug_20200617_010932-8d13832f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_20k_voc12aug/deeplabv3_r101-d8_512x512_20k_voc12aug_20200617_010932.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.68 + mIoU(ms+flip): 78.78 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_40k_voc12aug/deeplabv3_r50-d8_512x512_40k_voc12aug_20200613_161546-2ae96e7e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_40k_voc12aug/deeplabv3_r50-d8_512x512_40k_voc12aug_20200613_161546.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.92 + mIoU(ms+flip): 79.18 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_40k_voc12aug/deeplabv3_r101-d8_512x512_40k_voc12aug_20200613_161432-0017d784.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_40k_voc12aug/deeplabv3_r101-d8_512x512_40k_voc12aug_20200613_161432.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-40k_pascal-context-480x480 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 46.55 + mIoU(ms+flip): 47.81 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 9.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_40k_pascal_context/deeplabv3_r101-d8_480x480_40k_pascal_context_20200911_204118-1aa27336.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_40k_pascal_context/deeplabv3_r101-d8_480x480_40k_pascal_context-20200911_204118.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-80k_pascal-context-480x480 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 46.42 + mIoU(ms+flip): 47.53 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_80k_pascal_context/deeplabv3_r101-d8_480x480_80k_pascal_context_20200911_170155-2a21fff3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_80k_pascal_context/deeplabv3_r101-d8_480x480_80k_pascal_context-20200911_170155.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-40k_pascal-context-59-480x480 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 52.61 + mIoU(ms+flip): 54.28 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_40k_pascal_context_59/deeplabv3_r101-d8_480x480_40k_pascal_context_59_20210416_110332-cb08ea46.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_40k_pascal_context_59/deeplabv3_r101-d8_480x480_40k_pascal_context_59-20210416_110332.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-80k_pascal-context-59-480x480 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 52.46 + mIoU(ms+flip): 54.09 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_80k_pascal_context_59/deeplabv3_r101-d8_480x480_80k_pascal_context_59_20210416_113002-26303993.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_480x480_80k_pascal_context_59/deeplabv3_r101-d8_480x480_80k_pascal_context_59-20210416_113002.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-20k_coco-stuff10k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 10k + Metrics: + mIoU: 34.66 + mIoU(ms+flip): 36.08 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-20k_coco-stuff10k-512x512.py + Metadata: + Training Data: COCO-Stuff 10k + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 9.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_20k_coco-stuff10k/deeplabv3_r50-d8_512x512_4x4_20k_coco-stuff10k_20210821_043025-b35f789d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_20k_coco-stuff10k/deeplabv3_r50-d8_512x512_4x4_20k_coco-stuff10k_20210821_043025.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-20k_coco-stuff10k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 10k + Metrics: + mIoU: 37.3 + mIoU(ms+flip): 38.42 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-20k_coco-stuff10k-512x512.py + Metadata: + Training Data: COCO-Stuff 10k + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 13.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_20k_coco-stuff10k/deeplabv3_r101-d8_512x512_4x4_20k_coco-stuff10k_20210821_043025-c49752cb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_20k_coco-stuff10k/deeplabv3_r101-d8_512x512_4x4_20k_coco-stuff10k_20210821_043025.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-40k_coco-stuff10k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 10k + Metrics: + mIoU: 35.73 + mIoU(ms+flip): 37.09 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-40k_coco-stuff10k-512x512.py + Metadata: + Training Data: COCO-Stuff 10k + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_40k_coco-stuff10k/deeplabv3_r50-d8_512x512_4x4_40k_coco-stuff10k_20210821_043305-dc76f3ff.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_40k_coco-stuff10k/deeplabv3_r50-d8_512x512_4x4_40k_coco-stuff10k_20210821_043305.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-40k_coco-stuff10k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 10k + Metrics: + mIoU: 37.81 + mIoU(ms+flip): 38.8 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-40k_coco-stuff10k-512x512.py + Metadata: + Training Data: COCO-Stuff 10k + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_40k_coco-stuff10k/deeplabv3_r101-d8_512x512_4x4_40k_coco-stuff10k_20210821_043305-636cb433.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_40k_coco-stuff10k/deeplabv3_r101-d8_512x512_4x4_40k_coco-stuff10k_20210821_043305.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-80k_coco-stuff164k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 39.38 + mIoU(ms+flip): 40.03 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-80k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 9.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_80k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_80k_coco-stuff164k_20210709_163016-88675c24.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_80k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_80k_coco-stuff164k_20210709_163016.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-80k_coco-stuff164k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 40.87 + mIoU(ms+flip): 41.5 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-80k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 13.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_80k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_80k_coco-stuff164k_20210709_201252-13600dc2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_80k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_80k_coco-stuff164k_20210709_201252.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-160k_coco-stuff164k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 41.09 + mIoU(ms+flip): 41.69 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_160k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_160k_coco-stuff164k_20210709_163016-49f2812b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_160k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_160k_coco-stuff164k_20210709_163016.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-160k_coco-stuff164k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 41.82 + mIoU(ms+flip): 42.49 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_160k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_160k_coco-stuff164k_20210709_155402-f035acfd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_160k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_160k_coco-stuff164k_20210709_155402.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r50-d8_4xb4-320k_coco-stuff164k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 41.37 + mIoU(ms+flip): 42.22 + Config: configs/deeplabv3/deeplabv3_r50-d8_4xb4-320k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_320k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_320k_coco-stuff164k_20210709_155403-51b21115.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_4x4_320k_coco-stuff164k/deeplabv3_r50-d8_512x512_4x4_320k_coco-stuff164k_20210709_155403.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch +- Name: deeplabv3_r101-d8_4xb4-320k_coco-stuff164k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 42.61 + mIoU(ms+flip): 43.42 + Config: configs/deeplabv3/deeplabv3_r101-d8_4xb4-320k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_320k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_320k_coco-stuff164k_20210709_155402-3cbca14d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r101-d8_512x512_4x4_320k_coco-stuff164k/deeplabv3_r101-d8_512x512_4x4_320k_coco-stuff164k_20210709_155402.log.json + Paper: + Title: Rethinking atrous convolution for semantic image segmentation + URL: https://arxiv.org/abs/1706.05587 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/aspp_head.py#L54 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/my_deeplabv3_r50_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-769x769-testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/my_deeplabv3_r50_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-769x769-testslide.py new file mode 100644 index 0000000..9d78067 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/my_deeplabv3_r50_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-769x769-testslide.py @@ -0,0 +1,125 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (769, 769) + +data_preprocessor = dict( + size=(769, 769), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + type='ResNetV1c', + dilations=(1, 1, 1, 2), + strides=(1, 2, 2, 1), + multi_grid=(1, 2, 4), + ), + data_preprocessor=dict( + size=(769, 769), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + sampler=dict( + type='OHEMPixelSampler', + thresh=0.7, + min_kept=10000, + ), + dilations=(1, 6, 12, 18), + in_channels=2048, + channels=512, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + norm_cfg=dict( + type='BN', + ), + ), + auxiliary_head=dict( + in_channels=1024, + channels=256, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + norm_cfg=dict( + type='BN', + ), + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(769, 769), + stride=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3/my_deeplabv3_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3/my_deeplabv3_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py new file mode 100644 index 0000000..1e644af --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3/my_deeplabv3_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/deeplabv3_r50-d8.py', '../_base_/datasets/publicdataset_cholecseg8k.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 512, 'channels': 128, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/README.md b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/README.md new file mode 100644 index 0000000..04d01fa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/README.md @@ -0,0 +1,138 @@ +# DeepLabV3+ + +> [Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation](https://arxiv.org/abs/1802.02611) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Spatial pyramid pooling module or encode-decoder structure are used in deep neural networks for semantic segmentation task. The former networks are able to encode multi-scale contextual information by probing the incoming features with filters or pooling operations at multiple rates and multiple effective fields-of-view, while the latter networks can capture sharper object boundaries by gradually recovering the spatial information. In this work, we propose to combine the advantages from both methods. Specifically, our proposed model, DeepLabv3+, extends DeepLabv3 by adding a simple yet effective decoder module to refine the segmentation results especially along object boundaries. We further explore the Xception model and apply the depthwise separable convolution to both Atrous Spatial Pyramid Pooling and decoder modules, resulting in a faster and stronger encoder-decoder network. We demonstrate the effectiveness of the proposed model on PASCAL VOC 2012 and Cityscapes datasets, achieving the test set performance of 89.0% and 82.1% without any post-processing. Our paper is accompanied with a publicly available reference implementation of the proposed models in Tensorflow at [this https URL](https://github.com/tensorflow/models/tree/master/research/deeplab). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ----------------- | --------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| DeepLabV3+ | R-50-D8 | 512x1024 | 40000 | 7.5 | 3.94 | V100 | 79.61 | 81.01 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x1024_40k_cityscapes/deeplabv3plus_r50-d8_512x1024_40k_cityscapes_20200605_094610-d222ffcd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x1024_40k_cityscapes/deeplabv3plus_r50-d8_512x1024_40k_cityscapes_20200605_094610.log.json) | +| DeepLabV3+ | R-101-D8 | 512x1024 | 40000 | 11 | 2.60 | V100 | 80.21 | 81.82 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x1024_40k_cityscapes/deeplabv3plus_r101-d8_512x1024_40k_cityscapes_20200605_094614-3769eecf.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x1024_40k_cityscapes/deeplabv3plus_r101-d8_512x1024_40k_cityscapes_20200605_094614.log.json) | +| DeepLabV3+ | R-50-D8 | 769x769 | 40000 | 8.5 | 1.72 | V100 | 78.97 | 80.46 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_769x769_40k_cityscapes/deeplabv3plus_r50-d8_769x769_40k_cityscapes_20200606_114143-1dcb0e3c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_769x769_40k_cityscapes/deeplabv3plus_r50-d8_769x769_40k_cityscapes_20200606_114143.log.json) | +| DeepLabV3+ | R-101-D8 | 769x769 | 40000 | 12.5 | 1.15 | V100 | 79.46 | 80.50 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_769x769_40k_cityscapes/deeplabv3plus_r101-d8_769x769_40k_cityscapes_20200606_114304-ff414b9e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_769x769_40k_cityscapes/deeplabv3plus_r101-d8_769x769_40k_cityscapes_20200606_114304.log.json) | +| DeepLabV3+ | R-18-D8 | 512x1024 | 80000 | 2.2 | 14.27 | V100 | 76.89 | 78.76 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x1024_80k_cityscapes/deeplabv3plus_r18-d8_512x1024_80k_cityscapes_20201226_080942-cff257fe.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x1024_80k_cityscapes/deeplabv3plus_r18-d8_512x1024_80k_cityscapes-20201226_080942.log.json) | +| DeepLabV3+ | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 80.09 | 81.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x1024_80k_cityscapes/deeplabv3plus_r50-d8_512x1024_80k_cityscapes_20200606_114049-f9fb496d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x1024_80k_cityscapes/deeplabv3plus_r50-d8_512x1024_80k_cityscapes_20200606_114049.log.json) | +| DeepLabV3+ | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 80.97 | 82.03 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x1024_80k_cityscapes/deeplabv3plus_r101-d8_512x1024_80k_cityscapes_20200606_114143-068fcfe9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x1024_80k_cityscapes/deeplabv3plus_r101-d8_512x1024_80k_cityscapes_20200606_114143.log.json) | +| DeepLabV3+ (FP16) | R-101-D8 | 512x1024 | 80000 | 6.35 | 7.87 | V100 | 80.46 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_fp16_512x1024_80k_cityscapes/deeplabv3plus_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230920-f1104f4b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_fp16_512x1024_80k_cityscapes/deeplabv3plus_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230920.log.json) | +| DeepLabV3+ | R-18-D8 | 769x769 | 80000 | 2.5 | 5.74 | V100 | 76.26 | 77.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_769x769_80k_cityscapes/deeplabv3plus_r18-d8_769x769_80k_cityscapes_20201226_083346-f326e06a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_769x769_80k_cityscapes/deeplabv3plus_r18-d8_769x769_80k_cityscapes-20201226_083346.log.json) | +| DeepLabV3+ | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.83 | 81.48 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_769x769_80k_cityscapes/deeplabv3plus_r50-d8_769x769_80k_cityscapes_20200606_210233-0e9dfdc4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_769x769_80k_cityscapes/deeplabv3plus_r50-d8_769x769_80k_cityscapes_20200606_210233.log.json) | +| DeepLabV3+ | R-101-D8 | 769x769 | 80000 | - | - | V100 | 80.65 | 81.47 | [config\[1\]](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_769x769_80k_cityscapes/deeplabv3plus_r101-d8_769x769_80k_cityscapes_20220406_154720-dfcc0b68.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_769x769_80k_cityscapes/deeplabv3plus_r101-d8_769x769_80k_cityscapes_20220406_154720.log.json) | +| DeepLabV3+ | R-101-D16-MG124 | 512x1024 | 40000 | 5.8 | 7.48 | V100 | 79.09 | 80.36 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/ddeeplabv3plus_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d16-mg124_512x1024_40k_cityscapes/deeplabv3plus_r101-d16-mg124_512x1024_40k_cityscapes_20200908_005644-cf9ce186.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d16-mg124_512x1024_40k_cityscapes/deeplabv3plus_r101-d16-mg124_512x1024_40k_cityscapes-20200908_005644.log.json) | +| DeepLabV3+ | R-101-D16-MG124 | 512x1024 | 80000 | 9.9 | - | V100 | 79.90 | 81.33 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d16-mg124_512x1024_80k_cityscapes/deeplabv3plus_r101-d16-mg124_512x1024_80k_cityscapes_20200908_005644-ee6158e0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d16-mg124_512x1024_80k_cityscapes/deeplabv3plus_r101-d16-mg124_512x1024_80k_cityscapes-20200908_005644.log.json) | +| DeepLabV3+ | R-18b-D8 | 512x1024 | 80000 | 2.1 | 14.95 | V100 | 75.87 | 77.52 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18b-d8_512x1024_80k_cityscapes/deeplabv3plus_r18b-d8_512x1024_80k_cityscapes_20201226_090828-e451abd9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18b-d8_512x1024_80k_cityscapes/deeplabv3plus_r18b-d8_512x1024_80k_cityscapes-20201226_090828.log.json) | +| DeepLabV3+ | R-50b-D8 | 512x1024 | 80000 | 7.4 | 3.94 | V100 | 80.28 | 81.44 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50b-d8_512x1024_80k_cityscapes/deeplabv3plus_r50b-d8_512x1024_80k_cityscapes_20201225_213645-a97e4e43.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50b-d8_512x1024_80k_cityscapes/deeplabv3plus_r50b-d8_512x1024_80k_cityscapes-20201225_213645.log.json) | +| DeepLabV3+ | R-101b-D8 | 512x1024 | 80000 | 10.9 | 2.60 | V100 | 80.16 | 81.41 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101b-d8_512x1024_80k_cityscapes/deeplabv3plus_r101b-d8_512x1024_80k_cityscapes_20201226_190843-9c3c93a4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101b-d8_512x1024_80k_cityscapes/deeplabv3plus_r101b-d8_512x1024_80k_cityscapes-20201226_190843.log.json) | +| DeepLabV3+ | R-18b-D8 | 769x769 | 80000 | 2.4 | 5.96 | V100 | 76.36 | 78.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18b-d8_769x769_80k_cityscapes/deeplabv3plus_r18b-d8_769x769_80k_cityscapes_20201226_151312-2c868aff.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18b-d8_769x769_80k_cityscapes/deeplabv3plus_r18b-d8_769x769_80k_cityscapes-20201226_151312.log.json) | +| DeepLabV3+ | R-50b-D8 | 769x769 | 80000 | 8.4 | 1.72 | V100 | 79.41 | 80.56 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50b-d8_769x769_80k_cityscapes/deeplabv3plus_r50b-d8_769x769_80k_cityscapes_20201225_224655-8b596d1c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50b-d8_769x769_80k_cityscapes/deeplabv3plus_r50b-d8_769x769_80k_cityscapes-20201225_224655.log.json) | +| DeepLabV3+ | R-101b-D8 | 769x769 | 80000 | 12.3 | 1.10 | V100 | 79.88 | 81.46 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101b-d8_769x769_80k_cityscapes/deeplabv3plus_r101b-d8_769x769_80k_cityscapes_20201226_205041-227cdf7c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101b-d8_769x769_80k_cityscapes/deeplabv3plus_r101b-d8_769x769_80k_cityscapes-20201226_205041.log.json) | + +\[1\] The training of the model is sensitive to random seed, and the seed to train it is 1111. + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3+ | R-50-D8 | 512x512 | 80000 | 10.6 | 21.01 | V100 | 42.72 | 43.75 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_ade20k/deeplabv3plus_r50-d8_512x512_80k_ade20k_20200614_185028-bf1400d8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_ade20k/deeplabv3plus_r50-d8_512x512_80k_ade20k_20200614_185028.log.json) | +| DeepLabV3+ | R-101-D8 | 512x512 | 80000 | 14.1 | 14.16 | V100 | 44.60 | 46.06 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_ade20k/deeplabv3plus_r101-d8_512x512_80k_ade20k_20200615_014139-d5730af7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_ade20k/deeplabv3plus_r101-d8_512x512_80k_ade20k_20200615_014139.log.json) | +| DeepLabV3+ | R-50-D8 | 512x512 | 160000 | - | - | V100 | 43.95 | 44.93 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_160k_ade20k/deeplabv3plus_r50-d8_512x512_160k_ade20k_20200615_124504-6135c7e0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_160k_ade20k/deeplabv3plus_r50-d8_512x512_160k_ade20k_20200615_124504.log.json) | +| DeepLabV3+ | R-101-D8 | 512x512 | 160000 | - | - | V100 | 45.47 | 46.35 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_160k_ade20k/deeplabv3plus_r101-d8_512x512_160k_ade20k_20200615_123232-38ed86bb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_160k_ade20k/deeplabv3plus_r101-d8_512x512_160k_ade20k_20200615_123232.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3+ | R-50-D8 | 512x512 | 20000 | 7.6 | 21 | V100 | 75.93 | 77.50 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_20k_voc12aug/deeplabv3plus_r50-d8_512x512_20k_voc12aug_20200617_102323-aad58ef1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_20k_voc12aug/deeplabv3plus_r50-d8_512x512_20k_voc12aug_20200617_102323.log.json) | +| DeepLabV3+ | R-101-D8 | 512x512 | 20000 | 11 | 13.88 | V100 | 77.22 | 78.59 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_20k_voc12aug/deeplabv3plus_r101-d8_512x512_20k_voc12aug_20200617_102345-c7ff3d56.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_20k_voc12aug/deeplabv3plus_r101-d8_512x512_20k_voc12aug_20200617_102345.log.json) | +| DeepLabV3+ | R-50-D8 | 512x512 | 40000 | - | - | V100 | 76.81 | 77.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_40k_voc12aug/deeplabv3plus_r50-d8_512x512_40k_voc12aug_20200613_161759-e1b43aa9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_40k_voc12aug/deeplabv3plus_r50-d8_512x512_40k_voc12aug_20200613_161759.log.json) | +| DeepLabV3+ | R-101-D8 | 512x512 | 40000 | - | - | V100 | 78.62 | 79.53 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_40k_voc12aug/deeplabv3plus_r101-d8_512x512_40k_voc12aug_20200613_205333-faf03387.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_40k_voc12aug/deeplabv3plus_r101-d8_512x512_40k_voc12aug_20200613_205333.log.json) | + +### Pascal Context + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3+ | R-101-D8 | 480x480 | 40000 | - | 9.09 | V100 | 47.30 | 48.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_40k_pascal_context/deeplabv3plus_r101-d8_480x480_40k_pascal_context_20200911_165459-d3c8a29e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_40k_pascal_context/deeplabv3plus_r101-d8_480x480_40k_pascal_context-20200911_165459.log.json) | +| DeepLabV3+ | R-101-D8 | 480x480 | 80000 | - | - | V100 | 47.23 | 48.26 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_80k_pascal_context/deeplabv3plus_r101-d8_480x480_80k_pascal_context_20200911_155322-145d3ee8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_80k_pascal_context/deeplabv3plus_r101-d8_480x480_80k_pascal_context-20200911_155322.log.json) | + +### Pascal Context 59 + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3+ | R-101-D8 | 480x480 | 40000 | - | - | V100 | 52.86 | 54.54 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_40k_pascal_context_59/deeplabv3plus_r101-d8_480x480_40k_pascal_context_59_20210416_111233-ed937f15.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_40k_pascal_context_59/deeplabv3plus_r101-d8_480x480_40k_pascal_context_59-20210416_111233.log.json) | +| DeepLabV3+ | R-101-D8 | 480x480 | 80000 | - | - | V100 | 53.2 | 54.67 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_80k_pascal_context_59/deeplabv3plus_r101-d8_480x480_80k_pascal_context_59_20210416_111127-7ca0331d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_80k_pascal_context_59/deeplabv3plus_r101-d8_480x480_80k_pascal_context_59-20210416_111127.log.json) | + +### LoveDA + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| DeepLabV3+ | R-18-D8 | 512x512 | 80000 | 1.93 | 25.57 | V100 | 50.28 | 50.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_loveda-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x512_80k_loveda/deeplabv3plus_r18-d8_512x512_80k_loveda_20211104_132800-ce0fa0ca.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x512_80k_loveda/deeplabv3plus_r18-d8_512x512_80k_loveda_20211104_132800.log.json) | +| DeepLabV3+ | R-50-D8 | 512x512 | 80000 | 7.37 | 6.00 | V100 | 50.99 | 50.65 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_loveda/deeplabv3plus_r50-d8_512x512_80k_loveda_20211105_080442-f0720392.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_loveda/deeplabv3plus_r50-d8_512x512_80k_loveda_20211105_080442.log.json) | +| DeepLabV3+ | R-101-D8 | 512x512 | 80000 | 10.84 | 4.33 | V100 | 51.47 | 51.32 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_loveda-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_loveda/deeplabv3plus_r101-d8_512x512_80k_loveda_20211105_110759-4c1f297e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_loveda/deeplabv3plus_r101-d8_512x512_80k_loveda_20211105_110759.log.json) | + +### Potsdam + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3+ | R-18-D8 | 512x512 | 80000 | 1.91 | 81.68 | V100 | 77.09 | 78.44 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x512_80k_potsdam/deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601-75fd5bc3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x512_80k_potsdam/deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601.log.json) | +| DeepLabV3+ | R-50-D8 | 512x512 | 80000 | 7.36 | 26.44 | V100 | 78.33 | 79.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_potsdam/deeplabv3plus_r50-d8_512x512_80k_potsdam_20211219_031508-7e7a2b24.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_potsdam/deeplabv3plus_r50-d8_512x512_80k_potsdam_20211219_031508.log.json) | +| DeepLabV3+ | R-101-D8 | 512x512 | 80000 | 10.83 | 17.56 | V100 | 78.7 | 79.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_potsdam/deeplabv3plus_r101-d8_512x512_80k_potsdam_20211219_031508-8b112708.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_potsdam/deeplabv3plus_r101-d8_512x512_80k_potsdam_20211219_031508.log.json) | + +### Vaihingen + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3+ | R-18-D8 | 512x512 | 80000 | 1.91 | 72.79 | V100 | 72.50 | 74.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r18-d8_4x4_512x512_80k_vaihingen_20211231_230805-7626a263.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r18-d8_4x4_512x512_80k_vaihingen_20211231_230805.log.json) | +| DeepLabV3+ | R-50-D8 | 512x512 | 80000 | 7.36 | 26.91 | V100 | 73.97 | 75.05 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r50-d8_4x4_512x512_80k_vaihingen_20211231_230816-5040938d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r50-d8_4x4_512x512_80k_vaihingen_20211231_230816.log.json) | +| DeepLabV3+ | R-101-D8 | 512x512 | 80000 | 10.83 | 18.59 | V100 | 73.06 | 74.14 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r101-d8_4x4_512x512_80k_vaihingen_20211231_230816-8a095afa.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r101-d8_4x4_512x512_80k_vaihingen_20211231_230816.log.json) | + +### iSAID + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3+ | R-18-D8 | 896x896 | 80000 | 6.19 | 24.81 | V100 | 61.35 | 62.61 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_isaid-896x896.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_4x4_896x896_80k_isaid/deeplabv3plus_r18-d8_4x4_896x896_80k_isaid_20220110_180526-7059991d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_4x4_896x896_80k_isaid/deeplabv3plus_r18-d8_4x4_896x896_80k_isaid_20220110_180526.log.json) | +| DeepLabV3+ | R-50-D8 | 896x896 | 80000 | 21.45 | 8.42 | V100 | 67.06 | 68.02 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_isaid-896x896.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4x4_896x896_80k_isaid/deeplabv3plus_r50-d8_4x4_896x896_80k_isaid_20220110_180526-598be439.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4x4_896x896_80k_isaid/deeplabv3plus_r50-d8_4x4_896x896_80k_isaid_20220110_180526.log.json) | + +### Mapillary Vistas v1.2 + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DeepLabV3+ | R-50-D8 | 1280x1280 | 300000 | 24.04 | 17.92 | A100 | 47.35 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280_20230301_110504-655f8e43.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280_20230301_110504.json) | + +Note: + +- `D-8`/`D-16` here corresponding to the output stride 8/16 setting for DeepLab series. +- `MG-124` stands for multi-grid dilation in the last stage of ResNet. +- `FP16` means Mixed Precision (FP16) is adopted in training. +- `896x896` is the Crop Size of iSAID dataset, which is followed by the implementation of [PointFlow: Flowing Semantics Through Points for Aerial Image Segmentation](https://arxiv.org/pdf/2103.06564.pdf) + +## Citation + +```bibtex +@inproceedings{deeplabv3plus2018, + title={Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation}, + author={Liang-Chieh Chen and Yukun Zhu and George Papandreou and Florian Schroff and Hartwig Adam}, + booktitle={ECCV}, + year={2018} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..71c9118 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet101_v1c', + backbone=dict( + depth=101, + dilations=(1, 1, 1, 2), + strides=(1, 2, 2, 1), + multi_grid=(1, 2, 4)), + decode_head=dict( + dilations=(1, 6, 12, 18), + sampler=dict(type='OHEMPixelSampler', min_kept=100000))) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..7d1ccf0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet101_v1c', + backbone=dict( + depth=101, + dilations=(1, 1, 1, 2), + strides=(1, 2, 2, 1), + multi_grid=(1, 2, 4)), + decode_head=dict( + dilations=(1, 6, 12, 18), + sampler=dict(type='OHEMPixelSampler', min_kept=100000))) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..884b526 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..debb025 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..bc9334e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..4af9aa2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..9c9883d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = './deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py' +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=optimizer, + loss_scale=512.) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..c38a802 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..97bb827 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..e4b4011 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-40k_pascal-context-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..eeefae4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-40k_pascal-context-59-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..0755c53 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..844ac96 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..87c6da9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_loveda-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..115b1c9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_pascal-context-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..9aaa653 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_pascal-context-59-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..5063b13 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..b99c2c7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..d1bcb09 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,4 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..c78fc1e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,4 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..5f54913 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..1b361d6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_isaid-896x896.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_isaid-896x896.py new file mode 100644 index 0000000..3a1a753 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_isaid-896x896.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_isaid-896x896.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..01bbf9b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_loveda-512x512.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..134f2cf --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..2194838 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..ea86219 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet18', + backbone=dict(type='ResNet', depth=18), + decode_head=dict( + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..34ee7ed --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,11 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet18', + backbone=dict(type='ResNet', depth=18), + decode_head=dict( + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280.py new file mode 100644 index 0000000..133c45a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280.py @@ -0,0 +1,58 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/mapillary_v1_65.py', + '../_base_/default_runtime.py', +] + +crop_size = (1280, 1280) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict(depth=50), + decode_head=dict(num_classes=65), + auxiliary_head=dict(num_classes=65)) + +iters = 300000 +# optimizer +optimizer = dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.0001) +# optimizer +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict( + custom_keys={'backbone': dict(lr_mult=0.1, decay_mult=1.0)})) +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0, + power=0.9, + begin=0, + end=iters, + by_epoch=False) +] + +# training schedule for 300k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=iters, val_interval=iters // 10) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') + +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', by_epoch=False, interval=iters // 10), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) + +train_dataloader = dict(batch_size=2) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (4 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=8) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..32f994d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/cityscapes.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..8cdf534 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..0d249b0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/cityscapes.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..863a46e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..9a899fb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..1876d0c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..95b56d0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/pascal_context.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + auxiliary_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..459c62d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + auxiliary_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..0d61b50 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..6f872ca --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_isaid-896x896.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_isaid-896x896.py new file mode 100644 index 0000000..7edec14 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_isaid-896x896.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/isaid.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (896, 896) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=16), + auxiliary_head=dict(num_classes=16)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..64e262c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/loveda.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=7), + auxiliary_head=dict(num_classes=7)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..5ff7fcb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/pascal_context.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + auxiliary_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..84aaf25 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + auxiliary_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..5810d6b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/potsdam.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=6), + auxiliary_head=dict(num_classes=6)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..a7f4b2d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/vaihingen.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=6), + auxiliary_head=dict(num_classes=6)) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..3e28135 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..6366bd4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/metafile.yaml b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/metafile.yaml new file mode 100644 index 0000000..b41de4d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/metafile.yaml @@ -0,0 +1,1041 @@ +Collections: +- Name: DeepLabV3+ + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + - Pascal Context + - Pascal Context 59 + - LoveDA + - Potsdam + - Vaihingen + - iSAID + - Mapillary Vistas v1.2 + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + README: configs/deeplabv3plus/README.md + Frameworks: + - PyTorch +Models: +- Name: deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.61 + mIoU(ms+flip): 81.01 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 7.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x1024_40k_cityscapes/deeplabv3plus_r50-d8_512x1024_40k_cityscapes_20200605_094610-d222ffcd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x1024_40k_cityscapes/deeplabv3plus_r50-d8_512x1024_40k_cityscapes_20200605_094610.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.21 + mIoU(ms+flip): 81.82 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 11.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x1024_40k_cityscapes/deeplabv3plus_r101-d8_512x1024_40k_cityscapes_20200605_094614-3769eecf.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x1024_40k_cityscapes/deeplabv3plus_r101-d8_512x1024_40k_cityscapes_20200605_094614.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.97 + mIoU(ms+flip): 80.46 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 8.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_769x769_40k_cityscapes/deeplabv3plus_r50-d8_769x769_40k_cityscapes_20200606_114143-1dcb0e3c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_769x769_40k_cityscapes/deeplabv3plus_r50-d8_769x769_40k_cityscapes_20200606_114143.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.46 + mIoU(ms+flip): 80.5 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 12.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_769x769_40k_cityscapes/deeplabv3plus_r101-d8_769x769_40k_cityscapes_20200606_114304-ff414b9e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_769x769_40k_cityscapes/deeplabv3plus_r101-d8_769x769_40k_cityscapes_20200606_114304.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.89 + mIoU(ms+flip): 78.76 + Config: configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 2.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x1024_80k_cityscapes/deeplabv3plus_r18-d8_512x1024_80k_cityscapes_20201226_080942-cff257fe.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x1024_80k_cityscapes/deeplabv3plus_r18-d8_512x1024_80k_cityscapes-20201226_080942.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.09 + mIoU(ms+flip): 81.13 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x1024_80k_cityscapes/deeplabv3plus_r50-d8_512x1024_80k_cityscapes_20200606_114049-f9fb496d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x1024_80k_cityscapes/deeplabv3plus_r50-d8_512x1024_80k_cityscapes_20200606_114049.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.97 + mIoU(ms+flip): 82.03 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x1024_80k_cityscapes/deeplabv3plus_r101-d8_512x1024_80k_cityscapes_20200606_114143-068fcfe9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x1024_80k_cityscapes/deeplabv3plus_r101-d8_512x1024_80k_cityscapes_20200606_114143.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb2-amp-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.46 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3+ + - (FP16) + Training Resources: 4x V100 GPUS + Memory (GB): 6.35 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_fp16_512x1024_80k_cityscapes/deeplabv3plus_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230920-f1104f4b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_fp16_512x1024_80k_cityscapes/deeplabv3plus_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230920.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r18-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.26 + mIoU(ms+flip): 77.91 + Config: configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 2.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_769x769_80k_cityscapes/deeplabv3plus_r18-d8_769x769_80k_cityscapes_20201226_083346-f326e06a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_769x769_80k_cityscapes/deeplabv3plus_r18-d8_769x769_80k_cityscapes-20201226_083346.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.83 + mIoU(ms+flip): 81.48 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_769x769_80k_cityscapes/deeplabv3plus_r50-d8_769x769_80k_cityscapes_20200606_210233-0e9dfdc4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_769x769_80k_cityscapes/deeplabv3plus_r50-d8_769x769_80k_cityscapes_20200606_210233.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.65 + mIoU(ms+flip): 81.47 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_769x769_80k_cityscapes/deeplabv3plus_r101-d8_769x769_80k_cityscapes_20220406_154720-dfcc0b68.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_769x769_80k_cityscapes/deeplabv3plus_r101-d8_769x769_80k_cityscapes_20220406_154720.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: ddeeplabv3plus_r101-d16-mg124_4xb2-40k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.09 + mIoU(ms+flip): 80.36 + Config: configs/deeplabv3plus/ddeeplabv3plus_r101-d16-mg124_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D16-MG124 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 5.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d16-mg124_512x1024_40k_cityscapes/deeplabv3plus_r101-d16-mg124_512x1024_40k_cityscapes_20200908_005644-cf9ce186.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d16-mg124_512x1024_40k_cityscapes/deeplabv3plus_r101-d16-mg124_512x1024_40k_cityscapes-20200908_005644.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d16-mg124_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.9 + mIoU(ms+flip): 81.33 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d16-mg124_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D16-MG124 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 9.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d16-mg124_512x1024_80k_cityscapes/deeplabv3plus_r101-d16-mg124_512x1024_80k_cityscapes_20200908_005644-ee6158e0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d16-mg124_512x1024_80k_cityscapes/deeplabv3plus_r101-d16-mg124_512x1024_80k_cityscapes-20200908_005644.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.87 + mIoU(ms+flip): 77.52 + Config: configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18b-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 2.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18b-d8_512x1024_80k_cityscapes/deeplabv3plus_r18b-d8_512x1024_80k_cityscapes_20201226_090828-e451abd9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18b-d8_512x1024_80k_cityscapes/deeplabv3plus_r18b-d8_512x1024_80k_cityscapes-20201226_090828.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.28 + mIoU(ms+flip): 81.44 + Config: configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 7.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50b-d8_512x1024_80k_cityscapes/deeplabv3plus_r50b-d8_512x1024_80k_cityscapes_20201225_213645-a97e4e43.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50b-d8_512x1024_80k_cityscapes/deeplabv3plus_r50b-d8_512x1024_80k_cityscapes-20201225_213645.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.16 + mIoU(ms+flip): 81.41 + Config: configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 10.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101b-d8_512x1024_80k_cityscapes/deeplabv3plus_r101b-d8_512x1024_80k_cityscapes_20201226_190843-9c3c93a4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101b-d8_512x1024_80k_cityscapes/deeplabv3plus_r101b-d8_512x1024_80k_cityscapes-20201226_190843.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.36 + mIoU(ms+flip): 78.24 + Config: configs/deeplabv3plus/deeplabv3plus_r18b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18b-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 2.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18b-d8_769x769_80k_cityscapes/deeplabv3plus_r18b-d8_769x769_80k_cityscapes_20201226_151312-2c868aff.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18b-d8_769x769_80k_cityscapes/deeplabv3plus_r18b-d8_769x769_80k_cityscapes-20201226_151312.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.41 + mIoU(ms+flip): 80.56 + Config: configs/deeplabv3plus/deeplabv3plus_r50b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 8.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50b-d8_769x769_80k_cityscapes/deeplabv3plus_r50b-d8_769x769_80k_cityscapes_20201225_224655-8b596d1c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50b-d8_769x769_80k_cityscapes/deeplabv3plus_r50b-d8_769x769_80k_cityscapes-20201225_224655.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-769x769 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.88 + mIoU(ms+flip): 81.46 + Config: configs/deeplabv3plus/deeplabv3plus_r101b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 12.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101b-d8_769x769_80k_cityscapes/deeplabv3plus_r101b-d8_769x769_80k_cityscapes_20201226_205041-227cdf7c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101b-d8_769x769_80k_cityscapes/deeplabv3plus_r101b-d8_769x769_80k_cityscapes-20201226_205041.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.72 + mIoU(ms+flip): 43.75 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 10.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_ade20k/deeplabv3plus_r50-d8_512x512_80k_ade20k_20200614_185028-bf1400d8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_ade20k/deeplabv3plus_r50-d8_512x512_80k_ade20k_20200614_185028.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.6 + mIoU(ms+flip): 46.06 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 14.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_ade20k/deeplabv3plus_r101-d8_512x512_80k_ade20k_20200615_014139-d5730af7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_ade20k/deeplabv3plus_r101-d8_512x512_80k_ade20k_20200615_014139.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.95 + mIoU(ms+flip): 44.93 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_160k_ade20k/deeplabv3plus_r50-d8_512x512_160k_ade20k_20200615_124504-6135c7e0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_160k_ade20k/deeplabv3plus_r50-d8_512x512_160k_ade20k_20200615_124504.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.47 + mIoU(ms+flip): 46.35 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_160k_ade20k/deeplabv3plus_r101-d8_512x512_160k_ade20k_20200615_123232-38ed86bb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_160k_ade20k/deeplabv3plus_r101-d8_512x512_160k_ade20k_20200615_123232.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 75.93 + mIoU(ms+flip): 77.5 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 7.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_20k_voc12aug/deeplabv3plus_r50-d8_512x512_20k_voc12aug_20200617_102323-aad58ef1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_20k_voc12aug/deeplabv3plus_r50-d8_512x512_20k_voc12aug_20200617_102323.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.22 + mIoU(ms+flip): 78.59 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 11.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_20k_voc12aug/deeplabv3plus_r101-d8_512x512_20k_voc12aug_20200617_102345-c7ff3d56.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_20k_voc12aug/deeplabv3plus_r101-d8_512x512_20k_voc12aug_20200617_102345.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.81 + mIoU(ms+flip): 77.57 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_40k_voc12aug/deeplabv3plus_r50-d8_512x512_40k_voc12aug_20200613_161759-e1b43aa9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_40k_voc12aug/deeplabv3plus_r50-d8_512x512_40k_voc12aug_20200613_161759.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 78.62 + mIoU(ms+flip): 79.53 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_40k_voc12aug/deeplabv3plus_r101-d8_512x512_40k_voc12aug_20200613_205333-faf03387.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_40k_voc12aug/deeplabv3plus_r101-d8_512x512_40k_voc12aug_20200613_205333.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-40k_pascal-context-480x480 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 47.3 + mIoU(ms+flip): 48.47 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-40k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_40k_pascal_context/deeplabv3plus_r101-d8_480x480_40k_pascal_context_20200911_165459-d3c8a29e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_40k_pascal_context/deeplabv3plus_r101-d8_480x480_40k_pascal_context-20200911_165459.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-80k_pascal-context-480x480 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 47.23 + mIoU(ms+flip): 48.26 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_80k_pascal_context/deeplabv3plus_r101-d8_480x480_80k_pascal_context_20200911_155322-145d3ee8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_80k_pascal_context/deeplabv3plus_r101-d8_480x480_80k_pascal_context-20200911_155322.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-40k_pascal-context-59-480x480 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 52.86 + mIoU(ms+flip): 54.54 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-40k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_40k_pascal_context_59/deeplabv3plus_r101-d8_480x480_40k_pascal_context_59_20210416_111233-ed937f15.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_40k_pascal_context_59/deeplabv3plus_r101-d8_480x480_40k_pascal_context_59-20210416_111233.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-80k_pascal-context-59-480x480 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 53.2 + mIoU(ms+flip): 54.67 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_80k_pascal_context_59/deeplabv3plus_r101-d8_480x480_80k_pascal_context_59_20210416_111127-7ca0331d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_480x480_80k_pascal_context_59/deeplabv3plus_r101-d8_480x480_80k_pascal_context_59-20210416_111127.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r18-d8_4xb4-80k_loveda-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 50.28 + mIoU(ms+flip): 50.47 + Config: configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - R-18-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 1.93 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x512_80k_loveda/deeplabv3plus_r18-d8_512x512_80k_loveda_20211104_132800-ce0fa0ca.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x512_80k_loveda/deeplabv3plus_r18-d8_512x512_80k_loveda_20211104_132800.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 50.99 + mIoU(ms+flip): 50.65 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 7.37 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_loveda/deeplabv3plus_r50-d8_512x512_80k_loveda_20211105_080442-f0720392.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_loveda/deeplabv3plus_r50-d8_512x512_80k_loveda_20211105_080442.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-80k_loveda-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 51.47 + mIoU(ms+flip): 51.32 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 10.84 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_loveda/deeplabv3plus_r101-d8_512x512_80k_loveda_20211105_110759-4c1f297e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_loveda/deeplabv3plus_r101-d8_512x512_80k_loveda_20211105_110759.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r18-d8_4xb4-80k_potsdam-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 77.09 + mIoU(ms+flip): 78.44 + Config: configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - R-18-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 1.91 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x512_80k_potsdam/deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601-75fd5bc3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_512x512_80k_potsdam/deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 78.33 + mIoU(ms+flip): 79.27 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 7.36 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_potsdam/deeplabv3plus_r50-d8_512x512_80k_potsdam_20211219_031508-7e7a2b24.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_512x512_80k_potsdam/deeplabv3plus_r50-d8_512x512_80k_potsdam_20211219_031508.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 78.7 + mIoU(ms+flip): 79.47 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 10.83 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_potsdam/deeplabv3plus_r101-d8_512x512_80k_potsdam_20211219_031508-8b112708.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_512x512_80k_potsdam/deeplabv3plus_r101-d8_512x512_80k_potsdam_20211219_031508.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r18-d8_4xb4-80k_vaihingen-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 72.5 + mIoU(ms+flip): 74.13 + Config: configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - R-18-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 1.91 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r18-d8_4x4_512x512_80k_vaihingen_20211231_230805-7626a263.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r18-d8_4x4_512x512_80k_vaihingen_20211231_230805.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 73.97 + mIoU(ms+flip): 75.05 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 7.36 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r50-d8_4x4_512x512_80k_vaihingen_20211231_230816-5040938d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r50-d8_4x4_512x512_80k_vaihingen_20211231_230816.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r101-d8_4xb4-80k_vaihingen-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 73.06 + mIoU(ms+flip): 74.14 + Config: configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - R-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 10.83 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r101-d8_4x4_512x512_80k_vaihingen_20211231_230816-8a095afa.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r101-d8_4x4_512x512_80k_vaihingen/deeplabv3plus_r101-d8_4x4_512x512_80k_vaihingen_20211231_230816.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r18-d8_4xb4-80k_isaid-896x896 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: iSAID + Metrics: + mIoU: 61.35 + mIoU(ms+flip): 62.61 + Config: configs/deeplabv3plus/deeplabv3plus_r18-d8_4xb4-80k_isaid-896x896.py + Metadata: + Training Data: iSAID + Batch Size: 16 + Architecture: + - R-18-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 6.19 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_4x4_896x896_80k_isaid/deeplabv3plus_r18-d8_4x4_896x896_80k_isaid_20220110_180526-7059991d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r18-d8_4x4_896x896_80k_isaid/deeplabv3plus_r18-d8_4x4_896x896_80k_isaid_20220110_180526.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb4-80k_isaid-896x896 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: iSAID + Metrics: + mIoU: 67.06 + mIoU(ms+flip): 68.02 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb4-80k_isaid-896x896.py + Metadata: + Training Data: iSAID + Batch Size: 16 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 21.45 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4x4_896x896_80k_isaid/deeplabv3plus_r50-d8_4x4_896x896_80k_isaid_20220110_180526-598be439.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4x4_896x896_80k_isaid/deeplabv3plus_r50-d8_4x4_896x896_80k_isaid_20220110_180526.log.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch +- Name: deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Mapillary Vistas v1.2 + Metrics: + mIoU: 47.35 + Config: configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280.py + Metadata: + Training Data: Mapillary Vistas v1.2 + Batch Size: 8 + Architecture: + - R-50-D8 + - DeepLabV3+ + Training Resources: 4x A100 GPUS + Memory (GB): 24.04 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280_20230301_110504-655f8e43.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280/deeplabv3plus_r50-d8_4xb2-300k_mapillay_v1_65-1280x1280_20230301_110504.json + Paper: + Title: Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation + URL: https://arxiv.org/abs/1802.02611 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/sep_aspp_head.py#L30 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py new file mode 100644 index 0000000..3b5aaaa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py @@ -0,0 +1,116 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + type='ResNetV1c', + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + c1_in_channels=256, + c1_channels=48, + in_channels=2048, + channels=512, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + norm_cfg=dict( + type='BN', + ), + ), + auxiliary_head=dict( + in_channels=1024, + channels=256, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + norm_cfg=dict( + type='BN', + ), + ), +) + +test_cfg = dict( + crop_size=(512, 1024), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py new file mode 100644 index 0000000..b787abd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py @@ -0,0 +1,118 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/torchvision_012/resnet101.pth', + backbone=dict( + depth=101, + type='ResNet', + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + c1_in_channels=256, + c1_channels=48, + in_channels=2048, + channels=512, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + norm_cfg=dict( + type='BN', + ), + ), + auxiliary_head=dict( + in_channels=1024, + channels=256, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + norm_cfg=dict( + type='BN', + ), + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 1024), + stride=(341, 682), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..937e29e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r101_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,125 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + type='ResNetV1c', + dilations=(1, 1, 1, 2), + strides=(1, 2, 2, 1), + multi_grid=(1, 2, 4), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + sampler=dict( + type='OHEMPixelSampler', + thresh=0.7, + min_kept=10000, + ), + dilations=(1, 6, 12, 18), + c1_in_channels=256, + c1_channels=48, + in_channels=2048, + channels=512, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + norm_cfg=dict( + type='BN', + ), + ), + auxiliary_head=dict( + in_channels=1024, + channels=256, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + norm_cfg=dict( + type='BN', + ), + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r18_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-512x512-testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r18_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-512x512-testslide.py new file mode 100644 index 0000000..946157e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r18_Pre_ohempSampler_g1-40k_check_4000_my_dataset_model-512x512-testslide.py @@ -0,0 +1,127 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/torchvision_012/resnet18.pth', + backbone=dict( + depth=18, + type='ResNet', + dilations=(1, 1, 1, 2), + strides=(1, 2, 2, 1), + multi_grid=(1, 2, 4), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + sampler=dict( + type='OHEMPixelSampler', + thresh=0.7, + min_kept=10000, + ), + dilations=(1, 6, 12, 18), + c1_in_channels=64, + c1_channels=12, + in_channels=512, + channels=128, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + norm_cfg=dict( + type='BN', + ), + ), + auxiliary_head=dict( + in_channels=256, + channels=64, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + norm_cfg=dict( + type='BN', + ), + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 512), + stride=(341, 341), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py new file mode 100644 index 0000000..89967c2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_autolaparo.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [123.62464353460942, 85.34836259209033, 82.31539425671558], 'std': [47.172211618459315, 47.08256715323592, 48.135121265163605], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [123.62464353460942, 85.34836259209033, 82.31539425671558], 'std': [47.172211618459315, 47.08256715323592, 48.135121265163605], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 64, 'c1_channels': 12, 'in_channels': 512, 'channels': 128, 'num_classes': 10, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 10, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py new file mode 100644 index 0000000..d7d792c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_cholecseg8k.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 64, 'c1_channels': 12, 'in_channels': 512, 'channels': 128, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py new file mode 100644 index 0000000..fd862b3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_dresden.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [103.172638338208, 61.44762740851152, 51.407770213021976], 'std': [75.77031253622098, 54.63616729031377, 49.45572239497569], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [103.172638338208, 61.44762740851152, 51.407770213021976], 'std': [75.77031253622098, 54.63616729031377, 49.45572239497569], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 64, 'c1_channels': 12, 'in_channels': 512, 'channels': 128, 'num_classes': 11, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 11, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py new file mode 100644 index 0000000..3f7d069 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_endovis_2017.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 64, 'c1_channels': 12, 'in_channels': 512, 'channels': 128, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py new file mode 100644 index 0000000..2ce8cfd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_endovis_2018.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 64, 'c1_channels': 12, 'in_channels': 512, 'channels': 128, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py new file mode 100644 index 0000000..62f1040 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py @@ -0,0 +1,23 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_autolaparo.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +# TODO ADD +train_dataloader = dict(batch_size=8) + +data_preprocessor = {'size': (512, 512), 'mean': [123.62464353460942, 85.34836259209033, 82.31539425671558], 'std': [47.172211618459315, 47.08256715323592, 48.135121265163605], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [123.62464353460942, 85.34836259209033, 82.31539425671558], 'std': [47.172211618459315, 47.08256715323592, 48.135121265163605], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 256, 'c1_channels': 48, 'in_channels': 2048, 'channels': 512, 'num_classes': 10, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 10, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py new file mode 100644 index 0000000..34bc049 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py @@ -0,0 +1,22 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_cholecseg8k.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False} +# TODO ADD +train_dataloader = dict(batch_size=8) + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 256, 'c1_channels': 48, 'in_channels': 2048, 'channels': 512, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py new file mode 100644 index 0000000..0debf8a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py @@ -0,0 +1,23 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_dresden.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [103.172638338208, 61.44762740851152, 51.407770213021976], 'std': [75.77031253622098, 54.63616729031377, 49.45572239497569], 'bgr_to_rgb': False} + +# TODO ADD +train_dataloader = dict(batch_size=8) + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [103.172638338208, 61.44762740851152, 51.407770213021976], 'std': [75.77031253622098, 54.63616729031377, 49.45572239497569], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 256, 'c1_channels': 48, 'in_channels': 2048, 'channels': 512, 'num_classes': 11, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 11, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py new file mode 100644 index 0000000..9766854 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py @@ -0,0 +1,23 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_endovis_2017.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False} + +# TODO ADD +train_dataloader = dict(batch_size=8) + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 256, 'c1_channels': 48, 'in_channels': 2048, 'channels': 512, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py new file mode 100644 index 0000000..4ccd5b6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/deeplabv3plus/my_deeplabv3plus_r50_r50_b8_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py @@ -0,0 +1,23 @@ +_base_ = ['../_base_/models/deeplabv3plus_r50-d8.py', '../_base_/datasets/publicdataset_endovis_2018.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False} + +# TODO ADD +train_dataloader = dict(batch_size=8) + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False}, 'decode_head': {'c1_in_channels': 256, 'c1_channels': 48, 'in_channels': 2048, 'channels': 512, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'align_corners': False, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_deeplabv3plus_r50_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/README.md b/Seg_All_In_One_MMSeg/configs/dmnet/README.md new file mode 100644 index 0000000..b0cf944 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/README.md @@ -0,0 +1,59 @@ +# DMNet + +> [Dynamic Multi-scale Filters for Semantic Segmentation](https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Multi-scale representation provides an effective way toaddress scale variation of objects and stuff in semantic seg-mentation. Previous works construct multi-scale represen-tation by utilizing different filter sizes, expanding filter sizeswith dilated filters or pooling grids, and the parameters ofthese filters are fixed after training. These methods oftensuffer from heavy computational cost or have more param-eters, and are not adaptive to the input image during in-ference. To address these problems, this paper proposes aDynamic Multi-scale Network (DMNet) to adaptively cap-ture multi-scale contents for predicting pixel-level semanticlabels. DMNet is composed of multiple Dynamic Convolu-tional Modules (DCMs) arranged in parallel, each of whichexploits context-aware filters to estimate semantic represen-tation for a specific scale. The outputs of multiple DCMsare further integrated for final segmentation. We conductextensive experiments to evaluate our DMNet on three chal-lenging semantic segmentation and scene parsing datasets,PASCAL VOC 2012, Pascal-Context, and ADE20K. DMNetachieves a new record 84.4% mIoU on PASCAL VOC 2012test set without MS COCO pre-trained and post-processing,and also obtains state-of-the-art performance on Pascal-Context and ADE20K. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DMNet | R-50-D8 | 512x1024 | 40000 | 7.0 | 3.66 | V100 | 77.78 | 79.14 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x1024_40k_cityscapes/dmnet_r50-d8_512x1024_40k_cityscapes_20201215_042326-615373cf.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x1024_40k_cityscapes/dmnet_r50-d8_512x1024_40k_cityscapes-20201215_042326.log.json) | +| DMNet | R-101-D8 | 512x1024 | 40000 | 10.6 | 2.54 | V100 | 78.37 | 79.72 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x1024_40k_cityscapes/dmnet_r101-d8_512x1024_40k_cityscapes_20201215_043100-8291e976.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x1024_40k_cityscapes/dmnet_r101-d8_512x1024_40k_cityscapes-20201215_043100.log.json) | +| DMNet | R-50-D8 | 769x769 | 40000 | 7.9 | 1.57 | V100 | 78.49 | 80.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_769x769_40k_cityscapes/dmnet_r50-d8_769x769_40k_cityscapes_20201215_093706-e7f0e23e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_769x769_40k_cityscapes/dmnet_r50-d8_769x769_40k_cityscapes-20201215_093706.log.json) | +| DMNet | R-101-D8 | 769x769 | 40000 | 12.0 | 1.01 | V100 | 77.62 | 78.94 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_769x769_40k_cityscapes/dmnet_r101-d8_769x769_40k_cityscapes_20201215_081348-a74261f6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_769x769_40k_cityscapes/dmnet_r101-d8_769x769_40k_cityscapes-20201215_081348.log.json) | +| DMNet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 79.07 | 80.22 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x1024_80k_cityscapes/dmnet_r50-d8_512x1024_80k_cityscapes_20201215_053728-3c8893b9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x1024_80k_cityscapes/dmnet_r50-d8_512x1024_80k_cityscapes-20201215_053728.log.json) | +| DMNet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 79.64 | 80.67 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x1024_80k_cityscapes/dmnet_r101-d8_512x1024_80k_cityscapes_20201215_031718-fa081cb8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x1024_80k_cityscapes/dmnet_r101-d8_512x1024_80k_cityscapes-20201215_031718.log.json) | +| DMNet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.22 | 80.55 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_769x769_80k_cityscapes/dmnet_r50-d8_769x769_80k_cityscapes_20201215_034006-6060840e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_769x769_80k_cityscapes/dmnet_r50-d8_769x769_80k_cityscapes-20201215_034006.log.json) | +| DMNet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 79.19 | 80.65 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_769x769_80k_cityscapes/dmnet_r101-d8_769x769_80k_cityscapes_20201215_082810-7f0de59a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_769x769_80k_cityscapes/dmnet_r101-d8_769x769_80k_cityscapes-20201215_082810.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DMNet | R-50-D8 | 512x512 | 80000 | 9.4 | 20.95 | V100 | 42.37 | 43.62 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x512_80k_ade20k/dmnet_r50-d8_512x512_80k_ade20k_20201215_144744-f89092a6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x512_80k_ade20k/dmnet_r50-d8_512x512_80k_ade20k-20201215_144744.log.json) | +| DMNet | R-101-D8 | 512x512 | 80000 | 13.0 | 13.88 | V100 | 45.34 | 46.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x512_80k_ade20k/dmnet_r101-d8_512x512_80k_ade20k_20201215_104812-bfa45311.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x512_80k_ade20k/dmnet_r101-d8_512x512_80k_ade20k-20201215_104812.log.json) | +| DMNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 43.15 | 44.17 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x512_160k_ade20k/dmnet_r50-d8_512x512_160k_ade20k_20201215_115313-025ab3f9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x512_160k_ade20k/dmnet_r50-d8_512x512_160k_ade20k-20201215_115313.log.json) | +| DMNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 45.42 | 46.76 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet/dmnet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x512_160k_ade20k/dmnet_r101-d8_512x512_160k_ade20k_20201215_111145-a0bc02ef.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x512_160k_ade20k/dmnet_r101-d8_512x512_160k_ade20k-20201215_111145.log.json) | + +## Citation + +```bibtex +@InProceedings{He_2019_ICCV, +author = {He, Junjun and Deng, Zhongying and Qiao, Yu}, +title = {Dynamic Multi-Scale Filters for Semantic Segmentation}, +booktitle = {Proceedings of the IEEE/CVF International Conference on Computer Vision (ICCV)}, +month = {October}, +year = {2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..9832b62 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './dmnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..03346c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './dmnet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..fd7e9ac --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './dmnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..2205e60 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './dmnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..23e215b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './dmnet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..5c25587 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './dmnet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..aa86b01 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/dmnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..8c2dbf3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/dmnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..bc21606 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/dmnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..e32ae71 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/dmnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..71d0a04 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/dmnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..727bed0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/dmnet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/dmnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/dmnet/metafile.yaml new file mode 100644 index 0000000..7f5e536 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/metafile.yaml @@ -0,0 +1,296 @@ +Collections: +- Name: DMNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + README: configs/dmnet/README.md + Frameworks: + - PyTorch +Models: +- Name: dmnet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.78 + mIoU(ms+flip): 79.14 + Config: configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DMNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x1024_40k_cityscapes/dmnet_r50-d8_512x1024_40k_cityscapes_20201215_042326-615373cf.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x1024_40k_cityscapes/dmnet_r50-d8_512x1024_40k_cityscapes-20201215_042326.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.37 + mIoU(ms+flip): 79.72 + Config: configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DMNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x1024_40k_cityscapes/dmnet_r101-d8_512x1024_40k_cityscapes_20201215_043100-8291e976.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x1024_40k_cityscapes/dmnet_r101-d8_512x1024_40k_cityscapes-20201215_043100.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.49 + mIoU(ms+flip): 80.27 + Config: configs/dmnet/dmnet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DMNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_769x769_40k_cityscapes/dmnet_r50-d8_769x769_40k_cityscapes_20201215_093706-e7f0e23e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_769x769_40k_cityscapes/dmnet_r50-d8_769x769_40k_cityscapes-20201215_093706.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.62 + mIoU(ms+flip): 78.94 + Config: configs/dmnet/dmnet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DMNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_769x769_40k_cityscapes/dmnet_r101-d8_769x769_40k_cityscapes_20201215_081348-a74261f6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_769x769_40k_cityscapes/dmnet_r101-d8_769x769_40k_cityscapes-20201215_081348.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.07 + mIoU(ms+flip): 80.22 + Config: configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DMNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x1024_80k_cityscapes/dmnet_r50-d8_512x1024_80k_cityscapes_20201215_053728-3c8893b9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x1024_80k_cityscapes/dmnet_r50-d8_512x1024_80k_cityscapes-20201215_053728.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.64 + mIoU(ms+flip): 80.67 + Config: configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DMNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x1024_80k_cityscapes/dmnet_r101-d8_512x1024_80k_cityscapes_20201215_031718-fa081cb8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x1024_80k_cityscapes/dmnet_r101-d8_512x1024_80k_cityscapes-20201215_031718.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.22 + mIoU(ms+flip): 80.55 + Config: configs/dmnet/dmnet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DMNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_769x769_80k_cityscapes/dmnet_r50-d8_769x769_80k_cityscapes_20201215_034006-6060840e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_769x769_80k_cityscapes/dmnet_r50-d8_769x769_80k_cityscapes-20201215_034006.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.19 + mIoU(ms+flip): 80.65 + Config: configs/dmnet/dmnet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DMNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_769x769_80k_cityscapes/dmnet_r101-d8_769x769_80k_cityscapes_20201215_082810-7f0de59a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_769x769_80k_cityscapes/dmnet_r101-d8_769x769_80k_cityscapes-20201215_082810.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.37 + mIoU(ms+flip): 43.62 + Config: configs/dmnet/dmnet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DMNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x512_80k_ade20k/dmnet_r50-d8_512x512_80k_ade20k_20201215_144744-f89092a6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x512_80k_ade20k/dmnet_r50-d8_512x512_80k_ade20k-20201215_144744.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.34 + mIoU(ms+flip): 46.13 + Config: configs/dmnet/dmnet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DMNet + Training Resources: 4x V100 GPUS + Memory (GB): 13.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x512_80k_ade20k/dmnet_r101-d8_512x512_80k_ade20k_20201215_104812-bfa45311.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x512_80k_ade20k/dmnet_r101-d8_512x512_80k_ade20k-20201215_104812.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.15 + mIoU(ms+flip): 44.17 + Config: configs/dmnet/dmnet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DMNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x512_160k_ade20k/dmnet_r50-d8_512x512_160k_ade20k_20201215_115313-025ab3f9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r50-d8_512x512_160k_ade20k/dmnet_r50-d8_512x512_160k_ade20k-20201215_115313.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch +- Name: dmnet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: DMNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.42 + mIoU(ms+flip): 46.76 + Config: configs/dmnet/dmnet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DMNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x512_160k_ade20k/dmnet_r101-d8_512x512_160k_ade20k_20201215_111145-a0bc02ef.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dmnet/dmnet_r101-d8_512x512_160k_ade20k/dmnet_r101-d8_512x512_160k_ade20k-20201215_111145.log.json + Paper: + Title: Dynamic Multi-scale Filters for Semantic Segmentation + URL: https://openaccess.thecvf.com/content_ICCV_2019/papers/He_Dynamic_Multi-Scale_Filters_for_Semantic_Segmentation_ICCV_2019_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dm_head.py#L93 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/dmnet/my_dmnet_r50xb4-no_slide_test-160k_MyDataset-512x512.py b/Seg_All_In_One_MMSeg/configs/dmnet/my_dmnet_r50xb4-no_slide_test-160k_MyDataset-512x512.py new file mode 100644 index 0000000..0e9b03b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dmnet/my_dmnet_r50xb4-no_slide_test-160k_MyDataset-512x512.py @@ -0,0 +1,46 @@ +_base_ = [ + '../_base_/models/dmnet_r50-d8.py', + '../_base_/datasets/my_dataset.py', #换成自己定义的数据集 + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + pretrained='open-mmlab://resnet50_v1c', + backbone=dict(depth=50), + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=20, # TODO 设置不同分类种类 + loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0), # TODO 设置不同分类种类,它根据预测结果和真实标签的重叠区域来度量相似性 + # align_corners=True, + align_corners=False, # 在不用slide时 + ), + auxiliary_head=dict( + num_classes=20, # TODO 设置不同分类种类 + loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0), # TODO 设置不同分类种类,它根据预测结果和真实标签的重叠区域来度量相似性 + # align_corners=True, + align_corners=False, # 在不用slide时 + ), + # test_cfg=dict(mode='slide', crop_size=(512, 512), stride=(512, 512)) +) + +# optimizer(优化器设计)TODO +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0005), + clip_grad=dict(max_norm=1, norm_type=2)) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/README.md b/Seg_All_In_One_MMSeg/configs/dnlnet/README.md new file mode 100644 index 0000000..6835ffd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/README.md @@ -0,0 +1,62 @@ +# DNLNet + +> [Disentangled Non-Local Neural Networks](https://arxiv.org/abs/2006.06668) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +The non-local block is a popular module for strengthening the context modeling ability of a regular convolutional neural network. This paper first studies the non-local block in depth, where we find that its attention computation can be split into two terms, a whitened pairwise term accounting for the relationship between two pixels and a unary term representing the saliency of every pixel. We also observe that the two terms trained alone tend to model different visual clues, e.g. the whitened pairwise term learns within-region relationships while the unary term learns salient boundaries. However, the two terms are tightly coupled in the non-local block, which hinders the learning of each. Based on these findings, we present the disentangled non-local block, where the two terms are decoupled to facilitate learning for both terms. We demonstrate the effectiveness of the decoupled design on various tasks, such as semantic segmentation on Cityscapes, ADE20K and PASCAL Context, object detection on COCO, and action recognition on Kinetics. + + + +
+ +
+ +## Results and models (in progress) + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DNLNet | R-50-D8 | 512x1024 | 40000 | 7.3 | 2.56 | V100 | 78.61 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x1024_40k_cityscapes/dnl_r50-d8_512x1024_40k_cityscapes_20200904_233629-53d4ea93.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x1024_40k_cityscapes/dnl_r50-d8_512x1024_40k_cityscapes-20200904_233629.log.json) | +| DNLNet | R-101-D8 | 512x1024 | 40000 | 10.9 | 1.96 | V100 | 78.31 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x1024_40k_cityscapes/dnl_r101-d8_512x1024_40k_cityscapes_20200904_233629-9928ffef.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x1024_40k_cityscapes/dnl_r101-d8_512x1024_40k_cityscapes-20200904_233629.log.json) | +| DNLNet | R-50-D8 | 769x769 | 40000 | 9.2 | 1.50 | V100 | 78.44 | 80.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_769x769_40k_cityscapes/dnl_r50-d8_769x769_40k_cityscapes_20200820_232206-0f283785.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_769x769_40k_cityscapes/dnl_r50-d8_769x769_40k_cityscapes-20200820_232206.log.json) | +| DNLNet | R-101-D8 | 769x769 | 40000 | 12.6 | 1.02 | V100 | 76.39 | 77.77 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_769x769_40k_cityscapes/dnl_r101-d8_769x769_40k_cityscapes_20200820_171256-76c596df.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_769x769_40k_cityscapes/dnl_r101-d8_769x769_40k_cityscapes-20200820_171256.log.json) | +| DNLNet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 79.33 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x1024_80k_cityscapes/dnl_r50-d8_512x1024_80k_cityscapes_20200904_233629-58b2f778.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x1024_80k_cityscapes/dnl_r50-d8_512x1024_80k_cityscapes-20200904_233629.log.json) | +| DNLNet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 80.41 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x1024_80k_cityscapes/dnl_r101-d8_512x1024_80k_cityscapes_20200904_233629-758e2dd4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x1024_80k_cityscapes/dnl_r101-d8_512x1024_80k_cityscapes-20200904_233629.log.json) | +| DNLNet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.36 | 80.70 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_769x769_80k_cityscapes/dnl_r50-d8_769x769_80k_cityscapes_20200820_011925-366bc4c7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_769x769_80k_cityscapes/dnl_r50-d8_769x769_80k_cityscapes-20200820_011925.log.json) | +| DNLNet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 79.41 | 80.68 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_769x769_80k_cityscapes/dnl_r101-d8_769x769_80k_cityscapes_20200821_051111-95ff84ab.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_769x769_80k_cityscapes/dnl_r101-d8_769x769_80k_cityscapes-20200821_051111.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| DNLNet | R-50-D8 | 512x512 | 80000 | 8.8 | 20.66 | V100 | 41.76 | 42.99 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x512_80k_ade20k/dnl_r50-d8_512x512_80k_ade20k_20200826_183354-1cf6e0c1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x512_80k_ade20k/dnl_r50-d8_512x512_80k_ade20k-20200826_183354.log.json) | +| DNLNet | R-101-D8 | 512x512 | 80000 | 12.8 | 12.54 | V100 | 43.76 | 44.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x512_80k_ade20k/dnl_r101-d8_512x512_80k_ade20k_20200826_183354-d820d6ea.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x512_80k_ade20k/dnl_r101-d8_512x512_80k_ade20k-20200826_183354.log.json) | +| DNLNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 41.87 | 43.01 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x512_160k_ade20k/dnl_r50-d8_512x512_160k_ade20k_20200826_183350-37837798.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x512_160k_ade20k/dnl_r50-d8_512x512_160k_ade20k-20200826_183350.log.json) | +| DNLNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 44.25 | 45.78 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet/dnl_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x512_160k_ade20k/dnl_r101-d8_512x512_160k_ade20k_20200826_183350-ed522c61.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x512_160k_ade20k/dnl_r101-d8_512x512_160k_ade20k-20200826_183350.log.json) | + +## Notes + +This example is to reproduce ["Disentangled Non-Local Neural Networks"](https://arxiv.org/abs/2006.06668) for semantic segmentation. It is still in progress. + +## Citation + +```bibtex +@misc{yin2020disentangled, + title={Disentangled Non-Local Neural Networks}, + author={Minghao Yin and Zhuliang Yao and Yue Cao and Xiu Li and Zheng Zhang and Stephen Lin and Han Hu}, + year={2020}, + booktitle={ECCV} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..310d84e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './dnl_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..a94dbb8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './dnl_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..f9b6d5e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './dnl_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..9c7d557 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './dnl_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..1edc26f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './dnl_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..d29c17e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './dnl_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..be38992 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/dnl_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..9eaaa63 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/dnl_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..2e43178 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/dnl_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..cb379c1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,16 @@ +_base_ = [ + '../_base_/models/dnl_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) + +optim_wrapper = dict( + paramwise_cfg=dict( + custom_keys=dict(theta=dict(wd_mult=0.), phi=dict(wd_mult=0.)))) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..b2ae2a8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/dnl_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..f310a4e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/dnl_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/dnl_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/dnlnet/metafile.yaml new file mode 100644 index 0000000..22e48d3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/metafile.yaml @@ -0,0 +1,292 @@ +Collections: +- Name: DNLNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + README: configs/dnlnet/README.md + Frameworks: + - PyTorch +Models: +- Name: dnl_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.61 + Config: configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x1024_40k_cityscapes/dnl_r50-d8_512x1024_40k_cityscapes_20200904_233629-53d4ea93.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x1024_40k_cityscapes/dnl_r50-d8_512x1024_40k_cityscapes-20200904_233629.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.31 + Config: configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x1024_40k_cityscapes/dnl_r101-d8_512x1024_40k_cityscapes_20200904_233629-9928ffef.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x1024_40k_cityscapes/dnl_r101-d8_512x1024_40k_cityscapes-20200904_233629.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.44 + mIoU(ms+flip): 80.27 + Config: configs/dnlnet/dnl_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_769x769_40k_cityscapes/dnl_r50-d8_769x769_40k_cityscapes_20200820_232206-0f283785.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_769x769_40k_cityscapes/dnl_r50-d8_769x769_40k_cityscapes-20200820_232206.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.39 + mIoU(ms+flip): 77.77 + Config: configs/dnlnet/dnl_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_769x769_40k_cityscapes/dnl_r101-d8_769x769_40k_cityscapes_20200820_171256-76c596df.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_769x769_40k_cityscapes/dnl_r101-d8_769x769_40k_cityscapes-20200820_171256.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.33 + Config: configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x1024_80k_cityscapes/dnl_r50-d8_512x1024_80k_cityscapes_20200904_233629-58b2f778.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x1024_80k_cityscapes/dnl_r50-d8_512x1024_80k_cityscapes-20200904_233629.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.41 + Config: configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x1024_80k_cityscapes/dnl_r101-d8_512x1024_80k_cityscapes_20200904_233629-758e2dd4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x1024_80k_cityscapes/dnl_r101-d8_512x1024_80k_cityscapes-20200904_233629.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.36 + mIoU(ms+flip): 80.7 + Config: configs/dnlnet/dnl_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_769x769_80k_cityscapes/dnl_r50-d8_769x769_80k_cityscapes_20200820_011925-366bc4c7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_769x769_80k_cityscapes/dnl_r50-d8_769x769_80k_cityscapes-20200820_011925.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.41 + mIoU(ms+flip): 80.68 + Config: configs/dnlnet/dnl_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_769x769_80k_cityscapes/dnl_r101-d8_769x769_80k_cityscapes_20200821_051111-95ff84ab.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_769x769_80k_cityscapes/dnl_r101-d8_769x769_80k_cityscapes-20200821_051111.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.76 + mIoU(ms+flip): 42.99 + Config: configs/dnlnet/dnl_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x512_80k_ade20k/dnl_r50-d8_512x512_80k_ade20k_20200826_183354-1cf6e0c1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x512_80k_ade20k/dnl_r50-d8_512x512_80k_ade20k-20200826_183354.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.76 + mIoU(ms+flip): 44.91 + Config: configs/dnlnet/dnl_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x512_80k_ade20k/dnl_r101-d8_512x512_80k_ade20k_20200826_183354-d820d6ea.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x512_80k_ade20k/dnl_r101-d8_512x512_80k_ade20k-20200826_183354.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.87 + mIoU(ms+flip): 43.01 + Config: configs/dnlnet/dnl_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x512_160k_ade20k/dnl_r50-d8_512x512_160k_ade20k_20200826_183350-37837798.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r50-d8_512x512_160k_ade20k/dnl_r50-d8_512x512_160k_ade20k-20200826_183350.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch +- Name: dnl_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: DNLNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.25 + mIoU(ms+flip): 45.78 + Config: configs/dnlnet/dnl_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - DNLNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x512_160k_ade20k/dnl_r101-d8_512x512_160k_ade20k_20200826_183350-ed522c61.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dnlnet/dnl_r101-d8_512x512_160k_ade20k/dnl_r101-d8_512x512_160k_ade20k-20200826_183350.log.json + Paper: + Title: Disentangled Non-Local Neural Networks + URL: https://arxiv.org/abs/2006.06668 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dnl_head.py#L88 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/dnlnet/my_dnlnet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py b/Seg_All_In_One_MMSeg/configs/dnlnet/my_dnlnet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py new file mode 100644 index 0000000..33893ee --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dnlnet/my_dnlnet_r50_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py @@ -0,0 +1,105 @@ +_base_ = [ + '../_base_/models/dnl_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=True, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 512), + stride=(341, 341), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/dpt/README.md b/Seg_All_In_One_MMSeg/configs/dpt/README.md new file mode 100644 index 0000000..b3a5573 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dpt/README.md @@ -0,0 +1,67 @@ +# DPT + +> [Vision Transformer for Dense Prediction](https://arxiv.org/abs/2103.13413) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +We introduce dense vision transformers, an architecture that leverages vision transformers in place of convolutional networks as a backbone for dense prediction tasks. We assemble tokens from various stages of the vision transformer into image-like representations at various resolutions and progressively combine them into full-resolution predictions using a convolutional decoder. The transformer backbone processes representations at a constant and relatively high resolution and has a global receptive field at every stage. These properties allow the dense vision transformer to provide finer-grained and more globally coherent predictions when compared to fully-convolutional networks. Our experiments show that this architecture yields substantial improvements on dense prediction tasks, especially when a large amount of training data is available. For monocular depth estimation, we observe an improvement of up to 28% in relative performance when compared to a state-of-the-art fully-convolutional network. When applied to semantic segmentation, dense vision transformers set a new state of the art on ADE20K with 49.02% mIoU. We further show that the architecture can be fine-tuned on smaller datasets such as NYUv2, KITTI, and Pascal Context where it also sets the new state of the art. Our models are available at [this https URL](https://github.com/isl-org/DPT). + + + +
+ +
+ +## Usage + +To use other repositories' pre-trained models, it is necessary to convert keys. + +We provide a script [`vit2mmseg.py`](../../tools/model_converters/vit2mmseg.py) in the tools directory to convert the key of models from [timm](https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py) to MMSegmentation style. + +```shell +python tools/model_converters/vit2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +E.g. + +```shell +python tools/model_converters/vit2mmseg.py https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_base_p16_224-80ecf9dd.pth pretrain/jx_vit_base_p16_224-80ecf9dd.pth +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| DPT | ViT-B | 512x512 | 160000 | 8.09 | 10.41 | V100 | 46.97 | 48.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dpt/dpt_vit-b16_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/dpt/dpt_vit-b16_512x512_160k_ade20k/dpt_vit-b16_512x512_160k_ade20k-db31cf52.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/dpt/dpt_vit-b16_512x512_160k_ade20k/dpt_vit-b16_512x512_160k_ade20k-20210809_172025.log.json) | + +## Citation + +```bibtex +@article{dosoViTskiy2020, + title={An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale}, + author={DosoViTskiy, Alexey and Beyer, Lucas and Kolesnikov, Alexander and Weissenborn, Dirk and Zhai, Xiaohua and Unterthiner, Thomas and Dehghani, Mostafa and Minderer, Matthias and Heigold, Georg and Gelly, Sylvain and Uszkoreit, Jakob and Houlsby, Neil}, + journal={arXiv preprint arXiv:2010.11929}, + year={2020} +} + +@article{Ranftl2021, + author = {Ren\'{e} Ranftl and Alexey Bochkovskiy and Vladlen Koltun}, + title = {Vision Transformers for Dense Prediction}, + journal = {ArXiv preprint}, + year = {2021}, +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/dpt/dpt_vit-b16_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/dpt/dpt_vit-b16_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..28b4784 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dpt/dpt_vit-b16_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,40 @@ +_base_ = [ + '../_base_/models/dpt_vit-b16.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + # 自定义参数 + paramwise_cfg=dict( + custom_keys={ + 'pos_embed': dict(decay_mult=0.), # 位置嵌入(positional embeddings)。decay_mult=0. 意味着对这些嵌入不应用权重衰减 + 'cls_token': dict(decay_mult=0.), # 是在某些模型(如 Transformer 或 BERT)中,用于分类任务的特定 token + 'norm': dict(decay_mult=0.) # 对归一化层的参数也禁用了权重衰减 + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/dpt/metafile.yaml b/Seg_All_In_One_MMSeg/configs/dpt/metafile.yaml new file mode 100644 index 0000000..b721e04 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dpt/metafile.yaml @@ -0,0 +1,37 @@ +Collections: +- Name: DPT + License: Apache License 2.0 + Metadata: + Training Data: + - ADE20K + Paper: + Title: Vision Transformer for Dense Prediction + URL: https://arxiv.org/abs/2103.13413 + README: configs/dpt/README.md + Frameworks: + - PyTorch +Models: +- Name: dpt_vit-b16_8xb2-160k_ade20k-512x512 + In Collection: DPT + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.97 + mIoU(ms+flip): 48.34 + Config: configs/dpt/dpt_vit-b16_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ViT-B + - DPT + Training Resources: 8x V100 GPUS + Memory (GB): 8.09 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/dpt/dpt_vit-b16_512x512_160k_ade20k/dpt_vit-b16_512x512_160k_ade20k-db31cf52.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/dpt/dpt_vit-b16_512x512_160k_ade20k/dpt_vit-b16_512x512_160k_ade20k-20210809_172025.log.json + Paper: + Title: Vision Transformer for Dense Prediction + URL: https://arxiv.org/abs/2103.13413 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/dpt_head.py#L215 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1-40k_check_4000_my_dataset_model-1024x1024.py b/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1-40k_check_4000_my_dataset_model-1024x1024.py new file mode 100644 index 0000000..472946a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1-40k_check_4000_my_dataset_model-1024x1024.py @@ -0,0 +1,95 @@ +_base_ = [ + '../_base_/models/dpt_vit-b16.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (1024, 1024) + +data_preprocessor = dict( + size=(1024, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(1024, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/vit-b16_p16_224-80ecf9dd.pth', + decode_head=dict( + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0 + ), + num_classes=36, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), + paramwise_cfg=dict( + pos_embed=dict( + decay_mult=0.0, + ), + cls_token=dict( + decay_mult=0.0, + ), + norm=dict( + decay_mult=0.0, + ), + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b1-40k_check_4000_my_dataset_model-1024x1024.py b/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b1-40k_check_4000_my_dataset_model-1024x1024.py new file mode 100644 index 0000000..1d140cb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b1-40k_check_4000_my_dataset_model-1024x1024.py @@ -0,0 +1,101 @@ +_base_ = [ + '../_base_/models/dpt_vit-b16.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (1024, 1024) + +data_preprocessor = dict( + size=(1024, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(1024, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/vit-b16_p16_224-80ecf9dd.pth', + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=36, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), + paramwise_cfg=dict( + pos_embed=dict( + decay_mult=0.0, + ), + cls_token=dict( + decay_mult=0.0, + ), + norm=dict( + decay_mult=0.0, + ), + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=1, +) + diff --git a/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b1-40k_check_4000_my_dataset_model-512x512.py b/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b1-40k_check_4000_my_dataset_model-512x512.py new file mode 100644 index 0000000..1c9840b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b1-40k_check_4000_my_dataset_model-512x512.py @@ -0,0 +1,101 @@ +_base_ = [ + '../_base_/models/dpt_vit-b16.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/vit-b16_p16_224-80ecf9dd.pth', + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=36, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), + paramwise_cfg=dict( + pos_embed=dict( + decay_mult=0.0, + ), + cls_token=dict( + decay_mult=0.0, + ), + norm=dict( + decay_mult=0.0, + ), + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=28, +) + diff --git a/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b4-40k_check_4000_my_dataset_model-1024x1024.py b/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b4-40k_check_4000_my_dataset_model-1024x1024.py new file mode 100644 index 0000000..3ff0458 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dpt/my_dpt_vit_g1_b4-40k_check_4000_my_dataset_model-1024x1024.py @@ -0,0 +1,101 @@ +_base_ = [ + '../_base_/models/dpt_vit-b16.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (1024, 1024) + +data_preprocessor = dict( + size=(1024, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(1024, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/vit-b16_p16_224-80ecf9dd.pth', + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=36, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), + paramwise_cfg=dict( + pos_embed=dict( + decay_mult=0.0, + ), + cls_token=dict( + decay_mult=0.0, + ), + norm=dict( + decay_mult=0.0, + ), + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=4, +) + diff --git a/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/README.md b/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/README.md new file mode 100644 index 0000000..e564cff --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/README.md @@ -0,0 +1,103 @@ +# DSDL: Standard Description Language for DataSet + + + +## Abstract + + + +Data is the cornerstone of artificial intelligence. The efficiency of data acquisition, exchange, and application directly impacts the advances in technologies and applications. Over the long history of AI, a vast quantity of data sets have been developed and distributed. However, these datasets are defined in very different forms, which incurs significant overhead when it comes to exchange, integration, and utilization -- it is often the case that one needs to develop a new customized tool or script in order to incorporate a new dataset into a workflow. + +To overcome such difficulties, we develop **Data Set Description Language (DSDL)**. More details please visit our [official documents](https://opendatalab.github.io/dsdl-docs/getting_started/overview/), dsdl datasets can be downloaded from our platform [OpenDataLab](https://opendatalab.com/). + + + +## Steps + +- install dsdl and opendatalab: + + ``` + pip install dsdl + pip install opendatalab + ``` + +- install mmseg and pytorch: + please refer this [installation documents](https://mmsegmentation.readthedocs.io/en/latest/get_started.html). + +- prepare dsdl dataset (take voc2012 as an example) + + - dowaload dsdl dataset (you will need an opendatalab account to do so. [register one now](https://opendatalab.com/)) + + ``` + cd data + + odl login + odl get PASCAL_VOC2012 + ``` + + usually, dataset is compressed on opendatalab platform, the downloaded voc 2012 dataset should be like this: + + ``` + data/ + ├── PASCAL_VOC2012 + │   ├── dsdl + │   │   ├── dsdl_Det_full.zip + │   │   └── dsdl_SemSeg_full.zip + │   ├── raw + │   │   ├── VOC2012test.tar + │   │   ├── VOCdevkit_18-May-2011.tar + │   │   └── VOCtrainval_11-May-2012.tar + │   └── README.md + └── ... + ``` + + - decompress dataset + + ``` + cd dsdl + unzip dsdl_SemSeg_full.zip + ``` + + as we do not need detection dsdl files, we only decompress the semantic segmentation files here. + + ``` + cd ../raw + tar -xvf VOCtrainval_11-May-2012.tar + tar -xvf VOC2012test.tar + + cd ../../ + ``` + +- change traning config + + open the [voc config file](voc.py) and set some file paths as below: + + ``` + data_root = 'data/PASCAL_VOC2012' + img_prefix = 'raw/VOCdevkit/VOC2012' + train_ann = 'dsdl/dsdl_SemSeg_full/set-train/train.yaml' + val_ann = 'dsdl/dsdl_SemSeg_full/set-val/val.yaml' + ``` + + as dsdl datasets with one task using one dataloader, we can simplly change these file paths to train a model on a different dataset. + +- train: + + - using single gpu: + + ``` + python tools/train.py {config_file} + ``` + + - using slrum: + + ``` + ./tools/slurm_train.sh {partition} {job_name} {config_file} {work_dir} {gpu_nums} + ``` + +## Test Results + +| Datasets | Model | mIoU(%) | Config | +| :--------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----: | :-----------------------: | +| voc2012 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x512_20k_voc12aug/deeplabv3_r50-d8_512x512_20k_voc12aug_20200617_010906-596905ef.pth) | 76.73 | [config](./voc.py) | +| cityscapes | [model](https://download.openmmlab.com/mmsegmentation/v0.5/deeplabv3/deeplabv3_r50-d8_512x1024_40k_cityscapes/deeplabv3_r50-d8_512x1024_40k_cityscapes_20200605_022449-acadc2f8.pth) | 79.01 | [config](./cityscapes.py) | diff --git a/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/cityscapes.py b/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/cityscapes.py new file mode 100644 index 0000000..94ccc06 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/cityscapes.py @@ -0,0 +1,70 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] + +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +# dataset settings +dataset_type = 'DSDLSegDataset' +data_root = 'data/CityScapes' +img_prefix = 'raw/CityScapes' +train_ann = 'dsdl/dsdl_SemSeg_full/set-train/train.yaml' +val_ann = 'dsdl/dsdl_SemSeg_full/set-val/val.yaml' + +used_labels = [ + 'road', 'sidewalk', 'building', 'wall', 'fence', 'pole', 'traffic_light', + 'traffic_sign', 'vegetation', 'terrain', 'sky', 'person', 'rider', 'car', + 'truck', 'bus', 'train', 'motorcycle', 'bicycle' +] + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path=img_prefix, seg_map_path=img_prefix), + ann_file=train_ann, + used_labels=used_labels, + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path=img_prefix, seg_map_path=img_prefix), + ann_file=val_ann, + used_labels=used_labels, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/voc.py b/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/voc.py new file mode 100644 index 0000000..c1895f7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/dsdl【数据集描述语言】/voc.py @@ -0,0 +1,65 @@ +_base_ = [ + '../_base_/models/deeplabv3_r50-d8.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] + +# dataset settings +dataset_type = 'DSDLSegDataset' +data_root = 'data/PASCAL_VOC2012' +img_prefix = 'raw/VOCdevkit/VOC2012' +train_ann = 'dsdl/dsdl_SemSeg_full/set-train/train.yaml' +val_ann = 'dsdl/dsdl_SemSeg_full/set-val/val.yaml' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path=img_prefix, seg_map_path=img_prefix), + ann_file=train_ann, + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path=img_prefix, seg_map_path=img_prefix), + ann_file=val_ann, + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator + +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/emanet/README.md b/Seg_All_In_One_MMSeg/configs/emanet/README.md new file mode 100644 index 0000000..8ffaf47 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/emanet/README.md @@ -0,0 +1,46 @@ +# EMANet + +> [Expectation-Maximization Attention Networks for Semantic Segmentation](https://arxiv.org/abs/1907.13426) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Self-attention mechanism has been widely used for various tasks. It is designed to compute the representation of each position by a weighted sum of the features at all positions. Thus, it can capture long-range relations for computer vision tasks. However, it is computationally consuming. Since the attention maps are computed w.r.t all other positions. In this paper, we formulate the attention mechanism into an expectation-maximization manner and iteratively estimate a much more compact set of bases upon which the attention maps are computed. By a weighted summation upon these bases, the resulting representation is low-rank and deprecates noisy information from the input. The proposed Expectation-Maximization Attention (EMA) module is robust to the variance of input and is also friendly in memory and computation. Moreover, we set up the bases maintenance and normalization methods to stabilize its training procedure. We conduct extensive experiments on popular semantic segmentation benchmarks including PASCAL VOC, PASCAL Context and COCO Stuff, on which we set new records. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| EMANet | R-50-D8 | 512x1024 | 80000 | 5.4 | 4.58 | V100 | 77.59 | 79.44 | [config](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/emanet/eemanet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r50-d8_512x1024_80k_cityscapes/emanet_r50-d8_512x1024_80k_cityscapes_20200901_100301-c43fcef1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r50-d8_512x1024_80k_cityscapes/emanet_r50-d8_512x1024_80k_cityscapes-20200901_100301.log.json) | +| EMANet | R-101-D8 | 512x1024 | 80000 | 6.2 | 2.87 | V100 | 79.10 | 81.21 | [config](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r101-d8_512x1024_80k_cityscapes/emanet_r101-d8_512x1024_80k_cityscapes_20200901_100301-2d970745.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r101-d8_512x1024_80k_cityscapes/emanet_r101-d8_512x1024_80k_cityscapes-20200901_100301.log.json) | +| EMANet | R-50-D8 | 769x769 | 80000 | 8.9 | 1.97 | V100 | 79.33 | 80.49 | [config](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r50-d8_769x769_80k_cityscapes/emanet_r50-d8_769x769_80k_cityscapes_20200901_100301-16f8de52.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r50-d8_769x769_80k_cityscapes/emanet_r50-d8_769x769_80k_cityscapes-20200901_100301.log.json) | +| EMANet | R-101-D8 | 769x769 | 80000 | 10.1 | 1.22 | V100 | 79.62 | 81.00 | [config](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r101-d8_769x769_80k_cityscapes/emanet_r101-d8_769x769_80k_cityscapes_20200901_100301-47a324ce.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r101-d8_769x769_80k_cityscapes/emanet_r101-d8_769x769_80k_cityscapes-20200901_100301.log.json) | + +## Citation + +```bibtex +@inproceedings{li2019expectation, + title={Expectation-maximization attention networks for semantic segmentation}, + author={Li, Xia and Zhong, Zhisheng and Wu, Jianlong and Yang, Yibo and Lin, Zhouchen and Liu, Hong}, + booktitle={Proceedings of the IEEE International Conference on Computer Vision}, + pages={9167--9176}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..ee3a3b5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './emanet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..7319a3e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './emanet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..6198e1f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/emanet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..a8e4521 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/emanet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/emanet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/emanet/metafile.yaml new file mode 100644 index 0000000..b2a6b09 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/emanet/metafile.yaml @@ -0,0 +1,109 @@ +Collections: +- Name: EMANet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: Expectation-Maximization Attention Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1907.13426 + README: configs/emanet/README.md + Frameworks: + - PyTorch +Models: +- Name: eemanet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: EMANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.59 + mIoU(ms+flip): 79.44 + Config: configs/emanet/eemanet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - EMANet + Training Resources: 4x V100 GPUS + Memory (GB): 5.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r50-d8_512x1024_80k_cityscapes/emanet_r50-d8_512x1024_80k_cityscapes_20200901_100301-c43fcef1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r50-d8_512x1024_80k_cityscapes/emanet_r50-d8_512x1024_80k_cityscapes-20200901_100301.log.json + Paper: + Title: Expectation-Maximization Attention Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1907.13426 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ema_head.py#L80 + Framework: PyTorch +- Name: emanet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: EMANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.1 + mIoU(ms+flip): 81.21 + Config: configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - EMANet + Training Resources: 4x V100 GPUS + Memory (GB): 6.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r101-d8_512x1024_80k_cityscapes/emanet_r101-d8_512x1024_80k_cityscapes_20200901_100301-2d970745.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r101-d8_512x1024_80k_cityscapes/emanet_r101-d8_512x1024_80k_cityscapes-20200901_100301.log.json + Paper: + Title: Expectation-Maximization Attention Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1907.13426 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ema_head.py#L80 + Framework: PyTorch +- Name: emanet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: EMANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.33 + mIoU(ms+flip): 80.49 + Config: configs/emanet/emanet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - EMANet + Training Resources: 4x V100 GPUS + Memory (GB): 8.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r50-d8_769x769_80k_cityscapes/emanet_r50-d8_769x769_80k_cityscapes_20200901_100301-16f8de52.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r50-d8_769x769_80k_cityscapes/emanet_r50-d8_769x769_80k_cityscapes-20200901_100301.log.json + Paper: + Title: Expectation-Maximization Attention Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1907.13426 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ema_head.py#L80 + Framework: PyTorch +- Name: emanet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: EMANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.62 + mIoU(ms+flip): 81.0 + Config: configs/emanet/emanet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - EMANet + Training Resources: 4x V100 GPUS + Memory (GB): 10.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r101-d8_769x769_80k_cityscapes/emanet_r101-d8_769x769_80k_cityscapes_20200901_100301-47a324ce.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/emanet/emanet_r101-d8_769x769_80k_cityscapes/emanet_r101-d8_769x769_80k_cityscapes-20200901_100301.log.json + Paper: + Title: Expectation-Maximization Attention Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1907.13426 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ema_head.py#L80 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/emanet/my_emanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py b/Seg_All_In_One_MMSeg/configs/emanet/my_emanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py new file mode 100644 index 0000000..49d975d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/emanet/my_emanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py @@ -0,0 +1,105 @@ +_base_ = [ + '../_base_/models/emanet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=True, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 1024), + stride=(341, 682), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/emanet/my_emanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/emanet/my_emanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..6c42e09 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/emanet/my_emanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,103 @@ +_base_ = [ + '../_base_/models/emanet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/encnet/README.md b/Seg_All_In_One_MMSeg/configs/encnet/README.md new file mode 100644 index 0000000..ff09bc3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/README.md @@ -0,0 +1,59 @@ +# EncNet + +> [Context Encoding for Semantic Segmentation](https://arxiv.org/abs/1803.08904) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Recent work has made significant progress in improving spatial resolution for pixelwise labeling with Fully Convolutional Network (FCN) framework by employing Dilated/Atrous convolution, utilizing multi-scale features and refining boundaries. In this paper, we explore the impact of global contextual information in semantic segmentation by introducing the Context Encoding Module, which captures the semantic context of scenes and selectively highlights class-dependent featuremaps. The proposed Context Encoding Module significantly improves semantic segmentation results with only marginal extra computation cost over FCN. Our approach has achieved new state-of-the-art results 51.7% mIoU on PASCAL-Context, 85.9% mIoU on PASCAL VOC 2012. Our single model achieves a final score of 0.5567 on ADE20K test set, which surpass the winning entry of COCO-Place Challenge in 2017. In addition, we also explore how the Context Encoding Module can improve the feature representation of relatively shallow networks for the image classification on CIFAR-10 dataset. Our 14 layer network has achieved an error rate of 3.45%, which is comparable with state-of-the-art approaches with over 10 times more layers. The source code for the complete system are publicly available. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| EncNet | R-50-D8 | 512x1024 | 40000 | 8.6 | 4.58 | V100 | 75.67 | 77.08 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x1024_40k_cityscapes/encnet_r50-d8_512x1024_40k_cityscapes_20200621_220958-68638a47.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x1024_40k_cityscapes/encnet_r50-d8_512x1024_40k_cityscapes-20200621_220958.log.json) | +| EncNet | R-101-D8 | 512x1024 | 40000 | 12.1 | 2.66 | V100 | 75.81 | 77.21 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x1024_40k_cityscapes/encnet_r101-d8_512x1024_40k_cityscapes_20200621_220933-35e0a3e8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x1024_40k_cityscapes/encnet_r101-d8_512x1024_40k_cityscapes-20200621_220933.log.json) | +| EncNet | R-50-D8 | 769x769 | 40000 | 9.8 | 1.82 | V100 | 76.24 | 77.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_769x769_40k_cityscapes/encnet_r50-d8_769x769_40k_cityscapes_20200621_220958-3bcd2884.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_769x769_40k_cityscapes/encnet_r50-d8_769x769_40k_cityscapes-20200621_220958.log.json) | +| EncNet | R-101-D8 | 769x769 | 40000 | 13.7 | 1.26 | V100 | 74.25 | 76.25 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_769x769_40k_cityscapes/encnet_r101-d8_769x769_40k_cityscapes_20200621_220933-2fafed55.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_769x769_40k_cityscapes/encnet_r101-d8_769x769_40k_cityscapes-20200621_220933.log.json) | +| EncNet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 77.94 | 79.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x1024_80k_cityscapes/encnet_r50-d8_512x1024_80k_cityscapes_20200622_003554-fc5c5624.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x1024_80k_cityscapes/encnet_r50-d8_512x1024_80k_cityscapes-20200622_003554.log.json) | +| EncNet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 78.55 | 79.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x1024_80k_cityscapes/encnet_r101-d8_512x1024_80k_cityscapes_20200622_003555-1de64bec.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x1024_80k_cityscapes/encnet_r101-d8_512x1024_80k_cityscapes-20200622_003555.log.json) | +| EncNet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 77.44 | 78.72 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_769x769_80k_cityscapes/encnet_r50-d8_769x769_80k_cityscapes_20200622_003554-55096dcb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_769x769_80k_cityscapes/encnet_r50-d8_769x769_80k_cityscapes-20200622_003554.log.json) | +| EncNet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 76.10 | 76.97 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_769x769_80k_cityscapes/encnet_r101-d8_769x769_80k_cityscapes_20200622_003555-470ef79d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_769x769_80k_cityscapes/encnet_r101-d8_769x769_80k_cityscapes-20200622_003555.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| EncNet | R-50-D8 | 512x512 | 80000 | 10.1 | 22.81 | V100 | 39.53 | 41.17 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x512_80k_ade20k/encnet_r50-d8_512x512_80k_ade20k_20200622_042412-44b46b04.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x512_80k_ade20k/encnet_r50-d8_512x512_80k_ade20k-20200622_042412.log.json) | +| EncNet | R-101-D8 | 512x512 | 80000 | 13.6 | 14.87 | V100 | 42.11 | 43.61 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x512_80k_ade20k/encnet_r101-d8_512x512_80k_ade20k_20200622_101128-dd35e237.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x512_80k_ade20k/encnet_r101-d8_512x512_80k_ade20k-20200622_101128.log.json) | +| EncNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 40.10 | 41.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x512_160k_ade20k/encnet_r50-d8_512x512_160k_ade20k_20200622_101059-b2db95e0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x512_160k_ade20k/encnet_r50-d8_512x512_160k_ade20k-20200622_101059.log.json) | +| EncNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 42.61 | 44.01 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet/encnet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x512_160k_ade20k/encnet_r101-d8_512x512_160k_ade20k_20200622_073348-7989641f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x512_160k_ade20k/encnet_r101-d8_512x512_160k_ade20k-20200622_073348.log.json) | + +## Citation + +```bibtex +@InProceedings{Zhang_2018_CVPR, +author = {Zhang, Hang and Dana, Kristin and Shi, Jianping and Zhang, Zhongyue and Wang, Xiaogang and Tyagi, Ambrish and Agrawal, Amit}, +title = {Context Encoding for Semantic Segmentation}, +booktitle = {The IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, +month = {June}, +year = {2018} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..13ab367 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './encnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..7810ac4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './encnet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..bec6bd9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './encnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..e1f6409 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './encnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..9599f9c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './encnet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..a9edfc2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './encnet_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..d2fbab5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './encnet_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..debe8c8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './encnet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..d5c3027 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..045d0fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..4dafcd5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..e4d0b80 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..b916798 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..e5c9171 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..8ca126a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..931d6c0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50s-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50s-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..e98104d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/encnet_r50s-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(stem_channels=128), + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/encnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/encnet/metafile.yaml new file mode 100644 index 0000000..0dbdcfa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/metafile.yaml @@ -0,0 +1,296 @@ +Collections: +- Name: EncNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + README: configs/encnet/README.md + Frameworks: + - PyTorch +Models: +- Name: encnet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.67 + mIoU(ms+flip): 77.08 + Config: configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x1024_40k_cityscapes/encnet_r50-d8_512x1024_40k_cityscapes_20200621_220958-68638a47.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x1024_40k_cityscapes/encnet_r50-d8_512x1024_40k_cityscapes-20200621_220958.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.81 + mIoU(ms+flip): 77.21 + Config: configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x1024_40k_cityscapes/encnet_r101-d8_512x1024_40k_cityscapes_20200621_220933-35e0a3e8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x1024_40k_cityscapes/encnet_r101-d8_512x1024_40k_cityscapes-20200621_220933.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.24 + mIoU(ms+flip): 77.85 + Config: configs/encnet/encnet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_769x769_40k_cityscapes/encnet_r50-d8_769x769_40k_cityscapes_20200621_220958-3bcd2884.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_769x769_40k_cityscapes/encnet_r50-d8_769x769_40k_cityscapes-20200621_220958.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.25 + mIoU(ms+flip): 76.25 + Config: configs/encnet/encnet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 13.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_769x769_40k_cityscapes/encnet_r101-d8_769x769_40k_cityscapes_20200621_220933-2fafed55.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_769x769_40k_cityscapes/encnet_r101-d8_769x769_40k_cityscapes-20200621_220933.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.94 + mIoU(ms+flip): 79.13 + Config: configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - EncNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x1024_80k_cityscapes/encnet_r50-d8_512x1024_80k_cityscapes_20200622_003554-fc5c5624.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x1024_80k_cityscapes/encnet_r50-d8_512x1024_80k_cityscapes-20200622_003554.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.55 + mIoU(ms+flip): 79.47 + Config: configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - EncNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x1024_80k_cityscapes/encnet_r101-d8_512x1024_80k_cityscapes_20200622_003555-1de64bec.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x1024_80k_cityscapes/encnet_r101-d8_512x1024_80k_cityscapes-20200622_003555.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.44 + mIoU(ms+flip): 78.72 + Config: configs/encnet/encnet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - EncNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_769x769_80k_cityscapes/encnet_r50-d8_769x769_80k_cityscapes_20200622_003554-55096dcb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_769x769_80k_cityscapes/encnet_r50-d8_769x769_80k_cityscapes-20200622_003554.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.1 + mIoU(ms+flip): 76.97 + Config: configs/encnet/encnet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - EncNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_769x769_80k_cityscapes/encnet_r101-d8_769x769_80k_cityscapes_20200622_003555-470ef79d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_769x769_80k_cityscapes/encnet_r101-d8_769x769_80k_cityscapes-20200622_003555.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 39.53 + mIoU(ms+flip): 41.17 + Config: configs/encnet/encnet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x512_80k_ade20k/encnet_r50-d8_512x512_80k_ade20k_20200622_042412-44b46b04.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x512_80k_ade20k/encnet_r50-d8_512x512_80k_ade20k-20200622_042412.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.11 + mIoU(ms+flip): 43.61 + Config: configs/encnet/encnet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 13.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x512_80k_ade20k/encnet_r101-d8_512x512_80k_ade20k_20200622_101128-dd35e237.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x512_80k_ade20k/encnet_r101-d8_512x512_80k_ade20k-20200622_101128.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 40.1 + mIoU(ms+flip): 41.71 + Config: configs/encnet/encnet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - EncNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x512_160k_ade20k/encnet_r50-d8_512x512_160k_ade20k_20200622_101059-b2db95e0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r50-d8_512x512_160k_ade20k/encnet_r50-d8_512x512_160k_ade20k-20200622_101059.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch +- Name: encnet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: EncNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.61 + mIoU(ms+flip): 44.01 + Config: configs/encnet/encnet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - EncNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x512_160k_ade20k/encnet_r101-d8_512x512_160k_ade20k_20200622_073348-7989641f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/encnet/encnet_r101-d8_512x512_160k_ade20k/encnet_r101-d8_512x512_160k_ade20k-20200622_073348.log.json + Paper: + Title: Context Encoding for Semantic Segmentation + URL: https://arxiv.org/abs/1803.08904 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/enc_head.py#L63 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/encnet/my_encnet_r50_r50_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py b/Seg_All_In_One_MMSeg/configs/encnet/my_encnet_r50_r50_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py new file mode 100644 index 0000000..770701c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/encnet/my_encnet_r50_r50_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py @@ -0,0 +1,103 @@ +_base_ = [ + '../_base_/models/encnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + depth=50, + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 1024), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/erfnet/README.md b/Seg_All_In_One_MMSeg/configs/erfnet/README.md new file mode 100644 index 0000000..55d7197 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/erfnet/README.md @@ -0,0 +1,54 @@ +# ERFNet + +> [ERFNet: Efficient Residual Factorized ConvNet for Real-time Semantic Segmentation](http://www.robesafe.uah.es/personal/eduardo.romera/pdfs/Romera17tits.pdf) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Semantic segmentation is a challenging task that addresses most of the perception needs of intelligent vehicles (IVs) in an unified way. Deep neural networks excel at this task, as they can be trained end-to-end to accurately classify multiple object categories in an image at pixel level. However, a good tradeoff between high quality and computational resources is yet not present in the state-of-the-art semantic segmentation approaches, limiting their application in real vehicles. In this paper, we propose a deep architecture that is able to run in real time while providing accurate semantic segmentation. The core of our architecture is a novel layer that uses residual connections and factorized convolutions in order to remain efficient while retaining remarkable accuracy. Our approach is able to run at over 83 FPS in a single Titan X, and 7 FPS in a Jetson TX1 (embedded device). A comprehensive set of experiments on the publicly available Cityscapes data set demonstrates that our system achieves an accuracy that is similar to the state of the art, while being orders of magnitude faster to compute than other architectures that achieve top precision. The resulting tradeoff makes our model an ideal approach for scene understanding in IV applications. The code is publicly available at: https://github.com/Eromera/erfnet. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ---: | ------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ERFNet | ERFNet | 512x1024 | 160000 | 6.04 | 15.26 | V100 | 72.5 | 74.75 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/erfnet/erfnet_fcn_4xb4-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/erfnet/erfnet_fcn_4x4_512x1024_160k_cityscapes/erfnet_fcn_4x4_512x1024_160k_cityscapes_20220704_162145-dc90157a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/erfnet/erfnet_fcn_4x4_512x1024_160k_cityscapes/erfnet_fcn_4x4_512x1024_160k_cityscapes_20220704_162145.log.json) | + +Note: + +- The model is trained from scratch. + +- Last deconvolution layer in the [original paper](https://github.com/Eromera/erfnet_pytorch/blob/master/train/erfnet.py#L123) is replaced by a naive `FCNHead` decoder head and a bilinear upsampling layer, found more effective and efficient. + +- This model performance is sensitive to the seed values used, please refer to the log file for the specific settings of the seed. If you choose a different seed, the results might differ from the table results. + +## Citation + +```bibtex +@article{romera2017erfnet, + title={Erfnet: Efficient residual factorized convnet for real-time semantic segmentation}, + author={Romera, Eduardo and Alvarez, Jos{\'e} M and Bergasa, Luis M and Arroyo, Roberto}, + journal={IEEE Transactions on Intelligent Transportation Systems}, + volume={19}, + number={1}, + pages={263--272}, + year={2017}, + publisher={IEEE} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/erfnet/erfnet_fcn_4xb4-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/erfnet/erfnet_fcn_4xb4-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..7d65582 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/erfnet/erfnet_fcn_4xb4-160k_cityscapes-512x1024.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/erfnet_fcn.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/erfnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/erfnet/metafile.yaml new file mode 100644 index 0000000..bf51412 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/erfnet/metafile.yaml @@ -0,0 +1,37 @@ +Collections: +- Name: ERFNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: 'ERFNet: Efficient Residual Factorized ConvNet for Real-time Semantic Segmentation' + URL: http://www.robesafe.uah.es/personal/eduardo.romera/pdfs/Romera17tits.pdf + README: configs/erfnet/README.md + Frameworks: + - PyTorch +Models: +- Name: erfnet_fcn_4xb4-160k_cityscapes-512x1024 + In Collection: ERFNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 72.5 + mIoU(ms+flip): 74.75 + Config: configs/erfnet/erfnet_fcn_4xb4-160k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - ERFNet + - ERFNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.04 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/erfnet/erfnet_fcn_4x4_512x1024_160k_cityscapes/erfnet_fcn_4x4_512x1024_160k_cityscapes_20220704_162145-dc90157a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/erfnet/erfnet_fcn_4x4_512x1024_160k_cityscapes/erfnet_fcn_4x4_512x1024_160k_cityscapes_20220704_162145.log.json + Paper: + Title: 'ERFNet: Efficient Residual Factorized ConvNet for Real-time Semantic Segmentation' + URL: http://www.robesafe.uah.es/personal/eduardo.romera/pdfs/Romera17tits.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/erfnet.py#L321 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/erfnet/my_erfnet_fcn_fcn_g1-40k_check_4000_my_dataset_model-512x1024.py b/Seg_All_In_One_MMSeg/configs/erfnet/my_erfnet_fcn_fcn_g1-40k_check_4000_my_dataset_model-512x1024.py new file mode 100644 index 0000000..c1ab5ae --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/erfnet/my_erfnet_fcn_fcn_g1-40k_check_4000_my_dataset_model-512x1024.py @@ -0,0 +1,84 @@ +_base_ = [ + '../_base_/models/erfnet_fcn.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/README.md b/Seg_All_In_One_MMSeg/configs/fastfcn/README.md new file mode 100644 index 0000000..48644e5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/README.md @@ -0,0 +1,63 @@ +# FastFCN + +> [FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation](https://arxiv.org/abs/1903.11816) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Modern approaches for semantic segmentation usually employ dilated convolutions in the backbone to extract high-resolution feature maps, which brings heavy computation complexity and memory footprint. To replace the time and memory consuming dilated convolutions, we propose a novel joint upsampling module named Joint Pyramid Upsampling (JPU) by formulating the task of extracting high-resolution feature maps into a joint upsampling problem. With the proposed JPU, our method reduces the computation complexity by more than three times without performance loss. Experiments show that JPU is superior to other upsampling modules, which can be plugged into many existing approaches to reduce computation complexity and improve performance. By replacing dilated convolutions with the proposed JPU module, our method achieves the state-of-the-art performance in Pascal Context dataset (mIoU of 53.13%) and ADE20K dataset (final score of 0.5584) while running 3 times faster. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------------------- | -------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| FastFCN + DeepLabV3 | R-50-D32 | 512x1024 | 80000 | 5.67 | 2.64 | V100 | 79.12 | 80.58 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_aspp_512x1024_80k_cityscapes_20210928_053722-5d1a2648.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_aspp_512x1024_80k_cityscapes_20210928_053722.log.json) | +| FastFCN + DeepLabV3 | R-50-D32 (4x4) | 512x1024 | 80000 | 9.79 | - | V100 | 79.52 | 80.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_aspp_4x4_512x1024_80k_cityscapes_20210924_214357-72220849.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_aspp_4x4_512x1024_80k_cityscapes_20210924_214357.log.json) | +| FastFCN + PSPNet | R-50-D32 | 512x1024 | 80000 | 5.67 | 4.40 | V100 | 79.26 | 80.86 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_psp_512x1024_80k_cityscapes_20210928_053722-57749bed.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_psp_512x1024_80k_cityscapes_20210928_053722.log.json) | +| FastFCN + PSPNet | R-50-D32 (4x4) | 512x1024 | 80000 | 9.94 | - | V100 | 78.76 | 80.03 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_psp_4x4_512x1024_80k_cityscapes_20210925_061841-77e87b0a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_psp_4x4_512x1024_80k_cityscapes_20210925_061841.log.json) | +| FastFCN + EncNet | R-50-D32 | 512x1024 | 80000 | 8.15 | 4.77 | V100 | 77.97 | 79.92 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_enc_512x1024_80k_cityscapes_20210928_030036-78da5046.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_enc_512x1024_80k_cityscapes_20210928_030036.log.json) | +| FastFCN + EncNet | R-50-D32 (4x4) | 512x1024 | 80000 | 15.45 | - | V100 | 78.6 | 80.25 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_enc_4x4_512x1024_80k_cityscapes_20210926_093217-e1eb6dbb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_enc_4x4_512x1024_80k_cityscapes_20210926_093217.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------------------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FastFCN + DeepLabV3 | R-50-D32 | 512x1024 | 80000 | 8.46 | 12.06 | V100 | 41.88 | 42.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x512_80k_ade20k/fastfcn_r50-d32_jpu_aspp_512x512_80k_ade20k_20211013_190619-3aa40f2d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x512_80k_ade20k/fastfcn_r50-d32_jpu_aspp_512x512_80k_ade20k_20211013_190619.log.json) | +| FastFCN + DeepLabV3 | R-50-D32 | 512x1024 | 160000 | - | - | V100 | 43.58 | 44.92 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x512_160k_ade20k/fastfcn_r50-d32_jpu_aspp_512x512_160k_ade20k_20211008_152246-27036aee.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x512_160k_ade20k/fastfcn_r50-d32_jpu_aspp_512x512_160k_ade20k_20211008_152246.log.json) | +| FastFCN + PSPNet | R-50-D32 | 512x1024 | 80000 | 8.02 | 19.21 | V100 | 41.40 | 42.12 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x512_80k_ade20k/fastfcn_r50-d32_jpu_psp_512x512_80k_ade20k_20210930_225137-993d07c8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x512_80k_ade20k/fastfcn_r50-d32_jpu_psp_512x512_80k_ade20k_20210930_225137.log.json) | +| FastFCN + PSPNet | R-50-D32 | 512x1024 | 160000 | - | - | V100 | 42.63 | 43.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x512_160k_ade20k/fastfcn_r50-d32_jpu_psp_512x512_160k_ade20k_20211008_105455-e8f5a2fd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x512_160k_ade20k/fastfcn_r50-d32_jpu_psp_512x512_160k_ade20k_20211008_105455.log.json) | +| FastFCN + EncNet | R-50-D32 | 512x1024 | 80000 | 9.67 | 17.23 | V100 | 40.88 | 42.36 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x512_80k_ade20k/fastfcn_r50-d32_jpu_enc_512x512_80k_ade20k_20210930_225214-65aef6dd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x512_80k_ade20k/fastfcn_r50-d32_jpu_enc_512x512_80k_ade20k_20210930_225214.log.json) | +| FastFCN + EncNet | R-50-D32 | 512x1024 | 160000 | - | - | V100 | 42.50 | 44.21 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x512_160k_ade20k/fastfcn_r50-d32_jpu_enc_512x512_160k_ade20k_20211008_105456-d875ce3c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x512_160k_ade20k/fastfcn_r50-d32_jpu_enc_512x512_160k_ade20k_20211008_105456.log.json) | + +Note: + +- `4x4` means 4 GPUs with 4 samples per GPU in training, default setting is 4 GPUs with 2 samples per GPU in training. +- Results of [DeepLabV3 (mIoU: 79.32)](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3), [PSPNet (mIoU: 78.55)](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet) and [ENCNet (mIoU: 77.94)](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet) can be found in each original repository. + +## Citation + +```bibtex +@article{wu2019fastfcn, +title={Fastfcn: Rethinking dilated convolution in the backbone for semantic segmentation}, +author={Wu, Huikai and Zhang, Junge and Huang, Kaiqi and Liang, Kongming and Yu, Yizhou}, +journal={arXiv preprint arXiv:1903.11816}, +year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..39e6e23 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,20 @@ +# model settings +_base_ = './fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + decode_head=dict( + _delete_=True, + type='ASPPHead', + in_channels=2048, + in_index=2, + channels=512, + dilations=(1, 12, 24, 36), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..1913544 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,20 @@ +# model settings +_base_ = './fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + decode_head=dict( + _delete_=True, + type='ASPPHead', + in_channels=2048, + in_index=2, + channels=512, + dilations=(1, 12, 24, 36), + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..7516895 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,20 @@ +# model settings +_base_ = './fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + decode_head=dict( + _delete_=True, + type='ASPPHead', + in_channels=2048, + in_index=2, + channels=512, + dilations=(1, 12, 24, 36), + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..a8c5dc3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_cityscapes-512x1024.py @@ -0,0 +1,5 @@ +# model settings +_base_ = './fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py' +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..4840dd0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,24 @@ +# model settings +_base_ = './fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + decode_head=dict( + _delete_=True, + type='EncHead', + in_channels=[512, 1024, 2048], + in_index=(0, 1, 2), + channels=512, + num_codes=32, + use_se_loss=True, + add_lateral=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_se_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.2)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..619d086 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,24 @@ +# model settings +_base_ = './fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + decode_head=dict( + _delete_=True, + type='EncHead', + in_channels=[512, 1024, 2048], + in_index=(0, 1, 2), + channels=512, + num_codes=32, + use_se_loss=True, + add_lateral=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_se_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.2)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..a76b026 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,24 @@ +# model settings +_base_ = './fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + decode_head=dict( + _delete_=True, + type='EncHead', + in_channels=[512, 1024, 2048], + in_index=(0, 1, 2), + channels=512, + num_codes=32, + use_se_loss=True, + add_lateral=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + loss_se_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.2)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..6df1527 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_cityscapes-512x1024.py @@ -0,0 +1,5 @@ +# model settings +_base_ = './fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py' +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..dc5c54d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/cityscapes.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..887ace1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..3981e20 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..2c7d504 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/cityscapes.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/metafile.yaml b/Seg_All_In_One_MMSeg/configs/fastfcn/metafile.yaml new file mode 100644 index 0000000..f5fe03c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/metafile.yaml @@ -0,0 +1,311 @@ +Collections: +- Name: FastFCN + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + README: configs/fastfcn/README.md + Frameworks: + - PyTorch +Models: +- Name: fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.12 + mIoU(ms+flip): 80.58 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D32 + - FastFCN + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 5.67 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_aspp_512x1024_80k_cityscapes_20210928_053722-5d1a2648.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_aspp_512x1024_80k_cityscapes_20210928_053722.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.52 + mIoU(ms+flip): 80.91 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D32 + - FastFCN + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 9.79 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_aspp_4x4_512x1024_80k_cityscapes_20210924_214357-72220849.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_aspp_4x4_512x1024_80k_cityscapes_20210924_214357.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.26 + mIoU(ms+flip): 80.86 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D32 + - FastFCN + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 5.67 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_psp_512x1024_80k_cityscapes_20210928_053722-57749bed.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_psp_512x1024_80k_cityscapes_20210928_053722.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.76 + mIoU(ms+flip): 80.03 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D32 + - FastFCN + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.94 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_psp_4x4_512x1024_80k_cityscapes_20210925_061841-77e87b0a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_psp_4x4_512x1024_80k_cityscapes_20210925_061841.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.97 + mIoU(ms+flip): 79.92 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D32 + - FastFCN + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.15 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_enc_512x1024_80k_cityscapes_20210928_030036-78da5046.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_enc_512x1024_80k_cityscapes_20210928_030036.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.6 + mIoU(ms+flip): 80.25 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D32 + - FastFCN + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 15.45 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_enc_4x4_512x1024_80k_cityscapes_20210926_093217-e1eb6dbb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_4x4_512x1024_80k_cityscapes/fastfcn_r50-d32_jpu_enc_4x4_512x1024_80k_cityscapes_20210926_093217.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_aspp_4xb4-80k_ade20k-512x512 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.88 + mIoU(ms+flip): 42.91 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D32 + - FastFCN + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 8.46 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x512_80k_ade20k/fastfcn_r50-d32_jpu_aspp_512x512_80k_ade20k_20211013_190619-3aa40f2d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x512_80k_ade20k/fastfcn_r50-d32_jpu_aspp_512x512_80k_ade20k_20211013_190619.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_aspp_4xb4-160k_ade20k-512x512 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.58 + mIoU(ms+flip): 44.92 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_aspp_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D32 + - FastFCN + - DeepLabV3 + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x512_160k_ade20k/fastfcn_r50-d32_jpu_aspp_512x512_160k_ade20k_20211008_152246-27036aee.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_aspp_512x512_160k_ade20k/fastfcn_r50-d32_jpu_aspp_512x512_160k_ade20k_20211008_152246.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.4 + mIoU(ms+flip): 42.12 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D32 + - FastFCN + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.02 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x512_80k_ade20k/fastfcn_r50-d32_jpu_psp_512x512_80k_ade20k_20210930_225137-993d07c8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x512_80k_ade20k/fastfcn_r50-d32_jpu_psp_512x512_80k_ade20k_20210930_225137.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.63 + mIoU(ms+flip): 43.71 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_psp_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D32 + - FastFCN + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x512_160k_ade20k/fastfcn_r50-d32_jpu_psp_512x512_160k_ade20k_20211008_105455-e8f5a2fd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_psp_512x512_160k_ade20k/fastfcn_r50-d32_jpu_psp_512x512_160k_ade20k_20211008_105455.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_enc_4xb4-80k_ade20k-512x512 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 40.88 + mIoU(ms+flip): 42.36 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D32 + - FastFCN + - EncNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.67 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x512_80k_ade20k/fastfcn_r50-d32_jpu_enc_512x512_80k_ade20k_20210930_225214-65aef6dd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x512_80k_ade20k/fastfcn_r50-d32_jpu_enc_512x512_80k_ade20k_20210930_225214.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch +- Name: fastfcn_r50-d32_jpu_enc_4xb4-160k_ade20k-512x512 + In Collection: FastFCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.5 + mIoU(ms+flip): 44.21 + Config: configs/fastfcn/fastfcn_r50-d32_jpu_enc_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D32 + - FastFCN + - EncNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x512_160k_ade20k/fastfcn_r50-d32_jpu_enc_512x512_160k_ade20k_20211008_105456-d875ce3c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fastfcn/fastfcn_r50-d32_jpu_enc_512x512_160k_ade20k/fastfcn_r50-d32_jpu_enc_512x512_160k_ade20k_20211008_105456.log.json + Paper: + Title: 'FastFCN: Rethinking Dilated Convolution in the Backbone for Semantic Segmentation' + URL: https://arxiv.org/abs/1903.11816 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/jpu.py#L12 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r101_Pre_jpu_aspp_g1-40k_check_4000_my_dataset_model-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r101_Pre_jpu_aspp_g1-40k_check_4000_my_dataset_model-512x512.py new file mode 100644 index 0000000..932b3ff --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r101_Pre_jpu_aspp_g1-40k_check_4000_my_dataset_model-512x512.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + norm_cfg=dict( + type='BN', + ), + depth=101, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=2, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict( + type='BN', + ), + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + norm_cfg=dict( + type='BN', + ), + num_classes=36, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..3bfc635 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + norm_cfg=dict( + type='BN', + ), + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=2, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict( + type='BN', + ), + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + norm_cfg=dict( + type='BN', + ), + num_classes=10, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..edf7ad1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + norm_cfg=dict( + type='BN', + ), + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=2, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict( + type='BN', + ), + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + norm_cfg=dict( + type='BN', + ), + num_classes=13, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..33b701d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + norm_cfg=dict( + type='BN', + ), + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=2, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict( + type='BN', + ), + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + norm_cfg=dict( + type='BN', + ), + num_classes=11, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..f750172 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + norm_cfg=dict( + type='BN', + ), + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=2, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict( + type='BN', + ), + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + norm_cfg=dict( + type='BN', + ), + num_classes=8, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..13f5c46 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + norm_cfg=dict( + type='BN', + ), + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=2, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict( + type='BN', + ), + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + norm_cfg=dict( + type='BN', + ), + num_classes=8, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-40k_check_4000_my_dataset_model-512x512.py b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-40k_check_4000_my_dataset_model-512x512.py new file mode 100644 index 0000000..457b997 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastfcn/my_fastfcn_r50_neck_r50_Pre_jpu_aspp_g1-40k_check_4000_my_dataset_model-512x512.py @@ -0,0 +1,120 @@ +_base_ = [ + '../_base_/models/fastfcn_r50-d32_jpu_psp.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + norm_cfg=dict( + type='BN', + ), + depth=50, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + _delete_=True, + type='EncHead', + in_channels=[ + 512, + 1024, + 2048, + ], + in_index=(0, 1, 2), + channels=512, + num_codes=32, + use_se_loss=True, + add_lateral=False, + dropout_ratio=0.1, + num_classes=36, + norm_cfg=dict( + type='BN', + ), + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + loss_se_decode=dict( + type='DiceLoss', + use_sigmoid=True, + loss_weight=0.2, + ), + ), + auxiliary_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/README.md b/Seg_All_In_One_MMSeg/configs/fastscnn/README.md new file mode 100644 index 0000000..6be9814 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/README.md @@ -0,0 +1,42 @@ +# Fast-SCNN + +> [Fast-SCNN for Semantic Segmentation](https://arxiv.org/abs/1902.04502) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +The encoder-decoder framework is state-of-the-art for offline semantic image segmentation. Since the rise in autonomous systems, real-time computation is increasingly desirable. In this paper, we introduce fast segmentation convolutional neural network (Fast-SCNN), an above real-time semantic segmentation model on high resolution image data (1024x2048px) suited to efficient computation on embedded devices with low memory. Building on existing two-branch methods for fast segmentation, we introduce our \`learning to downsample' module which computes low-level features for multiple resolution branches simultaneously. Our network combines spatial detail at high resolution with deep features extracted at lower resolution, yielding an accuracy of 68.0% mean intersection over union at 123.5 frames per second on Cityscapes. We also show that large scale pre-training is unnecessary. We thoroughly validate our metric in experiments with ImageNet pre-training and the coarse labeled data of Cityscapes. Finally, we show even faster computation with competitive results on subsampled inputs, without any network modifications. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| -------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| FastSCNN | FastSCNN | 512x1024 | 160000 | 3.3 | 56.45 | V100 | 70.96 | 72.65 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastscnn/fast_scnn_8xb4-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fast_scnn/fast_scnn_lr0.12_8x4_160k_cityscapes/fast_scnn_lr0.12_8x4_160k_cityscapes_20210630_164853-0cec9937.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fast_scnn/fast_scnn_lr0.12_8x4_160k_cityscapes/fast_scnn_lr0.12_8x4_160k_cityscapes_20210630_164853.log.json) | + +## Citation + +```bibtex +@article{poudel2019fast, + title={Fast-scnn: Fast semantic segmentation network}, + author={Poudel, Rudra PK and Liwicki, Stephan and Cipolla, Roberto}, + journal={arXiv preprint arXiv:1902.04502}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/fast_scnn_8xb4-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fastscnn/fast_scnn_8xb4-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..e7f68bf --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/fast_scnn_8xb4-160k_cityscapes-512x1024.py @@ -0,0 +1,15 @@ +_base_ = [ + '../_base_/models/fast_scnn.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +# Re-config the data sampler. +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader + +# Re-config the optimizer. +optimizer = dict(type='SGD', lr=0.12, momentum=0.9, weight_decay=4e-5) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/metafile.yaml b/Seg_All_In_One_MMSeg/configs/fastscnn/metafile.yaml new file mode 100644 index 0000000..9e33c90 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/metafile.yaml @@ -0,0 +1,37 @@ +Collections: +- Name: FastSCNN + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: Fast-SCNN for Semantic Segmentation + URL: https://arxiv.org/abs/1902.04502 + README: configs/fastscnn/README.md + Frameworks: + - PyTorch +Models: +- Name: fast_scnn_8xb4-160k_cityscapes-512x1024 + In Collection: FastSCNN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 70.96 + mIoU(ms+flip): 72.65 + Config: configs/fastscnn/fast_scnn_8xb4-160k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 32 + Architecture: + - FastSCNN + - FastSCNN + Training Resources: 8x V100 GPUS + Memory (GB): 3.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fast_scnn/fast_scnn_lr0.12_8x4_160k_cityscapes/fast_scnn_lr0.12_8x4_160k_cityscapes_20210630_164853-0cec9937.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fast_scnn/fast_scnn_lr0.12_8x4_160k_cityscapes/fast_scnn_lr0.12_8x4_160k_cityscapes_20210630_164853.log.json + Paper: + Title: Fast-SCNN for Semantic Segmentation + URL: https://arxiv.org/abs/1902.04502 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/fast_scnn.py#L272 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..6237485 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,122 @@ +_base_ = [ + '../_base_/models/fast_scnn.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=32, + num_convs=1, + num_classes=10, + in_index=-2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=32, + num_convs=1, + num_classes=10, + in_index=-3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..0c3de29 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,122 @@ +_base_ = [ + '../_base_/models/fast_scnn.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=32, + num_convs=1, + num_classes=13, + in_index=-2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=32, + num_convs=1, + num_classes=13, + in_index=-3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..a6d95f8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,122 @@ +_base_ = [ + '../_base_/models/fast_scnn.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=32, + num_convs=1, + num_classes=11, + in_index=-2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=32, + num_convs=1, + num_classes=11, + in_index=-3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..01831e6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,122 @@ +_base_ = [ + '../_base_/models/fast_scnn.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=32, + num_convs=1, + num_classes=8, + in_index=-2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=32, + num_convs=1, + num_classes=8, + in_index=-3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..bd1f834 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,122 @@ +_base_ = [ + '../_base_/models/fast_scnn.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=32, + num_convs=1, + num_classes=8, + in_index=-2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=32, + num_convs=1, + num_classes=8, + in_index=-3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-40k_check_4000_my_dataset_model-512x1024.py b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-40k_check_4000_my_dataset_model-512x1024.py new file mode 100644 index 0000000..88e094b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fastscnn/my_fast_scnn_g1-40k_check_4000_my_dataset_model-512x1024.py @@ -0,0 +1,134 @@ +_base_ = [ + '../_base_/models/fast_scnn.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=32, + num_convs=1, + num_classes=19, + in_index=-2, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + momentum=0.01, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + # num_classes=36, + norm_cfg=dict( + type='BN', + ), + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=32, + num_convs=1, + num_classes=19, + in_index=-3, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + momentum=0.01, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + # num_classes=36, + norm_cfg=dict( + type='BN', + ), + ), + ), + ], +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fcn/README.md b/Seg_All_In_One_MMSeg/configs/fcn/README.md new file mode 100644 index 0000000..cf7379f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/README.md @@ -0,0 +1,111 @@ +# FCN + +> [Fully Convolutional Networks for Semantic Segmentation](https://arxiv.org/abs/1411.4038) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Convolutional networks are powerful visual models that yield hierarchies of features. We show that convolutional networks by themselves, trained end-to-end, pixels-to-pixels, exceed the state-of-the-art in semantic segmentation. Our key insight is to build "fully convolutional" networks that take input of arbitrary size and produce correspondingly-sized output with efficient inference and learning. We define and detail the space of fully convolutional networks, explain their application to spatially dense prediction tasks, and draw connections to prior models. We adapt contemporary classification networks (AlexNet, the VGG net, and GoogLeNet) into fully convolutional networks and transfer their learned representations by fine-tuning to the segmentation task. We then define a novel architecture that combines semantic information from a deep, coarse layer with appearance information from a shallow, fine layer to produce accurate and detailed segmentations. Our fully convolutional network achieves state-of-the-art segmentation of PASCAL VOC (20% relative improvement to 62.2% mean IU on 2012), NYUDv2, and SIFT Flow, while inference takes one third of a second for a typical image. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | ---------- | --------- | ------: | -------- | -------------- | -------- | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | R-50-D8 | 512x1024 | 40000 | 5.7 | 4.17 | V100 | 72.25 | 73.36 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_40k_cityscapes/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608-efe53f0d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_40k_cityscapes/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608.log.json) | +| FCN | R-101-D8 | 512x1024 | 40000 | 9.2 | 2.66 | V100 | 75.45 | 76.58 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x1024_40k_cityscapes/fcn_r101-d8_512x1024_40k_cityscapes_20200604_181852-a883d3a1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x1024_40k_cityscapes/fcn_r101-d8_512x1024_40k_cityscapes_20200604_181852.log.json) | +| FCN | R-50-D8 | 769x769 | 40000 | 6.5 | 1.80 | V100 | 71.47 | 72.54 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_769x769_40k_cityscapes/fcn_r50-d8_769x769_40k_cityscapes_20200606_113104-977b5d02.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_769x769_40k_cityscapes/fcn_r50-d8_769x769_40k_cityscapes_20200606_113104.log.json) | +| FCN | R-101-D8 | 769x769 | 40000 | 10.4 | 1.19 | V100 | 73.93 | 75.14 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_769x769_40k_cityscapes/fcn_r101-d8_769x769_40k_cityscapes_20200606_113208-7d4ab69c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_769x769_40k_cityscapes/fcn_r101-d8_769x769_40k_cityscapes_20200606_113208.log.json) | +| FCN | R-18-D8 | 512x1024 | 80000 | 1.7 | 14.65 | V100 | 71.11 | 72.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18-d8_512x1024_80k_cityscapes/fcn_r18-d8_512x1024_80k_cityscapes_20201225_021327-6c50f8b4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18-d8_512x1024_80k_cityscapes/fcn_r18-d8_512x1024_80k_cityscapes-20201225_021327.log.json) | +| FCN | R-50-D8 | 512x1024 | 80000 | - | | V100 | 73.61 | 74.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_80k_cityscapes/fcn_r50-d8_512x1024_80k_cityscapes_20200606_113019-03aa804d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_80k_cityscapes/fcn_r50-d8_512x1024_80k_cityscapes_20200606_113019.log.json) | +| FCN | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 75.13 | 75.94 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x1024_80k_cityscapes/fcn_r101-d8_512x1024_80k_cityscapes_20200606_113038-3fb937eb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x1024_80k_cityscapes/fcn_r101-d8_512x1024_80k_cityscapes_20200606_113038.log.json) | +| FCN (FP16) | R-101-D8 | 512x1024 | 80000 | 5.37 | 8.64 | V100 | 76.80 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_fp16_512x1024_80k_cityscapes/fcn_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230921-fb13e883.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_fp16_512x1024_80k_cityscapes/fcn_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230921.log.json) | +| FCN | R-18-D8 | 769x769 | 80000 | 1.9 | 6.40 | V100 | 70.80 | 73.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18-d8_769x769_80k_cityscapes/fcn_r18-d8_769x769_80k_cityscapes_20201225_021451-9739d1b8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18-d8_769x769_80k_cityscapes/fcn_r18-d8_769x769_80k_cityscapes-20201225_021451.log.json) | +| FCN | R-50-D8 | 769x769 | 80000 | - | - | V100 | 72.64 | 73.32 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_769x769_80k_cityscapes/fcn_r50-d8_769x769_80k_cityscapes_20200606_195749-f5caeabc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_769x769_80k_cityscapes/fcn_r50-d8_769x769_80k_cityscapes_20200606_195749.log.json) | +| FCN | R-101-D8 | 769x769 | 80000 | - | - | V100 | 75.52 | 76.61 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_769x769_80k_cityscapes/fcn_r101-d8_769x769_80k_cityscapes_20200606_214354-45cbac68.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_769x769_80k_cityscapes/fcn_r101-d8_769x769_80k_cityscapes_20200606_214354.log.json) | +| FCN | R-18b-D8 | 512x1024 | 80000 | 1.6 | 16.74 | V100 | 70.24 | 72.77 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18b-d8_512x1024_80k_cityscapes/fcn_r18b-d8_512x1024_80k_cityscapes_20201225_230143-92c0f445.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18b-d8_512x1024_80k_cityscapes/fcn_r18b-d8_512x1024_80k_cityscapes-20201225_230143.log.json) | +| FCN | R-50b-D8 | 512x1024 | 80000 | 5.6 | 4.20 | V100 | 75.65 | 77.59 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50b-d8_512x1024_80k_cityscapes/fcn_r50b-d8_512x1024_80k_cityscapes_20201225_094221-82957416.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50b-d8_512x1024_80k_cityscapes/fcn_r50b-d8_512x1024_80k_cityscapes-20201225_094221.log.json) | +| FCN | R-101b-D8 | 512x1024 | 80000 | 9.1 | 2.73 | V100 | 77.37 | 78.77 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101b-d8_512x1024_80k_cityscapes/fcn_r101b-d8_512x1024_80k_cityscapes_20201226_160213-4543858f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101b-d8_512x1024_80k_cityscapes/fcn_r101b-d8_512x1024_80k_cityscapes-20201226_160213.log.json) | +| FCN | R-18b-D8 | 769x769 | 80000 | 1.7 | 6.70 | V100 | 69.66 | 72.07 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18b-d8_769x769_80k_cityscapes/fcn_r18b-d8_769x769_80k_cityscapes_20201226_004430-32d504e5.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18b-d8_769x769_80k_cityscapes/fcn_r18b-d8_769x769_80k_cityscapes-20201226_004430.log.json) | +| FCN | R-50b-D8 | 769x769 | 80000 | 6.3 | 1.82 | V100 | 73.83 | 76.60 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50b-d8_769x769_80k_cityscapes/fcn_r50b-d8_769x769_80k_cityscapes_20201225_094223-94552d38.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50b-d8_769x769_80k_cityscapes/fcn_r50b-d8_769x769_80k_cityscapes-20201225_094223.log.json) | +| FCN | R-101b-D8 | 769x769 | 80000 | 10.3 | 1.15 | V100 | 77.02 | 78.67 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101b-d8_769x769_80k_cityscapes/fcn_r101b-d8_769x769_80k_cityscapes_20201226_170012-82be37e2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101b-d8_769x769_80k_cityscapes/fcn_r101b-d8_769x769_80k_cityscapes-20201226_170012.log.json) | +| FCN (D6) | R-50-D16 | 512x1024 | 40000 | 3.4 | 10.22 | TITAN Xp | 77.06 | 78.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_512x1024_40k_cityscapes/fcn_d6_r50-d16_512x1024_40k_cityscapes_20210305_130133-98d5d1bc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_512x1024_40k_cityscapes/fcn_d6_r50-d16_512x1024_40k_cityscapes-20210305_130133.log.json) | +| FCN (D6) | R-50-D16 | 512x1024 | 80000 | - | 10.35 | TITAN Xp | 77.27 | 78.88 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_512x1024_80k_cityscapes/fcn_d6_r50-d16_512x1024_80k_cityscapes_20210306_115604-133c292f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_512x1024_80k_cityscapes/fcn_d6_r50-d16_512x1024_80k_cityscapes-20210306_115604.log.json) | +| FCN (D6) | R-50-D16 | 769x769 | 40000 | 3.7 | 4.17 | TITAN Xp | 76.82 | 78.22 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_769x769_40k_cityscapes/fcn_d6_r50-d16_769x769_40k_cityscapes_20210305_185744-1aab18ed.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_769x769_40k_cityscapes/fcn_d6_r50-d16_769x769_40k_cityscapes-20210305_185744.log.json) | +| FCN (D6) | R-50-D16 | 769x769 | 80000 | - | 4.15 | TITAN Xp | 77.04 | 78.40 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_769x769_80k_cityscapes/fcn_d6_r50-d16_769x769_80k_cityscapes_20210305_200413-109d88eb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_769x769_80k_cityscapes/fcn_d6_r50-d16_769x769_80k_cityscapes-20210305_200413.log.json) | +| FCN (D6) | R-101-D16 | 512x1024 | 40000 | 4.5 | 8.04 | TITAN Xp | 77.36 | 79.18 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_512x1024_40k_cityscapes/fcn_d6_r101-d16_512x1024_40k_cityscapes_20210305_130337-9cf2b450.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_512x1024_40k_cityscapes/fcn_d6_r101-d16_512x1024_40k_cityscapes-20210305_130337.log.json) | +| FCN (D6) | R-101-D16 | 512x1024 | 80000 | - | 8.26 | TITAN Xp | 78.46 | 80.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_512x1024_80k_cityscapes/fcn_d6_r101-d16_512x1024_80k_cityscapes_20210308_102747-cb336445.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_512x1024_80k_cityscapes/fcn_d6_r101-d16_512x1024_80k_cityscapes-20210308_102747.log.json) | +| FCN (D6) | R-101-D16 | 769x769 | 40000 | 5.0 | 3.12 | TITAN Xp | 77.28 | 78.95 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_769x769_40k_cityscapes/fcn_d6_r101-d16_769x769_40k_cityscapes_20210308_102453-60b114e9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_769x769_40k_cityscapes/fcn_d6_r101-d16_769x769_40k_cityscapes-20210308_102453.log.json) | +| FCN (D6) | R-101-D16 | 769x769 | 80000 | - | 3.21 | TITAN Xp | 78.06 | 79.58 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_769x769_80k_cityscapes/fcn_d6_r101-d16_769x769_80k_cityscapes_20210306_120016-e33adc4f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_769x769_80k_cityscapes/fcn_d6_r101-d16_769x769_80k_cityscapes-20210306_120016.log.json) | +| FCN (D6) | R-50b-D16 | 512x1024 | 80000 | 3.2 | 10.16 | TITAN Xp | 76.99 | 79.03 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50b-d16_512x1024_80k_cityscapes/fcn_d6_r50b-d16_512x1024_80k_cityscapes_20210311_125550-6a0b62e9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50b_d16_512x1024_80k_cityscapes/fcn_d6_r50b_d16_512x1024_80k_cityscapes-20210311_125550.log.json) | +| FCN (D6) | R-50b-D16 | 769x769 | 80000 | 3.6 | 4.17 | TITAN Xp | 76.86 | 78.52 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50b-d16_769x769_80k_cityscapes/fcn_d6_r50b-d16_769x769_80k_cityscapes_20210311_131012-d665f231.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50b_d16_769x769_80k_cityscapes/fcn_d6_r50b_d16_769x769_80k_cityscapes-20210311_131012.log.json) | +| FCN (D6) | R-101b-D16 | 512x1024 | 80000 | 4.3 | 8.46 | TITAN Xp | 77.72 | 79.53 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101b-d16_512x1024_80k_cityscapes/fcn_d6_r101b-d16_512x1024_80k_cityscapes_20210311_144305-3f2eb5b4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101b_d16_512x1024_80k_cityscapes/fcn_d6_r101b_d16_512x1024_80k_cityscapes-20210311_144305.log.json) | +| FCN (D6) | R-101b-D16 | 769x769 | 80000 | 4.8 | 3.32 | TITAN Xp | 77.34 | 78.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101b-d16_769x769_80k_cityscapes/fcn_d6_r101b-d16_769x769_80k_cityscapes_20210311_154527-c4d8bfbc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101b_d16_769x769_80k_cityscapes/fcn_d6_r101b_d16_769x769_80k_cityscapes-20210311_154527.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | R-50-D8 | 512x512 | 80000 | 8.5 | 23.49 | V100 | 35.94 | 37.94 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_80k_ade20k/fcn_r50-d8_512x512_80k_ade20k_20200614_144016-f8ac5082.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_80k_ade20k/fcn_r50-d8_512x512_80k_ade20k_20200614_144016.log.json) | +| FCN | R-101-D8 | 512x512 | 80000 | 12 | 14.78 | V100 | 39.61 | 40.83 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb4-80k_ade20k-512x512.pyy) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_80k_ade20k/fcn_r101-d8_512x512_80k_ade20k_20200615_014143-bc1809f7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_80k_ade20k/fcn_r101-d8_512x512_80k_ade20k_20200615_014143.log.json) | +| FCN | R-50-D8 | 512x512 | 160000 | - | - | V100 | 36.10 | 38.08 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_160k_ade20k/fcn_r50-d8_512x512_160k_ade20k_20200615_100713-4edbc3b4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_160k_ade20k/fcn_r50-d8_512x512_160k_ade20k_20200615_100713.log.json) | +| FCN | R-101-D8 | 512x512 | 160000 | - | - | V100 | 39.91 | 41.40 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_160k_ade20k/fcn_r101-d8_512x512_160k_ade20k_20200615_105816-fd192bd5.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_160k_ade20k/fcn_r101-d8_512x512_160k_ade20k_20200615_105816.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | R-50-D8 | 512x512 | 20000 | 5.7 | 23.28 | V100 | 67.08 | 69.94 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_20k_voc12aug/fcn_r50-d8_512x512_20k_voc12aug_20200617_010715-52dc5306.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_20k_voc12aug/fcn_r50-d8_512x512_20k_voc12aug_20200617_010715.log.json) | +| FCN | R-101-D8 | 512x512 | 20000 | 9.2 | 14.81 | V100 | 71.16 | 73.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_20k_voc12aug/fcn_r101-d8_512x512_20k_voc12aug_20200617_010842-0bb4e798.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_20k_voc12aug/fcn_r101-d8_512x512_20k_voc12aug_20200617_010842.log.json) | +| FCN | R-50-D8 | 512x512 | 40000 | - | - | V100 | 66.97 | 69.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_40k_voc12aug/fcn_r50-d8_512x512_40k_voc12aug_20200613_161222-5e2dbf40.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_40k_voc12aug/fcn_r50-d8_512x512_40k_voc12aug_20200613_161222.log.json) | +| FCN | R-101-D8 | 512x512 | 40000 | - | - | V100 | 69.91 | 72.38 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_40k_voc12aug/fcn_r101-d8_512x512_40k_voc12aug_20200613_161240-4c8bcefd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_40k_voc12aug/fcn_r101-d8_512x512_40k_voc12aug_20200613_161240.log.json) | + +### Pascal Context + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | R-101-D8 | 480x480 | 40000 | - | 9.93 | V100 | 44.43 | 45.63 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_40k_pascal_context/fcn_r101-d8_480x480_40k_pascal_context_20210421_154757-b5e97937.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_40k_pascal_context/fcn_r101-d8_480x480_40k_pascal_context-20210421_154757.log.json) | +| FCN | R-101-D8 | 480x480 | 80000 | - | - | V100 | 44.13 | 45.26 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_80k_pascal_context/fcn_r101-d8_480x480_80k_pascal_context_20210421_163310-4711813f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_80k_pascal_context/fcn_r101-d8_480x480_80k_pascal_context-20210421_163310.log.json) | + +### Pascal Context 59 + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | R-101-D8 | 480x480 | 40000 | - | - | V100 | 48.42 | 50.4 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_40k_pascal_context_59/fcn_r101-d8_480x480_40k_pascal_context_59_20210415_230724-8cf83682.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_40k_pascal_context_59/fcn_r101-d8_480x480_40k_pascal_context_59-20210415_230724.log.json) | +| FCN | R-101-D8 | 480x480 | 80000 | - | - | V100 | 49.35 | 51.38 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_80k_pascal_context_59/fcn_r101-d8_480x480_80k_pascal_context_59_20210416_110804-9a6f2c94.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_80k_pascal_context_59/fcn_r101-d8_480x480_80k_pascal_context_59-20210416_110804.log.json) | + +Note: + +- `FP16` means Mixed Precision (FP16) is adopted in training. +- `FCN D6` means dilation rate of convolution operator in FCN is 6. + +## Citation + +```bibtex +@article{shelhamer2017fully, + title={Fully convolutional networks for semantic segmentation}, + author={Shelhamer, Evan and Long, Jonathan and Darrell, Trevor}, + journal={IEEE transactions on pattern analysis and machine intelligence}, + volume={39}, + number={4}, + pages={640--651}, + year={2017}, + publisher={IEEE Trans Pattern Anal Mach Intell} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..8f2cd02 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './fcn-d6_r50-d16_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..4782b30 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './fcn-d6_r50-d16_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..5f654b4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..91eca1c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..62e6127 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,4 @@ +_base_ = './fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..1b8d247 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,4 @@ +_base_ = './fcn-d6_r50b-d16_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..9a1efb4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(dilations=(1, 1, 1, 2), strides=(1, 2, 2, 1)), + decode_head=dict(dilation=6), + auxiliary_head=dict(dilation=6)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..2b2a6f4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(dilations=(1, 1, 1, 2), strides=(1, 2, 2, 1)), + decode_head=dict(align_corners=True, dilation=6), + auxiliary_head=dict(align_corners=True, dilation=6), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..e6cca00 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(dilations=(1, 1, 1, 2), strides=(1, 2, 2, 1)), + decode_head=dict(dilation=6), + auxiliary_head=dict(dilation=6)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..990ff9c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(dilations=(1, 1, 1, 2), strides=(1, 2, 2, 1)), + decode_head=dict(align_corners=True, dilation=6), + auxiliary_head=dict(align_corners=True, dilation=6), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..7d470a5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..e9093ea --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..b3ec0a7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..1f83fe2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..4527b3b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..6ce1124 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..b4d9487 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py @@ -0,0 +1,6 @@ +_base_ = './fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py' +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005), + loss_scale=512.) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..b1f5c5c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..61ee96f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..1161193 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb4-40k_pascal-context-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..f3a6dbc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb4-40k_pascal-context-59-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..b68b6e0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..3facce3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..1161193 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb4-40k_pascal-context-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..cebe330 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb4-80k_pascal-context-59-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..e53751b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,4 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..daa6502 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,4 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..4073148 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..2c1d2b6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,9 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..08ab467 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet18', + backbone=dict(type='ResNet', depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..c591ebe --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,9 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet18', + backbone=dict(type='ResNet', depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..4fba723 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..d57afe1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..6b1fdae --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..8a713fd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..258b9fb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..eac86d5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/pascal_voc12_aug.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..d99cb0d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/pascal_context.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + auxiliary_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..64c9410 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + auxiliary_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..42edb46 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/pascal_voc12_aug.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..099f6af --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..1eeafb8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', '../_base_/datasets/pascal_context.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + auxiliary_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..c11a9bb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50-d8_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + auxiliary_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..44821fd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..a85b391 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './fcn_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/fcn/metafile.yaml b/Seg_All_In_One_MMSeg/configs/fcn/metafile.yaml new file mode 100644 index 0000000..f3d80f6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/metafile.yaml @@ -0,0 +1,997 @@ +Collections: +- Name: FCN + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + - Pascal Context + - Pascal Context 59 + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + README: configs/fcn/README.md + Frameworks: + - PyTorch +Models: +- Name: fcn_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 72.25 + mIoU(ms+flip): 73.36 + Config: configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 5.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_40k_cityscapes/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608-efe53f0d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_40k_cityscapes/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.45 + mIoU(ms+flip): 76.58 + Config: configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 9.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x1024_40k_cityscapes/fcn_r101-d8_512x1024_40k_cityscapes_20200604_181852-a883d3a1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x1024_40k_cityscapes/fcn_r101-d8_512x1024_40k_cityscapes_20200604_181852.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 71.47 + mIoU(ms+flip): 72.54 + Config: configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_769x769_40k_cityscapes/fcn_r50-d8_769x769_40k_cityscapes_20200606_113104-977b5d02.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_769x769_40k_cityscapes/fcn_r50-d8_769x769_40k_cityscapes_20200606_113104.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.93 + mIoU(ms+flip): 75.14 + Config: configs/fcn/fcn_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 10.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_769x769_40k_cityscapes/fcn_r101-d8_769x769_40k_cityscapes_20200606_113208-7d4ab69c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_769x769_40k_cityscapes/fcn_r101-d8_769x769_40k_cityscapes_20200606_113208.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r18-d8_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 71.11 + mIoU(ms+flip): 72.91 + Config: configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18-d8_512x1024_80k_cityscapes/fcn_r18-d8_512x1024_80k_cityscapes_20201225_021327-6c50f8b4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18-d8_512x1024_80k_cityscapes/fcn_r18-d8_512x1024_80k_cityscapes-20201225_021327.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.61 + mIoU(ms+flip): 74.24 + Config: configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_80k_cityscapes/fcn_r50-d8_512x1024_80k_cityscapes_20200606_113019-03aa804d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_80k_cityscapes/fcn_r50-d8_512x1024_80k_cityscapes_20200606_113019.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.13 + mIoU(ms+flip): 75.94 + Config: configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x1024_80k_cityscapes/fcn_r101-d8_512x1024_80k_cityscapes_20200606_113038-3fb937eb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x1024_80k_cityscapes/fcn_r101-d8_512x1024_80k_cityscapes_20200606_113038.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb2-amp-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.8 + Config: configs/fcn/fcn_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - FCN + - (FP16) + Training Resources: 4x V100 GPUS + Memory (GB): 5.37 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_fp16_512x1024_80k_cityscapes/fcn_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230921-fb13e883.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_fp16_512x1024_80k_cityscapes/fcn_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230921.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r18-d8_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 70.8 + mIoU(ms+flip): 73.16 + Config: configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18-d8_769x769_80k_cityscapes/fcn_r18-d8_769x769_80k_cityscapes_20201225_021451-9739d1b8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18-d8_769x769_80k_cityscapes/fcn_r18-d8_769x769_80k_cityscapes-20201225_021451.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 72.64 + mIoU(ms+flip): 73.32 + Config: configs/fcn/fcn_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_769x769_80k_cityscapes/fcn_r50-d8_769x769_80k_cityscapes_20200606_195749-f5caeabc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_769x769_80k_cityscapes/fcn_r50-d8_769x769_80k_cityscapes_20200606_195749.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.52 + mIoU(ms+flip): 76.61 + Config: configs/fcn/fcn_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_769x769_80k_cityscapes/fcn_r101-d8_769x769_80k_cityscapes_20200606_214354-45cbac68.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_769x769_80k_cityscapes/fcn_r101-d8_769x769_80k_cityscapes_20200606_214354.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r18b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 70.24 + mIoU(ms+flip): 72.77 + Config: configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18b-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18b-d8_512x1024_80k_cityscapes/fcn_r18b-d8_512x1024_80k_cityscapes_20201225_230143-92c0f445.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18b-d8_512x1024_80k_cityscapes/fcn_r18b-d8_512x1024_80k_cityscapes-20201225_230143.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.65 + mIoU(ms+flip): 77.59 + Config: configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 5.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50b-d8_512x1024_80k_cityscapes/fcn_r50b-d8_512x1024_80k_cityscapes_20201225_094221-82957416.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50b-d8_512x1024_80k_cityscapes/fcn_r50b-d8_512x1024_80k_cityscapes-20201225_094221.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.37 + mIoU(ms+flip): 78.77 + Config: configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 9.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101b-d8_512x1024_80k_cityscapes/fcn_r101b-d8_512x1024_80k_cityscapes_20201226_160213-4543858f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101b-d8_512x1024_80k_cityscapes/fcn_r101b-d8_512x1024_80k_cityscapes-20201226_160213.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r18b-d8_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 69.66 + mIoU(ms+flip): 72.07 + Config: configs/fcn/fcn_r18b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18b-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18b-d8_769x769_80k_cityscapes/fcn_r18b-d8_769x769_80k_cityscapes_20201226_004430-32d504e5.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r18b-d8_769x769_80k_cityscapes/fcn_r18b-d8_769x769_80k_cityscapes-20201226_004430.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50b-d8_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.83 + mIoU(ms+flip): 76.6 + Config: configs/fcn/fcn_r50b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50b-d8_769x769_80k_cityscapes/fcn_r50b-d8_769x769_80k_cityscapes_20201225_094223-94552d38.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50b-d8_769x769_80k_cityscapes/fcn_r50b-d8_769x769_80k_cityscapes-20201225_094223.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101b-d8_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.02 + mIoU(ms+flip): 78.67 + Config: configs/fcn/fcn_r101b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 10.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101b-d8_769x769_80k_cityscapes/fcn_r101b-d8_769x769_80k_cityscapes_20201226_170012-82be37e2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101b-d8_769x769_80k_cityscapes/fcn_r101b-d8_769x769_80k_cityscapes-20201226_170012.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r50-d16_4xb2-40k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.06 + mIoU(ms+flip): 78.85 + Config: configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Memory (GB): 3.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_512x1024_40k_cityscapes/fcn_d6_r50-d16_512x1024_40k_cityscapes_20210305_130133-98d5d1bc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_512x1024_40k_cityscapes/fcn_d6_r50-d16_512x1024_40k_cityscapes-20210305_130133.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.27 + mIoU(ms+flip): 78.88 + Config: configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_512x1024_80k_cityscapes/fcn_d6_r50-d16_512x1024_80k_cityscapes_20210306_115604-133c292f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_512x1024_80k_cityscapes/fcn_d6_r50-d16_512x1024_80k_cityscapes-20210306_115604.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r50-d16_4xb2-40k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.82 + mIoU(ms+flip): 78.22 + Config: configs/fcn/fcn-d6_r50-d16_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Memory (GB): 3.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_769x769_40k_cityscapes/fcn_d6_r50-d16_769x769_40k_cityscapes_20210305_185744-1aab18ed.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_769x769_40k_cityscapes/fcn_d6_r50-d16_769x769_40k_cityscapes-20210305_185744.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.04 + mIoU(ms+flip): 78.4 + Config: configs/fcn/fcn-d6_r50-d16_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_769x769_80k_cityscapes/fcn_d6_r50-d16_769x769_80k_cityscapes_20210305_200413-109d88eb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50-d16_769x769_80k_cityscapes/fcn_d6_r50-d16_769x769_80k_cityscapes-20210305_200413.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r101-d16_4xb2-40k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.36 + mIoU(ms+flip): 79.18 + Config: configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Memory (GB): 4.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_512x1024_40k_cityscapes/fcn_d6_r101-d16_512x1024_40k_cityscapes_20210305_130337-9cf2b450.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_512x1024_40k_cityscapes/fcn_d6_r101-d16_512x1024_40k_cityscapes-20210305_130337.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r101-d16_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.46 + mIoU(ms+flip): 80.42 + Config: configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_512x1024_80k_cityscapes/fcn_d6_r101-d16_512x1024_80k_cityscapes_20210308_102747-cb336445.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_512x1024_80k_cityscapes/fcn_d6_r101-d16_512x1024_80k_cityscapes-20210308_102747.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r101-d16_4xb2-40k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.28 + mIoU(ms+flip): 78.95 + Config: configs/fcn/fcn-d6_r101-d16_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Memory (GB): 5.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_769x769_40k_cityscapes/fcn_d6_r101-d16_769x769_40k_cityscapes_20210308_102453-60b114e9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_769x769_40k_cityscapes/fcn_d6_r101-d16_769x769_40k_cityscapes-20210308_102453.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r101-d16_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.06 + mIoU(ms+flip): 79.58 + Config: configs/fcn/fcn-d6_r101-d16_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_769x769_80k_cityscapes/fcn_d6_r101-d16_769x769_80k_cityscapes_20210306_120016-e33adc4f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101-d16_769x769_80k_cityscapes/fcn_d6_r101-d16_769x769_80k_cityscapes-20210306_120016.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r50b-d16_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.99 + mIoU(ms+flip): 79.03 + Config: configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Memory (GB): 3.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50b-d16_512x1024_80k_cityscapes/fcn_d6_r50b-d16_512x1024_80k_cityscapes_20210311_125550-6a0b62e9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50b_d16_512x1024_80k_cityscapes/fcn_d6_r50b_d16_512x1024_80k_cityscapes-20210311_125550.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r50b-d16_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.86 + mIoU(ms+flip): 78.52 + Config: configs/fcn/fcn-d6_r50b-d16_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Memory (GB): 3.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50b-d16_769x769_80k_cityscapes/fcn_d6_r50b-d16_769x769_80k_cityscapes_20210311_131012-d665f231.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r50b_d16_769x769_80k_cityscapes/fcn_d6_r50b_d16_769x769_80k_cityscapes-20210311_131012.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r101b-d16_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.72 + mIoU(ms+flip): 79.53 + Config: configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Memory (GB): 4.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101b-d16_512x1024_80k_cityscapes/fcn_d6_r101b-d16_512x1024_80k_cityscapes_20210311_144305-3f2eb5b4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101b_d16_512x1024_80k_cityscapes/fcn_d6_r101b_d16_512x1024_80k_cityscapes-20210311_144305.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn-d6_r101b-d16_4xb2-80k_cityscapes-769x769 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.34 + mIoU(ms+flip): 78.91 + Config: configs/fcn/fcn-d6_r101b-d16_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D16 + - FCN + - (D6) + Training Resources: 4x TITAN Xp GPUS + Memory (GB): 4.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101b-d16_769x769_80k_cityscapes/fcn_d6_r101b-d16_769x769_80k_cityscapes_20210311_154527-c4d8bfbc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_d6_r101b_d16_769x769_80k_cityscapes/fcn_d6_r101b_d16_769x769_80k_cityscapes-20210311_154527.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 35.94 + mIoU(ms+flip): 37.94 + Config: configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 8.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_80k_ade20k/fcn_r50-d8_512x512_80k_ade20k_20200614_144016-f8ac5082.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_80k_ade20k/fcn_r50-d8_512x512_80k_ade20k_20200614_144016.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 39.61 + mIoU(ms+flip): 40.83 + Config: configs/fcn/fcn_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 12.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_80k_ade20k/fcn_r101-d8_512x512_80k_ade20k_20200615_014143-bc1809f7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_80k_ade20k/fcn_r101-d8_512x512_80k_ade20k_20200615_014143.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 36.1 + mIoU(ms+flip): 38.08 + Config: configs/fcn/fcn_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_160k_ade20k/fcn_r50-d8_512x512_160k_ade20k_20200615_100713-4edbc3b4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_160k_ade20k/fcn_r50-d8_512x512_160k_ade20k_20200615_100713.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 39.91 + mIoU(ms+flip): 41.4 + Config: configs/fcn/fcn_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_160k_ade20k/fcn_r101-d8_512x512_160k_ade20k_20200615_105816-fd192bd5.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_160k_ade20k/fcn_r101-d8_512x512_160k_ade20k_20200615_105816.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 67.08 + mIoU(ms+flip): 69.94 + Config: configs/fcn/fcn_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 5.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_20k_voc12aug/fcn_r50-d8_512x512_20k_voc12aug_20200617_010715-52dc5306.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_20k_voc12aug/fcn_r50-d8_512x512_20k_voc12aug_20200617_010715.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 71.16 + mIoU(ms+flip): 73.57 + Config: configs/fcn/fcn_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 9.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_20k_voc12aug/fcn_r101-d8_512x512_20k_voc12aug_20200617_010842-0bb4e798.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_20k_voc12aug/fcn_r101-d8_512x512_20k_voc12aug_20200617_010842.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 66.97 + mIoU(ms+flip): 69.04 + Config: configs/fcn/fcn_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_40k_voc12aug/fcn_r50-d8_512x512_40k_voc12aug_20200613_161222-5e2dbf40.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x512_40k_voc12aug/fcn_r50-d8_512x512_40k_voc12aug_20200613_161222.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 69.91 + mIoU(ms+flip): 72.38 + Config: configs/fcn/fcn_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_40k_voc12aug/fcn_r101-d8_512x512_40k_voc12aug_20200613_161240-4c8bcefd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_512x512_40k_voc12aug/fcn_r101-d8_512x512_40k_voc12aug_20200613_161240.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb4-40k_pascal-context-480x480 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 44.43 + mIoU(ms+flip): 45.63 + Config: configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_40k_pascal_context/fcn_r101-d8_480x480_40k_pascal_context_20210421_154757-b5e97937.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_40k_pascal_context/fcn_r101-d8_480x480_40k_pascal_context-20210421_154757.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb4-80k_pascal-context-480x480 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 44.13 + mIoU(ms+flip): 45.26 + Config: configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_80k_pascal_context/fcn_r101-d8_480x480_80k_pascal_context_20210421_163310-4711813f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_80k_pascal_context/fcn_r101-d8_480x480_80k_pascal_context-20210421_163310.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb4-40k_pascal-context-59-480x480 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 48.42 + mIoU(ms+flip): 50.4 + Config: configs/fcn/fcn_r101-d8_4xb4-40k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_40k_pascal_context_59/fcn_r101-d8_480x480_40k_pascal_context_59_20210415_230724-8cf83682.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_40k_pascal_context_59/fcn_r101-d8_480x480_40k_pascal_context_59-20210415_230724.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch +- Name: fcn_r101-d8_4xb4-80k_pascal-context-59-480x480 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 49.35 + mIoU(ms+flip): 51.38 + Config: configs/fcn/fcn_r101-d8_4xb4-80k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - R-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_80k_pascal_context_59/fcn_r101-d8_480x480_80k_pascal_context_59_20210416_110804-9a6f2c94.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r101-d8_480x480_80k_pascal_context_59/fcn_r101-d8_480x480_80k_pascal_context_59-20210416_110804.log.json + Paper: + Title: Fully Convolutional Networks for Semantic Segmentation + URL: https://arxiv.org/abs/1411.4038 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fcn_head.py#L11 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py b/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py new file mode 100644 index 0000000..174383d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/torchvision_012/resnet101.pth', + backbone=dict( + type='ResNet', + depth=101, + strides=(1, 2, 2, 1), + dilations=(1, 1, 1, 2), + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + dilation=6, + channels=512, + in_channels=2048, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + dilation=6, + channels=256, + in_channels=1024, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 1024), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py new file mode 100644 index 0000000..df90944 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-no_testslide.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/torchvision_012/resnet101.pth', + backbone=dict( + type='ResNet', + depth=101, + strides=(1, 2, 1, 1), + dilations=(1, 1, 2, 4), + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + dilation=1, + channels=512, + in_channels=2048, + num_classes=36, + decode_head_loss_decode_dict=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + dilation=1, + channels=256, + in_channels=1024, + num_classes=36, + auxiliary_head_loss_decode_dict=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r18_Pre_g1-40k_check_4000_my_dataset_model-769x769-testslide.py b/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r18_Pre_g1-40k_check_4000_my_dataset_model-769x769-testslide.py new file mode 100644 index 0000000..7e2edfd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r18_Pre_g1-40k_check_4000_my_dataset_model-769x769-testslide.py @@ -0,0 +1,114 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (769, 769) + +data_preprocessor = dict( + size=(769, 769), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/torchvision_012/resnet18.pth', + backbone=dict( + type='ResNet', + depth=18, + strides=(1, 2, 2, 1), + dilations=(1, 1, 1, 2), + ), + data_preprocessor=dict( + size=(769, 769), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + dilation=6, + channels=128, + in_channels=512, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + ), + auxiliary_head=dict( + dilation=6, + channels=64, + in_channels=256, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=True, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(769, 769), + stride=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r50_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py b/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r50_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py new file mode 100644 index 0000000..0e15f19 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/fcn/my_fcn_r50_r50_Pre_g1-40k_check_4000_my_dataset_model-512x1024-no_testslide.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/models/fcn_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + backbone=dict( + type='ResNetV1c', + depth=50, + strides=(1, 2, 2, 1), + dilations=(1, 1, 1, 2), + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + dilation=6, + channels=512, + in_channels=2048, + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=dict( + dilation=6, + channels=256, + in_channels=1024, + num_classes=36, + auxiliary_head_loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=False, + ), +) + +test_cfg = dict( + crop_size=(512, 1024), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/README.md b/Seg_All_In_One_MMSeg/configs/gcnet/README.md new file mode 100644 index 0000000..ba1a21e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/README.md @@ -0,0 +1,68 @@ +# GCNet + +> [GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond](https://arxiv.org/abs/1904.11492) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +The Non-Local Network (NLNet) presents a pioneering approach for capturing long-range dependencies, via aggregating query-specific global context to each query position. However, through a rigorous empirical analysis, we have found that the global contexts modeled by non-local network are almost the same for different query positions within an image. In this paper, we take advantage of this finding to create a simplified network based on a query-independent formulation, which maintains the accuracy of NLNet but with significantly less computation. We further observe that this simplified design shares similar structure with Squeeze-Excitation Network (SENet). Hence we unify them into a three-step general framework for global context modeling. Within the general framework, we design a better instantiation, called the global context (GC) block, which is lightweight and can effectively model the global context. The lightweight property allows us to apply it for multiple layers in a backbone network to construct a global context network (GCNet), which generally outperforms both simplified NLNet and SENet on major benchmarks for various recognition tasks. The code and configurations are released at [this https URL](https://github.com/xvjiarui/GCNet). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| GCNet | R-50-D8 | 512x1024 | 40000 | 5.8 | 3.93 | V100 | 77.69 | 78.56 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x1024_40k_cityscapes/gcnet_r50-d8_512x1024_40k_cityscapes_20200618_074436-4b0fd17b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x1024_40k_cityscapes/gcnet_r50-d8_512x1024_40k_cityscapes_20200618_074436.log.json) | +| GCNet | R-101-D8 | 512x1024 | 40000 | 9.2 | 2.61 | V100 | 78.28 | 79.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x1024_40k_cityscapes/gcnet_r101-d8_512x1024_40k_cityscapes_20200618_074436-5e62567f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x1024_40k_cityscapes/gcnet_r101-d8_512x1024_40k_cityscapes_20200618_074436.log.json) | +| GCNet | R-50-D8 | 769x769 | 40000 | 6.5 | 1.67 | V100 | 78.12 | 80.09 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_769x769_40k_cityscapes/gcnet_r50-d8_769x769_40k_cityscapes_20200618_182814-a26f4471.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_769x769_40k_cityscapes/gcnet_r50-d8_769x769_40k_cityscapes_20200618_182814.log.json) | +| GCNet | R-101-D8 | 769x769 | 40000 | 10.5 | 1.13 | V100 | 78.95 | 80.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_769x769_40k_cityscapes/gcnet_r101-d8_769x769_40k_cityscapes_20200619_092550-ca4f0a84.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_769x769_40k_cityscapes/gcnet_r101-d8_769x769_40k_cityscapes_20200619_092550.log.json) | +| GCNet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 78.48 | 80.01 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x1024_80k_cityscapes/gcnet_r50-d8_512x1024_80k_cityscapes_20200618_074450-ef8f069b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x1024_80k_cityscapes/gcnet_r50-d8_512x1024_80k_cityscapes_20200618_074450.log.json) | +| GCNet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 79.03 | 79.84 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-512x1024.pyy) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x1024_80k_cityscapes/gcnet_r101-d8_512x1024_80k_cityscapes_20200618_074450-778ebf69.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x1024_80k_cityscapes/gcnet_r101-d8_512x1024_80k_cityscapes_20200618_074450.log.json) | +| GCNet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 78.68 | 80.66 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_769x769_80k_cityscapes/gcnet_r50-d8_769x769_80k_cityscapes_20200619_092516-4839565b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_769x769_80k_cityscapes/gcnet_r50-d8_769x769_80k_cityscapes_20200619_092516.log.json) | +| GCNet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 79.18 | 80.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_769x769_80k_cityscapes/gcnet_r101-d8_769x769_80k_cityscapes_20200619_092628-8e043423.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_769x769_80k_cityscapes/gcnet_r101-d8_769x769_80k_cityscapes_20200619_092628.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| GCNet | R-50-D8 | 512x512 | 80000 | 8.5 | 23.38 | V100 | 41.47 | 42.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_80k_ade20k/gcnet_r50-d8_512x512_80k_ade20k_20200614_185146-91a6da41.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_80k_ade20k/gcnet_r50-d8_512x512_80k_ade20k_20200614_185146.log.json) | +| GCNet | R-101-D8 | 512x512 | 80000 | 12 | 15.20 | V100 | 42.82 | 44.54 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_80k_ade20k/gcnet_r101-d8_512x512_80k_ade20k_20200615_020811-c3fcb6dd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_80k_ade20k/gcnet_r101-d8_512x512_80k_ade20k_20200615_020811.log.json) | +| GCNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 42.37 | 43.52 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_160k_ade20k/gcnet_r50-d8_512x512_160k_ade20k_20200615_224122-d95f3e1f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_160k_ade20k/gcnet_r50-d8_512x512_160k_ade20k_20200615_224122.log.json) | +| GCNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 43.69 | 45.21 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_160k_ade20k/gcnet_r101-d8_512x512_160k_ade20k_20200615_225406-615528d7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_160k_ade20k/gcnet_r101-d8_512x512_160k_ade20k_20200615_225406.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| GCNet | R-50-D8 | 512x512 | 20000 | 5.8 | 23.35 | V100 | 76.42 | 77.51 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_20k_voc12aug/gcnet_r50-d8_512x512_20k_voc12aug_20200617_165701-3cbfdab1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_20k_voc12aug/gcnet_r50-d8_512x512_20k_voc12aug_20200617_165701.log.json) | +| GCNet | R-101-D8 | 512x512 | 20000 | 9.2 | 14.80 | V100 | 77.41 | 78.56 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_20k_voc12aug/gcnet_r101-d8_512x512_20k_voc12aug_20200617_165713-6c720aa9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_20k_voc12aug/gcnet_r101-d8_512x512_20k_voc12aug_20200617_165713.log.json) | +| GCNet | R-50-D8 | 512x512 | 40000 | - | - | V100 | 76.24 | 77.63 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_40k_voc12aug/gcnet_r50-d8_512x512_40k_voc12aug_20200613_195105-9797336d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_40k_voc12aug/gcnet_r50-d8_512x512_40k_voc12aug_20200613_195105.log.json) | +| GCNet | R-101-D8 | 512x512 | 40000 | - | - | V100 | 77.84 | 78.59 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet/gcnet_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_40k_voc12aug/gcnet_r101-d8_512x512_40k_voc12aug_20200613_185806-1e38208d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_40k_voc12aug/gcnet_r101-d8_512x512_40k_voc12aug_20200613_185806.log.json) | + +## Citation + +```bibtex +@inproceedings{cao2019gcnet, + title={Gcnet: Non-local networks meet squeeze-excitation networks and beyond}, + author={Cao, Yue and Xu, Jiarui and Lin, Stephen and Wei, Fangyun and Hu, Han}, + booktitle={Proceedings of the IEEE International Conference on Computer Vision Workshops}, + pages={0--0}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..e8f7c55 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './gcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..887d17b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './gcnet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..aa47578 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './gcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..ddf4ad7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './gcnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..45285c0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './gcnet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..b466c40 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './gcnet_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..9c7f741 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './gcnet_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..61337db --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './gcnet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..f976bd9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..34ce822 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..5088929 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..f886f17 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..d3f5631 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..356b088 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..802b766 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..7327934 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/gcnet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/gcnet/metafile.yaml new file mode 100644 index 0000000..1f3c462 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/metafile.yaml @@ -0,0 +1,391 @@ +Collections: +- Name: GCNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + README: configs/gcnet/README.md + Frameworks: + - PyTorch +Models: +- Name: gcnet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.69 + mIoU(ms+flip): 78.56 + Config: configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - GCNet + Training Resources: 4x V100 GPUS + Memory (GB): 5.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x1024_40k_cityscapes/gcnet_r50-d8_512x1024_40k_cityscapes_20200618_074436-4b0fd17b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x1024_40k_cityscapes/gcnet_r50-d8_512x1024_40k_cityscapes_20200618_074436.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.28 + mIoU(ms+flip): 79.34 + Config: configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - GCNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x1024_40k_cityscapes/gcnet_r101-d8_512x1024_40k_cityscapes_20200618_074436-5e62567f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x1024_40k_cityscapes/gcnet_r101-d8_512x1024_40k_cityscapes_20200618_074436.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.12 + mIoU(ms+flip): 80.09 + Config: configs/gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - GCNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_769x769_40k_cityscapes/gcnet_r50-d8_769x769_40k_cityscapes_20200618_182814-a26f4471.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_769x769_40k_cityscapes/gcnet_r50-d8_769x769_40k_cityscapes_20200618_182814.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.95 + mIoU(ms+flip): 80.71 + Config: configs/gcnet/gcnet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - GCNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_769x769_40k_cityscapes/gcnet_r101-d8_769x769_40k_cityscapes_20200619_092550-ca4f0a84.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_769x769_40k_cityscapes/gcnet_r101-d8_769x769_40k_cityscapes_20200619_092550.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.48 + mIoU(ms+flip): 80.01 + Config: configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - GCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x1024_80k_cityscapes/gcnet_r50-d8_512x1024_80k_cityscapes_20200618_074450-ef8f069b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x1024_80k_cityscapes/gcnet_r50-d8_512x1024_80k_cityscapes_20200618_074450.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.03 + mIoU(ms+flip): 79.84 + Config: configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - GCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x1024_80k_cityscapes/gcnet_r101-d8_512x1024_80k_cityscapes_20200618_074450-778ebf69.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x1024_80k_cityscapes/gcnet_r101-d8_512x1024_80k_cityscapes_20200618_074450.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.68 + mIoU(ms+flip): 80.66 + Config: configs/gcnet/gcnet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - GCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_769x769_80k_cityscapes/gcnet_r50-d8_769x769_80k_cityscapes_20200619_092516-4839565b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_769x769_80k_cityscapes/gcnet_r50-d8_769x769_80k_cityscapes_20200619_092516.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.18 + mIoU(ms+flip): 80.71 + Config: configs/gcnet/gcnet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - GCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_769x769_80k_cityscapes/gcnet_r101-d8_769x769_80k_cityscapes_20200619_092628-8e043423.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_769x769_80k_cityscapes/gcnet_r101-d8_769x769_80k_cityscapes_20200619_092628.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.47 + mIoU(ms+flip): 42.85 + Config: configs/gcnet/gcnet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - GCNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_80k_ade20k/gcnet_r50-d8_512x512_80k_ade20k_20200614_185146-91a6da41.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_80k_ade20k/gcnet_r50-d8_512x512_80k_ade20k_20200614_185146.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.82 + mIoU(ms+flip): 44.54 + Config: configs/gcnet/gcnet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - GCNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_80k_ade20k/gcnet_r101-d8_512x512_80k_ade20k_20200615_020811-c3fcb6dd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_80k_ade20k/gcnet_r101-d8_512x512_80k_ade20k_20200615_020811.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.37 + mIoU(ms+flip): 43.52 + Config: configs/gcnet/gcnet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - GCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_160k_ade20k/gcnet_r50-d8_512x512_160k_ade20k_20200615_224122-d95f3e1f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_160k_ade20k/gcnet_r50-d8_512x512_160k_ade20k_20200615_224122.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.69 + mIoU(ms+flip): 45.21 + Config: configs/gcnet/gcnet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - GCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_160k_ade20k/gcnet_r101-d8_512x512_160k_ade20k_20200615_225406-615528d7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_160k_ade20k/gcnet_r101-d8_512x512_160k_ade20k_20200615_225406.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.42 + mIoU(ms+flip): 77.51 + Config: configs/gcnet/gcnet_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - GCNet + Training Resources: 4x V100 GPUS + Memory (GB): 5.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_20k_voc12aug/gcnet_r50-d8_512x512_20k_voc12aug_20200617_165701-3cbfdab1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_20k_voc12aug/gcnet_r50-d8_512x512_20k_voc12aug_20200617_165701.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.41 + mIoU(ms+flip): 78.56 + Config: configs/gcnet/gcnet_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - GCNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_20k_voc12aug/gcnet_r101-d8_512x512_20k_voc12aug_20200617_165713-6c720aa9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_20k_voc12aug/gcnet_r101-d8_512x512_20k_voc12aug_20200617_165713.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.24 + mIoU(ms+flip): 77.63 + Config: configs/gcnet/gcnet_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - GCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_40k_voc12aug/gcnet_r50-d8_512x512_40k_voc12aug_20200613_195105-9797336d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r50-d8_512x512_40k_voc12aug/gcnet_r50-d8_512x512_40k_voc12aug_20200613_195105.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch +- Name: gcnet_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: GCNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.84 + mIoU(ms+flip): 78.59 + Config: configs/gcnet/gcnet_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - GCNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_40k_voc12aug/gcnet_r101-d8_512x512_40k_voc12aug_20200613_185806-1e38208d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/gcnet/gcnet_r101-d8_512x512_40k_voc12aug/gcnet_r101-d8_512x512_40k_voc12aug_20200613_185806.log.json + Paper: + Title: 'GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond' + URL: https://arxiv.org/abs/1904.11492 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/gc_head.py#L10 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/gcnet/my_gcnet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py b/Seg_All_In_One_MMSeg/configs/gcnet/my_gcnet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py new file mode 100644 index 0000000..7915669 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/gcnet/my_gcnet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py @@ -0,0 +1,105 @@ +_base_ = [ + '../_base_/models/gcnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=True, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 1024), + stride=(341, 682), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/README.md b/Seg_All_In_One_MMSeg/configs/hrnet/README.md new file mode 100644 index 0000000..b529fc8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/README.md @@ -0,0 +1,122 @@ +# HRNet + +> [Deep High-Resolution Representation Learning for Human Pose Estimation](https://arxiv.org/abs/1908.07919) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +High-resolution representations are essential for position-sensitive vision problems, such as human pose estimation, semantic segmentation, and object detection. Existing state-of-the-art frameworks first encode the input image as a low-resolution representation through a subnetwork that is formed by connecting high-to-low resolution convolutions \\emph{in series} (e.g., ResNet, VGGNet), and then recover the high-resolution representation from the encoded low-resolution representation. Instead, our proposed network, named as High-Resolution Network (HRNet), maintains high-resolution representations through the whole process. There are two key characteristics: (i) Connect the high-to-low resolution convolution streams \\emph{in parallel}; (ii) Repeatedly exchange the information across resolutions. The benefit is that the resulting representation is semantically richer and spatially more precise. We show the superiority of the proposed HRNet in a wide range of applications, including human pose estimation, semantic segmentation, and object detection, suggesting that the HRNet is a stronger backbone for computer vision problems. All the codes are available at [this https URL](https://github.com/HRNet). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | HRNetV2p-W18-Small | 512x1024 | 40000 | 1.7 | 23.74 | V100 | 73.86 | 75.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_40k_cityscapes/fcn_hr18s_512x1024_40k_cityscapes_20200601_014216-93db27d0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_40k_cityscapes/fcn_hr18s_512x1024_40k_cityscapes_20200601_014216.log.json) | +| FCN | HRNetV2p-W18 | 512x1024 | 40000 | 2.9 | 12.97 | V100 | 77.19 | 78.92 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_40k_cityscapes/fcn_hr18_512x1024_40k_cityscapes_20200601_014216-f196fb4e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_40k_cityscapes/fcn_hr18_512x1024_40k_cityscapes_20200601_014216.log.json) | +| FCN | HRNetV2p-W48 | 512x1024 | 40000 | 6.2 | 6.42 | V100 | 78.48 | 79.69 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_40k_cityscapes/fcn_hr48_512x1024_40k_cityscapes_20200601_014240-a989b146.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_40k_cityscapes/fcn_hr48_512x1024_40k_cityscapes_20200601_014240.log.json) | +| FCN | HRNetV2p-W18-Small | 512x1024 | 80000 | - | - | V100 | 75.31 | 77.48 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_80k_cityscapes/fcn_hr18s_512x1024_80k_cityscapes_20200601_202700-1462b75d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_80k_cityscapes/fcn_hr18s_512x1024_80k_cityscapes_20200601_202700.log.json) | +| FCN | HRNetV2p-W18 | 512x1024 | 80000 | - | - | V100 | 78.65 | 80.35 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_80k_cityscapes/fcn_hr18_512x1024_80k_cityscapes_20200601_223255-4e7b345e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_80k_cityscapes/fcn_hr18_512x1024_80k_cityscapes_20200601_223255.log.json) | +| FCN | HRNetV2p-W48 | 512x1024 | 80000 | - | - | V100 | 79.93 | 80.72 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_80k_cityscapes/fcn_hr48_512x1024_80k_cityscapes_20200601_202606-58ea95d6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_80k_cityscapes/fcn_hr48_512x1024_80k_cityscapes_20200601_202606.log.json) | +| FCN | HRNetV2p-W18-Small | 512x1024 | 160000 | - | - | V100 | 76.31 | 78.31 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb2-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_160k_cityscapes/fcn_hr18s_512x1024_160k_cityscapes_20200602_190901-4a0797ea.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_160k_cityscapes/fcn_hr18s_512x1024_160k_cityscapes_20200602_190901.log.json) | +| FCN | HRNetV2p-W18 | 512x1024 | 160000 | - | - | V100 | 78.80 | 80.74 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb2-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_160k_cityscapes/fcn_hr18_512x1024_160k_cityscapes_20200602_190822-221e4a4f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_160k_cityscapes/fcn_hr18_512x1024_160k_cityscapes_20200602_190822.log.json) | +| FCN | HRNetV2p-W48 | 512x1024 | 160000 | - | - | V100 | 80.65 | 81.92 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb2-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_160k_cityscapes/fcn_hr48_512x1024_160k_cityscapes_20200602_190946-59b7973e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_160k_cityscapes/fcn_hr48_512x1024_160k_cityscapes_20200602_190946.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| FCN | HRNetV2p-W18-Small | 512x512 | 80000 | 3.8 | 38.66 | V100 | 31.38 | 32.45 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_ade20k/fcn_hr18s_512x512_80k_ade20k_20200614_144345-77fc814a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_ade20k/fcn_hr18s_512x512_80k_ade20k_20200614_144345.log.json) | +| FCN | HRNetV2p-W18 | 512x512 | 80000 | 4.9 | 22.57 | V100 | 36.27 | 37.28 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_ade20k/fcn_hr18_512x512_80k_ade20k_20210827_114910-6c9382c0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_ade20k/fcn_hr18_512x512_80k_ade20k_20210827_114910.log.json) | +| FCN | HRNetV2p-W48 | 512x512 | 80000 | 8.2 | 21.23 | V100 | 41.90 | 43.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_ade20k/fcn_hr48_512x512_80k_ade20k_20200614_193946-7ba5258d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_ade20k/fcn_hr48_512x512_80k_ade20k_20200614_193946.log.json) | +| FCN | HRNetV2p-W18-Small | 512x512 | 160000 | - | - | V100 | 33.07 | 34.56 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_160k_ade20k/fcn_hr18s_512x512_160k_ade20k_20210829_174739-f1e7c2e7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_160k_ade20k/fcn_hr18s_512x512_160k_ade20k_20210829_174739.log.json) | +| FCN | HRNetV2p-W18 | 512x512 | 160000 | - | - | V100 | 36.79 | 38.58 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_160k_ade20k/fcn_hr18_512x512_160k_ade20k_20200614_214426-ca961836.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_160k_ade20k/fcn_hr18_512x512_160k_ade20k_20200614_214426.log.json) | +| FCN | HRNetV2p-W48 | 512x512 | 160000 | - | - | V100 | 42.02 | 43.86 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_160k_ade20k/fcn_hr48_512x512_160k_ade20k_20200614_214407-a52fc02c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_160k_ade20k/fcn_hr48_512x512_160k_ade20k_20200614_214407.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | HRNetV2p-W18-Small | 512x512 | 20000 | 1.8 | 43.36 | V100 | 65.5 | 68.89 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_20k_voc12aug/fcn_hr18s_512x512_20k_voc12aug_20210829_174910-0aceadb4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_20k_voc12aug/fcn_hr18s_512x512_20k_voc12aug_20210829_174910.log.json) | +| FCN | HRNetV2p-W18 | 512x512 | 20000 | 2.9 | 23.48 | V100 | 72.30 | 74.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_20k_voc12aug/fcn_hr18_512x512_20k_voc12aug_20200617_224503-488d45f7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_20k_voc12aug/fcn_hr18_512x512_20k_voc12aug_20200617_224503.log.json) | +| FCN | HRNetV2p-W48 | 512x512 | 20000 | 6.2 | 22.05 | V100 | 75.87 | 78.58 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_20k_voc12aug/fcn_hr48_512x512_20k_voc12aug_20200617_224419-89de05cd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_20k_voc12aug/fcn_hr48_512x512_20k_voc12aug_20200617_224419.log.json) | +| FCN | HRNetV2p-W18-Small | 512x512 | 40000 | - | - | V100 | 66.61 | 70.00 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_40k_voc12aug/fcn_hr18s_512x512_40k_voc12aug_20200614_000648-4f8d6e7f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_40k_voc12aug/fcn_hr18s_512x512_40k_voc12aug_20200614_000648.log.json) | +| FCN | HRNetV2p-W18 | 512x512 | 40000 | - | - | V100 | 72.90 | 75.59 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_40k_voc12aug/fcn_hr18_512x512_40k_voc12aug_20200613_224401-1b4b76cd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_40k_voc12aug/fcn_hr18_512x512_40k_voc12aug_20200613_224401.log.json) | +| FCN | HRNetV2p-W48 | 512x512 | 40000 | - | - | V100 | 76.24 | 78.49 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_40k_voc12aug/fcn_hr48_512x512_40k_voc12aug_20200613_222111-1b0f18bc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_40k_voc12aug/fcn_hr48_512x512_40k_voc12aug_20200613_222111.log.json) | + +### Pascal Context + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| FCN | HRNetV2p-W48 | 480x480 | 40000 | 6.1 | 8.86 | V100 | 45.14 | 47.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_40k_pascal_context/fcn_hr48_480x480_40k_pascal_context_20200911_164852-667d00b0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_40k_pascal_context/fcn_hr48_480x480_40k_pascal_context-20200911_164852.log.json) | +| FCN | HRNetV2p-W48 | 480x480 | 80000 | - | - | V100 | 45.84 | 47.84 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_80k_pascal_context/fcn_hr48_480x480_80k_pascal_context_20200911_155322-847a6711.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_80k_pascal_context/fcn_hr48_480x480_80k_pascal_context-20200911_155322.log.json) | + +### Pascal Context 59 + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| FCN | HRNetV2p-W48 | 480x480 | 40000 | - | - | V100 | 50.33 | 52.83 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_40k_pascal_context_59/fcn_hr48_480x480_40k_pascal_context_59_20210410_122738-b808b8b2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_40k_pascal_context_59/fcn_hr48_480x480_40k_pascal_context_59-20210410_122738.log.json) | +| FCN | HRNetV2p-W48 | 480x480 | 80000 | - | - | V100 | 51.12 | 53.56 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_80k_pascal_context_59/fcn_hr48_480x480_80k_pascal_context_59_20210411_003240-3ae7081e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_80k_pascal_context_59/fcn_hr48_480x480_80k_pascal_context_59-20210411_003240.log.json) | + +### LoveDA + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | HRNetV2p-W18-Small | 512x512 | 80000 | 1.59 | 24.87 | V100 | 49.28 | 49.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb4-80k_loveda-512x512.pyy) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_loveda/fcn_hr18s_512x512_80k_loveda_20211210_203228-60a86a7a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_loveda/fcn_hr18s_512x512_80k_loveda_20211210_203228.log.json) | +| FCN | HRNetV2p-W18 | 512x512 | 80000 | 2.76 | 12.92 | V100 | 50.81 | 50.95 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb4-80k_loveda-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_loveda/fcn_hr18_512x512_80k_loveda_20211210_203952-93d9c3b3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_loveda/fcn_hr18_512x512_80k_loveda_20211210_203952.log.json) | +| FCN | HRNetV2p-W48 | 512x512 | 80000 | 6.20 | 9.61 | V100 | 51.42 | 51.64 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-80k_loveda-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_loveda/fcn_hr48_512x512_80k_loveda_20211211_044756-67072f55.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_loveda/fcn_hr48_512x512_80k_loveda_20211211_044756.log.json) | + +### Potsdam + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| FCN | HRNetV2p-W18-Small | 512x512 | 80000 | 1.58 | 36.00 | V100 | 77.64 | 78.8 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_potsdam/fcn_hr18s_512x512_80k_potsdam_20211218_205517-ba32af63.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_potsdam/fcn_hr18s_512x512_80k_potsdam_20211218_205517.log.json) | +| FCN | HRNetV2p-W18 | 512x512 | 80000 | 2.76 | 19.25 | V100 | 78.26 | 79.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_potsdam/fcn_hr18_512x512_80k_potsdam_20211218_205517-5d0387ad.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_potsdam/fcn_hr18_512x512_80k_potsdam_20211218_205517.log.json) | +| FCN | HRNetV2p-W48 | 512x512 | 80000 | 6.20 | 16.42 | V100 | 78.39 | 79.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_potsdam/fcn_hr48_512x512_80k_potsdam_20211219_020601-97434c78.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_potsdam/fcn_hr48_512x512_80k_potsdam_20211219_020601.log.json) | + +### Vaihingen + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| FCN | HRNetV2p-W18-Small | 512x512 | 80000 | 1.58 | 38.11 | V100 | 71.81 | 73.1 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_4x4_512x512_80k_vaihingen/fcn_hr18s_4x4_512x512_80k_vaihingen_20211231_230909-b23aae02.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_4x4_512x512_80k_vaihingen/fcn_hr18s_4x4_512x512_80k_vaihingen_20211231_230909.log.json) | +| FCN | HRNetV2p-W18 | 512x512 | 80000 | 2.76 | 19.55 | V100 | 72.57 | 74.09 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_4x4_512x512_80k_vaihingen/fcn_hr18_4x4_512x512_80k_vaihingen_20211231_231216-2ec3ae8a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_4x4_512x512_80k_vaihingen/fcn_hr18_4x4_512x512_80k_vaihingen_20211231_231216.log.json) | +| FCN | HRNetV2p-W48 | 512x512 | 80000 | 6.20 | 17.25 | V100 | 72.50 | 73.52 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_4x4_512x512_80k_vaihingen/fcn_hr48_4x4_512x512_80k_vaihingen_20211231_231244-7133cb22.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_4x4_512x512_80k_vaihingen/fcn_hr48_4x4_512x512_80k_vaihingen_20211231_231244.log.json) | + +### iSAID + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | HRNetV2p-W18-Small | 896x896 | 80000 | 4.95 | 13.84 | V100 | 62.30 | 62.97 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18s_4xb4-80k_isaid-896x896.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_4x4_896x896_80k_isaid/fcn_hr18s_4x4_896x896_80k_isaid_20220118_001603-3cc0769b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_4x4_896x896_80k_isaid/fcn_hr18s_4x4_896x896_80k_isaid_20220118_001603.log.json) | +| FCN | HRNetV2p-W18 | 896x896 | 80000 | 8.30 | 7.71 | V100 | 65.06 | 65.60 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr18_4xb4-80k_isaid-896x896.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_4x4_896x896_80k_isaid/fcn_hr18_4x4_896x896_80k_isaid_20220110_182230-49bf752e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_4x4_896x896_80k_isaid/fcn_hr18_4x4_896x896_80k_isaid_20220110_182230.log.json) | +| FCN | HRNetV2p-W48 | 896x896 | 80000 | 16.89 | 7.34 | V100 | 67.80 | 68.53 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet/fcn_hr48_4xb4-80k_isaid-896x896.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_4x4_896x896_80k_isaid/fcn_hr48_4x4_896x896_80k_isaid_20220114_174643-547fc420.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_4x4_896x896_80k_isaid/fcn_hr48_4x4_896x896_80k_isaid_20220114_174643.log.json) | + +Note: + +- `896x896` is the Crop Size of iSAID dataset, which is followed by the implementation of [PointFlow: Flowing Semantics Through Points for Aerial Image Segmentation](https://arxiv.org/pdf/2103.06564.pdf) + +## Citation + +```bibtext +@inproceedings{SunXLW19, + title={Deep High-Resolution Representation Learning for Human Pose Estimation}, + author={Ke Sun and Bin Xiao and Dong Liu and Jingdong Wang}, + booktitle={CVPR}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..0b37463 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-160k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..598b938 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..eb7da49 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..c4f732c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..107df6b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/pascal_voc12_aug.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..f744bae --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/pascal_context.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..0daaa35 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/pascal_context_59.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..2aa16b1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/pascal_voc12_aug.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..029b7d0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_isaid-896x896.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_isaid-896x896.py new file mode 100644 index 0000000..33a6ac7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_isaid-896x896.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/isaid.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (896, 896) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=16)) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..1a918b2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_loveda-512x512.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/loveda.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=7)) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..4f37e8a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/pascal_context.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..2c35cb9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/pascal_context_59.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..181c03d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/potsdam.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=6)) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..6303bb6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', '../_base_/datasets/vaihingen.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=6)) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..6ca631c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-160k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb2-160k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..ba7e9c6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb2-40k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..26ab621 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..29cbd10 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..9dd1933 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-20k_voc12aug-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..5f88f53 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-40k_pascal-context-480x480.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..b616fad --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-40k_pascal-context-59-480x480.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..b10b282 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-40k_voc12aug-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..f9f4936 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-80k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_isaid-896x896.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_isaid-896x896.py new file mode 100644 index 0000000..ab2d241 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_isaid-896x896.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-80k_isaid-896x896.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..dd17076 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_loveda-512x512.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-80k_loveda-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..b7b5233 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-80k_pascal-context-480x480.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..ccf1040 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-80k_pascal-context-59-480x480.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..3a5726f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-80k_potsdam-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..720c173 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr18s_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,9 @@ +_base_ = './fcn_hr18_4xb4-80k_vaihingen-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..4aa5d94 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-160k_cityscapes-512x1024.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb2-160k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..7cb7952 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb2-40k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..3e2ce03 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..89b1f04 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..7ca38a9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-20k_voc12aug-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..379be1d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-40k_pascal-context-480x480.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..12730dd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-40k_pascal-context-59-480x480.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..3e1b920 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-40k_voc12aug-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..14fd663 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-80k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_isaid-896x896.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_isaid-896x896.py new file mode 100644 index 0000000..81815ef --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_isaid-896x896.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-80k_isaid-896x896.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..34d23af --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_loveda-512x512.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-80k_loveda-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..4d193d9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-80k_pascal-context-480x480.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..d8b4c4a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-80k_pascal-context-59-480x480.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..58a6500 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-80k_potsdam-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..db91ed8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/fcn_hr48_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,10 @@ +_base_ = './fcn_hr18_4xb4-80k_vaihingen-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=dict( + in_channels=[48, 96, 192, 384], channels=sum([48, 96, 192, 384]))) diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/hrnet/metafile.yaml new file mode 100644 index 0000000..11c3016 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/metafile.yaml @@ -0,0 +1,874 @@ +Models: +- Name: fcn_hr18s_4xb2-40k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.86 + mIoU(ms+flip): 75.91 + Config: configs/hrnet/fcn_hr18s_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_40k_cityscapes/fcn_hr18s_512x1024_40k_cityscapes_20200601_014216-93db27d0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_40k_cityscapes/fcn_hr18s_512x1024_40k_cityscapes_20200601_014216.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb2-40k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.19 + mIoU(ms+flip): 78.92 + Config: configs/hrnet/fcn_hr18_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 2.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_40k_cityscapes/fcn_hr18_512x1024_40k_cityscapes_20200601_014216-f196fb4e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_40k_cityscapes/fcn_hr18_512x1024_40k_cityscapes_20200601_014216.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb2-40k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.48 + mIoU(ms+flip): 79.69 + Config: configs/hrnet/fcn_hr48_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_40k_cityscapes/fcn_hr48_512x1024_40k_cityscapes_20200601_014240-a989b146.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_40k_cityscapes/fcn_hr48_512x1024_40k_cityscapes_20200601_014240.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.31 + mIoU(ms+flip): 77.48 + Config: configs/hrnet/fcn_hr18s_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_80k_cityscapes/fcn_hr18s_512x1024_80k_cityscapes_20200601_202700-1462b75d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_80k_cityscapes/fcn_hr18s_512x1024_80k_cityscapes_20200601_202700.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.65 + mIoU(ms+flip): 80.35 + Config: configs/hrnet/fcn_hr18_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_80k_cityscapes/fcn_hr18_512x1024_80k_cityscapes_20200601_223255-4e7b345e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_80k_cityscapes/fcn_hr18_512x1024_80k_cityscapes_20200601_223255.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.93 + mIoU(ms+flip): 80.72 + Config: configs/hrnet/fcn_hr48_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_80k_cityscapes/fcn_hr48_512x1024_80k_cityscapes_20200601_202606-58ea95d6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_80k_cityscapes/fcn_hr48_512x1024_80k_cityscapes_20200601_202606.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb2-160k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.31 + mIoU(ms+flip): 78.31 + Config: configs/hrnet/fcn_hr18s_4xb2-160k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_160k_cityscapes/fcn_hr18s_512x1024_160k_cityscapes_20200602_190901-4a0797ea.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x1024_160k_cityscapes/fcn_hr18s_512x1024_160k_cityscapes_20200602_190901.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb2-160k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.8 + mIoU(ms+flip): 80.74 + Config: configs/hrnet/fcn_hr18_4xb2-160k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_160k_cityscapes/fcn_hr18_512x1024_160k_cityscapes_20200602_190822-221e4a4f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x1024_160k_cityscapes/fcn_hr18_512x1024_160k_cityscapes_20200602_190822.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb2-160k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.65 + mIoU(ms+flip): 81.92 + Config: configs/hrnet/fcn_hr48_4xb2-160k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_160k_cityscapes/fcn_hr48_512x1024_160k_cityscapes_20200602_190946-59b7973e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x1024_160k_cityscapes/fcn_hr48_512x1024_160k_cityscapes_20200602_190946.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb4-80k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 31.38 + mIoU(ms+flip): 32.45 + Config: configs/hrnet/fcn_hr18s_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 3.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_ade20k/fcn_hr18s_512x512_80k_ade20k_20200614_144345-77fc814a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_ade20k/fcn_hr18s_512x512_80k_ade20k_20200614_144345.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb4-80k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 36.27 + mIoU(ms+flip): 37.28 + Config: configs/hrnet/fcn_hr18_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 4.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_ade20k/fcn_hr18_512x512_80k_ade20k_20210827_114910-6c9382c0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_ade20k/fcn_hr18_512x512_80k_ade20k_20210827_114910.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-80k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.9 + mIoU(ms+flip): 43.27 + Config: configs/hrnet/fcn_hr48_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 8.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_ade20k/fcn_hr48_512x512_80k_ade20k_20200614_193946-7ba5258d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_ade20k/fcn_hr48_512x512_80k_ade20k_20200614_193946.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb4-160k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 33.07 + mIoU(ms+flip): 34.56 + Config: configs/hrnet/fcn_hr18s_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_160k_ade20k/fcn_hr18s_512x512_160k_ade20k_20210829_174739-f1e7c2e7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_160k_ade20k/fcn_hr18s_512x512_160k_ade20k_20210829_174739.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb4-160k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 36.79 + mIoU(ms+flip): 38.58 + Config: configs/hrnet/fcn_hr18_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_160k_ade20k/fcn_hr18_512x512_160k_ade20k_20200614_214426-ca961836.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_160k_ade20k/fcn_hr18_512x512_160k_ade20k_20200614_214426.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-160k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.02 + mIoU(ms+flip): 43.86 + Config: configs/hrnet/fcn_hr48_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_160k_ade20k/fcn_hr48_512x512_160k_ade20k_20200614_214407-a52fc02c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_160k_ade20k/fcn_hr48_512x512_160k_ade20k_20200614_214407.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb4-20k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 65.5 + mIoU(ms+flip): 68.89 + Config: configs/hrnet/fcn_hr18s_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_20k_voc12aug/fcn_hr18s_512x512_20k_voc12aug_20210829_174910-0aceadb4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_20k_voc12aug/fcn_hr18s_512x512_20k_voc12aug_20210829_174910.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb4-20k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 72.3 + mIoU(ms+flip): 74.71 + Config: configs/hrnet/fcn_hr18_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 2.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_20k_voc12aug/fcn_hr18_512x512_20k_voc12aug_20200617_224503-488d45f7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_20k_voc12aug/fcn_hr18_512x512_20k_voc12aug_20200617_224503.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-20k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 75.87 + mIoU(ms+flip): 78.58 + Config: configs/hrnet/fcn_hr48_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_20k_voc12aug/fcn_hr48_512x512_20k_voc12aug_20200617_224419-89de05cd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_20k_voc12aug/fcn_hr48_512x512_20k_voc12aug_20200617_224419.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb4-40k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 66.61 + mIoU(ms+flip): 70.0 + Config: configs/hrnet/fcn_hr18s_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_40k_voc12aug/fcn_hr18s_512x512_40k_voc12aug_20200614_000648-4f8d6e7f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_40k_voc12aug/fcn_hr18s_512x512_40k_voc12aug_20200614_000648.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb4-40k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 72.9 + mIoU(ms+flip): 75.59 + Config: configs/hrnet/fcn_hr18_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_40k_voc12aug/fcn_hr18_512x512_40k_voc12aug_20200613_224401-1b4b76cd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_40k_voc12aug/fcn_hr18_512x512_40k_voc12aug_20200613_224401.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-40k_voc12aug-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.24 + mIoU(ms+flip): 78.49 + Config: configs/hrnet/fcn_hr48_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_40k_voc12aug/fcn_hr48_512x512_40k_voc12aug_20200613_222111-1b0f18bc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_40k_voc12aug/fcn_hr48_512x512_40k_voc12aug_20200613_222111.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-40k_pascal-context-480x480 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 45.14 + mIoU(ms+flip): 47.42 + Config: configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_40k_pascal_context/fcn_hr48_480x480_40k_pascal_context_20200911_164852-667d00b0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_40k_pascal_context/fcn_hr48_480x480_40k_pascal_context-20200911_164852.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-80k_pascal-context-480x480 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 45.84 + mIoU(ms+flip): 47.84 + Config: configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_80k_pascal_context/fcn_hr48_480x480_80k_pascal_context_20200911_155322-847a6711.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_80k_pascal_context/fcn_hr48_480x480_80k_pascal_context-20200911_155322.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-40k_pascal-context-59-480x480 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 50.33 + mIoU(ms+flip): 52.83 + Config: configs/hrnet/fcn_hr48_4xb4-40k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_40k_pascal_context_59/fcn_hr48_480x480_40k_pascal_context_59_20210410_122738-b808b8b2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_40k_pascal_context_59/fcn_hr48_480x480_40k_pascal_context_59-20210410_122738.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-80k_pascal-context-59-480x480 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 51.12 + mIoU(ms+flip): 53.56 + Config: configs/hrnet/fcn_hr48_4xb4-80k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_80k_pascal_context_59/fcn_hr48_480x480_80k_pascal_context_59_20210411_003240-3ae7081e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_480x480_80k_pascal_context_59/fcn_hr48_480x480_80k_pascal_context_59-20210411_003240.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb4-80k_loveda-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 49.28 + mIoU(ms+flip): 49.42 + Config: configs/hrnet/fcn_hr18s_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.59 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_loveda/fcn_hr18s_512x512_80k_loveda_20211210_203228-60a86a7a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_loveda/fcn_hr18s_512x512_80k_loveda_20211210_203228.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb4-80k_loveda-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 50.81 + mIoU(ms+flip): 50.95 + Config: configs/hrnet/fcn_hr18_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 2.76 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_loveda/fcn_hr18_512x512_80k_loveda_20211210_203952-93d9c3b3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_loveda/fcn_hr18_512x512_80k_loveda_20211210_203952.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-80k_loveda-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 51.42 + mIoU(ms+flip): 51.64 + Config: configs/hrnet/fcn_hr48_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_loveda/fcn_hr48_512x512_80k_loveda_20211211_044756-67072f55.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_loveda/fcn_hr48_512x512_80k_loveda_20211211_044756.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb4-80k_potsdam-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 77.64 + mIoU(ms+flip): 78.8 + Config: configs/hrnet/fcn_hr18s_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.58 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_potsdam/fcn_hr18s_512x512_80k_potsdam_20211218_205517-ba32af63.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_512x512_80k_potsdam/fcn_hr18s_512x512_80k_potsdam_20211218_205517.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb4-80k_potsdam-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 78.26 + mIoU(ms+flip): 79.24 + Config: configs/hrnet/fcn_hr18_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 2.76 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_potsdam/fcn_hr18_512x512_80k_potsdam_20211218_205517-5d0387ad.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_512x512_80k_potsdam/fcn_hr18_512x512_80k_potsdam_20211218_205517.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-80k_potsdam-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 78.39 + mIoU(ms+flip): 79.34 + Config: configs/hrnet/fcn_hr48_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_potsdam/fcn_hr48_512x512_80k_potsdam_20211219_020601-97434c78.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_512x512_80k_potsdam/fcn_hr48_512x512_80k_potsdam_20211219_020601.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb4-80k_vaihingen-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 71.81 + mIoU(ms+flip): 73.1 + Config: configs/hrnet/fcn_hr18s_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 1.58 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_4x4_512x512_80k_vaihingen/fcn_hr18s_4x4_512x512_80k_vaihingen_20211231_230909-b23aae02.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_4x4_512x512_80k_vaihingen/fcn_hr18s_4x4_512x512_80k_vaihingen_20211231_230909.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb4-80k_vaihingen-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 72.57 + mIoU(ms+flip): 74.09 + Config: configs/hrnet/fcn_hr18_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 2.76 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_4x4_512x512_80k_vaihingen/fcn_hr18_4x4_512x512_80k_vaihingen_20211231_231216-2ec3ae8a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_4x4_512x512_80k_vaihingen/fcn_hr18_4x4_512x512_80k_vaihingen_20211231_231216.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-80k_vaihingen-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 72.5 + mIoU(ms+flip): 73.52 + Config: configs/hrnet/fcn_hr48_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_4x4_512x512_80k_vaihingen/fcn_hr48_4x4_512x512_80k_vaihingen_20211231_231244-7133cb22.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_4x4_512x512_80k_vaihingen/fcn_hr48_4x4_512x512_80k_vaihingen_20211231_231244.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18s_4xb4-80k_isaid-896x896 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: iSAID + Metrics: + mIoU: 62.3 + mIoU(ms+flip): 62.97 + Config: configs/hrnet/fcn_hr18s_4xb4-80k_isaid-896x896.py + Metadata: + Training Data: iSAID + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 4.95 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_4x4_896x896_80k_isaid/fcn_hr18s_4x4_896x896_80k_isaid_20220118_001603-3cc0769b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18s_4x4_896x896_80k_isaid/fcn_hr18s_4x4_896x896_80k_isaid_20220118_001603.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr18_4xb4-80k_isaid-896x896 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: iSAID + Metrics: + mIoU: 65.06 + mIoU(ms+flip): 65.6 + Config: configs/hrnet/fcn_hr18_4xb4-80k_isaid-896x896.py + Metadata: + Training Data: iSAID + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 8.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_4x4_896x896_80k_isaid/fcn_hr18_4x4_896x896_80k_isaid_20220110_182230-49bf752e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr18_4x4_896x896_80k_isaid/fcn_hr18_4x4_896x896_80k_isaid_20220110_182230.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch +- Name: fcn_hr48_4xb4-80k_isaid-896x896 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: iSAID + Metrics: + mIoU: 67.8 + mIoU(ms+flip): 68.53 + Config: configs/hrnet/fcn_hr48_4xb4-80k_isaid-896x896.py + Metadata: + Training Data: iSAID + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 16.89 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_4x4_896x896_80k_isaid/fcn_hr48_4x4_896x896_80k_isaid_20220114_174643-547fc420.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/hrnet/fcn_hr48_4x4_896x896_80k_isaid/fcn_hr48_4x4_896x896_80k_isaid_20220114_174643.log.json + Paper: + Title: Deep High-Resolution Representation Learning for Human Pose Estimation + URL: https://arxiv.org/abs/1908.07919 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/hrnet.py#L218 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w18-small_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py b/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w18-small_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py new file mode 100644 index 0000000..8e77002 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w18-small_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py @@ -0,0 +1,118 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/msra/hrnetv2_w18_small.pth', + backbone=dict( + extra=dict( + stage1=dict( + num_blocks=(2,), + ), + stage2=dict( + num_blocks=(2, 2), + ), + stage3=dict( + num_modules=3, + num_blocks=(2, 2, 2), + ), + stage4=dict( + num_modules=2, + num_blocks=(2, 2, 2, 2), + ), + ), + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + in_channels=[ + 18, + 36, + 72, + 144, + ], + channels=270, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 1024), + stride=(341, 682), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w18_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py b/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w18_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py new file mode 100644 index 0000000..9406cc3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w18_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py @@ -0,0 +1,122 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/msra/hrnetv2_w18.pth', + backbone=dict( + extra=dict( + stage1=dict( + num_blocks=(4,), + num_channels=64, + ), + stage2=dict( + num_blocks=(4, 4), + num_channels=(18, 36), + ), + stage3=dict( + num_modules=4, + num_blocks=(4, 4, 4), + num_channels=(18, 36, 72), + ), + stage4=dict( + num_modules=3, + num_blocks=(4, 4, 4, 4), + num_channels=(18, 36, 72, 144), + ), + ), + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + in_channels=[ + 18, + 36, + 72, + 144, + ], + channels=270, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 1024), + stride=(341, 682), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w48_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py b/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w48_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py new file mode 100644 index 0000000..0c4cc8c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w48_Pre_g1-40k_check_4000_my_dataset_model-512x1024-testslide.py @@ -0,0 +1,113 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 1024) + +data_preprocessor = dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/msra/hrnetv2_w48.pth', + backbone=dict( + extra=dict( + stage2=dict( + num_channels=(48, 96), + ), + stage3=dict( + num_channels=(48, 96, 192), + ), + stage4=dict( + num_channels=(48, 96, 192, 384), + ), + ), + ), + data_preprocessor=dict( + size=(512, 1024), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + in_channels=[ + 48, + 96, + 192, + 384, + ], + channels=720, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 1024), + stride=(341, 682), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w48_Pre_g1-40k_check_4000_my_dataset_model-769x769-testslide.py b/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w48_Pre_g1-40k_check_4000_my_dataset_model-769x769-testslide.py new file mode 100644 index 0000000..1560b96 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/hrnet/my_hrnet_fcn_w48_Pre_g1-40k_check_4000_my_dataset_model-769x769-testslide.py @@ -0,0 +1,113 @@ +_base_ = [ + '../_base_/models/fcn_hr18.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (769, 769) + +data_preprocessor = dict( + size=(769, 769), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/msra/hrnetv2_w48.pth', + backbone=dict( + extra=dict( + stage2=dict( + num_channels=(48, 96), + ), + stage3=dict( + num_channels=(48, 96, 192), + ), + stage4=dict( + num_channels=(48, 96, 192, 384), + ), + ), + ), + data_preprocessor=dict( + size=(769, 769), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + in_channels=[ + 48, + 96, + 192, + 384, + ], + channels=720, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(769, 769), + stride=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/README.md b/Seg_All_In_One_MMSeg/configs/icnet/README.md new file mode 100644 index 0000000..fa2327f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/README.md @@ -0,0 +1,56 @@ +# ICNet + +> [ICNet for Real-time Semantic Segmentation on High-resolution Images](https://arxiv.org/abs/1704.08545) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +We focus on the challenging task of real-time semantic segmentation in this paper. It finds many practical applications and yet is with fundamental difficulty of reducing a large portion of computation for pixel-wise label inference. We propose an image cascade network (ICNet) that incorporates multi-resolution branches under proper label guidance to address this challenge. We provide in-depth analysis of our framework and introduce the cascade feature fusion unit to quickly achieve high-quality segmentation. Our system yields real-time inference on a single GPU card with decent quality results evaluated on challenging datasets like Cityscapes, CamVid and COCO-Stuff. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ICNet | R-18-D8 | 832x832 | 80000 | 1.70 | 27.12 | V100 | 68.14 | 70.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r18-d8_4xb2-80k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_832x832_80k_cityscapes/icnet_r18-d8_832x832_80k_cityscapes_20210925_225521-2e36638d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_832x832_80k_cityscapes/icnet_r18-d8_832x832_80k_cityscapes_20210925_225521.log.json) | +| ICNet | R-18-D8 | 832x832 | 160000 | - | - | V100 | 71.64 | 74.18 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r18-d8_4xb2-160k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_832x832_160k_cityscapes/icnet_r18-d8_832x832_160k_cityscapes_20210925_230153-2c6eb6e0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_832x832_160k_cityscapes/icnet_r18-d8_832x832_160k_cityscapes_20210925_230153.log.json) | +| ICNet (in1k-pre) | R-18-D8 | 832x832 | 80000 | - | - | V100 | 72.51 | 74.78 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_in1k-pre_832x832_80k_cityscapes/icnet_r18-d8_in1k-pre_832x832_80k_cityscapes_20210925_230354-1cbe3022.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_in1k-pre_832x832_80k_cityscapes/icnet_r18-d8_in1k-pre_832x832_80k_cityscapes_20210925_230354.log.json) | +| ICNet (in1k-pre) | R-18-D8 | 832x832 | 160000 | - | - | V100 | 74.43 | 76.72 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_in1k-pre_832x832_160k_cityscapes/icnet_r18-d8_in1k-pre_832x832_160k_cityscapes_20210926_052702-619c8ae1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_in1k-pre_832x832_160k_cityscapes/icnet_r18-d8_in1k-pre_832x832_160k_cityscapes_20210926_052702.log.json) | +| ICNet | R-50-D8 | 832x832 | 80000 | 2.53 | 20.08 | V100 | 68.91 | 69.72 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r50-d8_4xb2-80k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_832x832_80k_cityscapes/icnet_r50-d8_832x832_80k_cityscapes_20210926_044625-c6407341.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_832x832_80k_cityscapes/icnet_r50-d8_832x832_80k_cityscapes_20210926_044625.log.json) | +| ICNet | R-50-D8 | 832x832 | 160000 | - | - | V100 | 73.82 | 75.67 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r50-d8_4xb2-160k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_832x832_160k_cityscapes/icnet_r50-d8_832x832_160k_cityscapes_20210925_232612-a95f0d4e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_832x832_160k_cityscapes/icnet_r50-d8_832x832_160k_cityscapes_20210925_232612.log.json) | +| ICNet (in1k-pre) | R-50-D8 | 832x832 | 80000 | - | - | V100 | 74.58 | 76.41 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_in1k-pre_832x832_80k_cityscapes/icnet_r50-d8_in1k-pre_832x832_80k_cityscapes_20210926_032943-1743dc7b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_in1k-pre_832x832_80k_cityscapes/icnet_r50-d8_in1k-pre_832x832_80k_cityscapes_20210926_032943.log.json) | +| ICNet (in1k-pre) | R-50-D8 | 832x832 | 160000 | - | - | V100 | 76.29 | 78.09 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_in1k-pre_832x832_160k_cityscapes/icnet_r50-d8_in1k-pre_832x832_160k_cityscapes_20210926_042715-ce310aea.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_in1k-pre_832x832_160k_cityscapes/icnet_r50-d8_in1k-pre_832x832_160k_cityscapes_20210926_042715.log.json) | +| ICNet | R-101-D8 | 832x832 | 80000 | 3.08 | 16.95 | V100 | 70.28 | 71.95 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r101-d8_4xb2-80k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_832x832_80k_cityscapes/icnet_r101-d8_832x832_80k_cityscapes_20210926_072447-b52f936e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_832x832_80k_cityscapes/icnet_r101-d8_832x832_80k_cityscapes_20210926_072447.log.json) | +| ICNet | R-101-D8 | 832x832 | 160000 | - | - | V100 | 73.80 | 76.10 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r101-d8_4xb2-160k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_832x832_160k_cityscapes/icnet_r101-d8_832x832_160k_cityscapes_20210926_092350-3a1ebf1a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_832x832_160k_cityscapes/icnet_r101-d8_832x832_160k_cityscapes_20210926_092350.log.json) | +| ICNet (in1k-pre) | R-101-D8 | 832x832 | 80000 | - | - | V100 | 75.57 | 77.86 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_in1k-pre_832x832_80k_cityscapes/icnet_r101-d8_in1k-pre_832x832_80k_cityscapes_20210926_020414-7ceb12c5.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_in1k-pre_832x832_80k_cityscapes/icnet_r101-d8_in1k-pre_832x832_80k_cityscapes_20210926_020414.log.json) | +| ICNet (in1k-pre) | R-101-D8 | 832x832 | 160000 | - | - | V100 | 76.15 | 77.98 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_in1k-pre_832x832_160k_cityscapes/icnet_r101-d8_in1k-pre_832x832_160k_cityscapes_20210925_232612-9484ae8a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_in1k-pre_832x832_160k_cityscapes/icnet_r101-d8_in1k-pre_832x832_160k_cityscapes_20210925_232612.log.json) | + +Note: `in1k-pre` means pretrained model is used. + +## Citation + +```bibtext +@inproceedings{zhao2018icnet, + title={Icnet for real-time semantic segmentation on high-resolution images}, + author={Zhao, Hengshuang and Qi, Xiaojuan and Shen, Xiaoyong and Shi, Jianping and Jia, Jiaya}, + booktitle={Proceedings of the European conference on computer vision (ECCV)}, + pages={405--420}, + year={2018} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py new file mode 100644 index 0000000..a6840a1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py @@ -0,0 +1,7 @@ +_base_ = './icnet_r50-d8_4xb2-160k_cityscapes-832x832.py' +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=101, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet101_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py new file mode 100644 index 0000000..ca81df8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py @@ -0,0 +1,7 @@ +_base_ = './icnet_r50-d8_4xb2-80k_cityscapes-832x832.py' +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=101, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet101_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8_4xb2-160k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8_4xb2-160k_cityscapes-832x832.py new file mode 100644 index 0000000..ef60446 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8_4xb2-160k_cityscapes-832x832.py @@ -0,0 +1,2 @@ +_base_ = './icnet_r50-d8_4xb2-160k_cityscapes-832x832.py' +model = dict(backbone=dict(backbone_cfg=dict(depth=101))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8_4xb2-80k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8_4xb2-80k_cityscapes-832x832.py new file mode 100644 index 0000000..5173d2d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r101-d8_4xb2-80k_cityscapes-832x832.py @@ -0,0 +1,2 @@ +_base_ = './icnet_r50-d8_4xb2-80k_cityscapes-832x832.py' +model = dict(backbone=dict(backbone_cfg=dict(depth=101))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py new file mode 100644 index 0000000..5f72daa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py @@ -0,0 +1,8 @@ +_base_ = './icnet_r50-d8_4xb2-160k_cityscapes-832x832.py' +model = dict( + backbone=dict( + layer_channels=(128, 512), + backbone_cfg=dict( + depth=18, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet18_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py new file mode 100644 index 0000000..2fc79ab --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py @@ -0,0 +1,8 @@ +_base_ = './icnet_r50-d8_4xb2-80k_cityscapes-832x832.py' +model = dict( + backbone=dict( + layer_channels=(128, 512), + backbone_cfg=dict( + depth=18, + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet18_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8_4xb2-160k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8_4xb2-160k_cityscapes-832x832.py new file mode 100644 index 0000000..2c70e94 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8_4xb2-160k_cityscapes-832x832.py @@ -0,0 +1,3 @@ +_base_ = './icnet_r50-d8_4xb2-160k_cityscapes-832x832.py' +model = dict( + backbone=dict(layer_channels=(128, 512), backbone_cfg=dict(depth=18))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8_4xb2-80k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8_4xb2-80k_cityscapes-832x832.py new file mode 100644 index 0000000..23c7ac2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r18-d8_4xb2-80k_cityscapes-832x832.py @@ -0,0 +1,3 @@ +_base_ = './icnet_r50-d8_4xb2-80k_cityscapes-832x832.py' +model = dict( + backbone=dict(layer_channels=(128, 512), backbone_cfg=dict(depth=18))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py new file mode 100644 index 0000000..f9ab863 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py @@ -0,0 +1,6 @@ +_base_ = './icnet_r50-d8_4xb2-160k_cityscapes-832x832.py' +model = dict( + backbone=dict( + backbone_cfg=dict( + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet50_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py new file mode 100644 index 0000000..9a085d4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py @@ -0,0 +1,6 @@ +_base_ = './icnet_r50-d8_4xb2-80k_cityscapes-832x832.py' +model = dict( + backbone=dict( + backbone_cfg=dict( + init_cfg=dict( + type='Pretrained', checkpoint='open-mmlab://resnet50_v1c')))) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8_4xb2-160k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8_4xb2-160k_cityscapes-832x832.py new file mode 100644 index 0000000..1b7b188 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8_4xb2-160k_cityscapes-832x832.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/cityscapes_832x832.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (832, 832) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8_4xb2-80k_cityscapes-832x832.py b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8_4xb2-80k_cityscapes-832x832.py new file mode 100644 index 0000000..001dbca --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/icnet_r50-d8_4xb2-80k_cityscapes-832x832.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/cityscapes_832x832.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (832, 832) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/icnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/icnet/metafile.yaml new file mode 100644 index 0000000..1d843ee --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/metafile.yaml @@ -0,0 +1,298 @@ +Collections: +- Name: ICNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + README: configs/icnet/README.md + Frameworks: + - PyTorch +Models: +- Name: icnet_r18-d8_4xb2-80k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 68.14 + mIoU(ms+flip): 70.16 + Config: configs/icnet/icnet_r18-d8_4xb2-80k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - ICNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_832x832_80k_cityscapes/icnet_r18-d8_832x832_80k_cityscapes_20210925_225521-2e36638d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_832x832_80k_cityscapes/icnet_r18-d8_832x832_80k_cityscapes_20210925_225521.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r18-d8_4xb2-160k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 71.64 + mIoU(ms+flip): 74.18 + Config: configs/icnet/icnet_r18-d8_4xb2-160k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - ICNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_832x832_160k_cityscapes/icnet_r18-d8_832x832_160k_cityscapes_20210925_230153-2c6eb6e0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_832x832_160k_cityscapes/icnet_r18-d8_832x832_160k_cityscapes_20210925_230153.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r18-d8-in1k-pre_4xb2-80k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 72.51 + mIoU(ms+flip): 74.78 + Config: configs/icnet/icnet_r18-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - ICNet + - (in1k-pre) + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_in1k-pre_832x832_80k_cityscapes/icnet_r18-d8_in1k-pre_832x832_80k_cityscapes_20210925_230354-1cbe3022.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_in1k-pre_832x832_80k_cityscapes/icnet_r18-d8_in1k-pre_832x832_80k_cityscapes_20210925_230354.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r18-d8-in1k-pre_4xb2-160k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.43 + mIoU(ms+flip): 76.72 + Config: configs/icnet/icnet_r18-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - ICNet + - (in1k-pre) + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_in1k-pre_832x832_160k_cityscapes/icnet_r18-d8_in1k-pre_832x832_160k_cityscapes_20210926_052702-619c8ae1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r18-d8_in1k-pre_832x832_160k_cityscapes/icnet_r18-d8_in1k-pre_832x832_160k_cityscapes_20210926_052702.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r50-d8_4xb2-80k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 68.91 + mIoU(ms+flip): 69.72 + Config: configs/icnet/icnet_r50-d8_4xb2-80k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ICNet + Training Resources: 4x V100 GPUS + Memory (GB): 2.53 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_832x832_80k_cityscapes/icnet_r50-d8_832x832_80k_cityscapes_20210926_044625-c6407341.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_832x832_80k_cityscapes/icnet_r50-d8_832x832_80k_cityscapes_20210926_044625.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r50-d8_4xb2-160k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.82 + mIoU(ms+flip): 75.67 + Config: configs/icnet/icnet_r50-d8_4xb2-160k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ICNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_832x832_160k_cityscapes/icnet_r50-d8_832x832_160k_cityscapes_20210925_232612-a95f0d4e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_832x832_160k_cityscapes/icnet_r50-d8_832x832_160k_cityscapes_20210925_232612.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r50-d8-in1k-pre_4xb2-80k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.58 + mIoU(ms+flip): 76.41 + Config: configs/icnet/icnet_r50-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ICNet + - (in1k-pre) + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_in1k-pre_832x832_80k_cityscapes/icnet_r50-d8_in1k-pre_832x832_80k_cityscapes_20210926_032943-1743dc7b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_in1k-pre_832x832_80k_cityscapes/icnet_r50-d8_in1k-pre_832x832_80k_cityscapes_20210926_032943.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r50-d8-in1k-pre_4xb2-160k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.29 + mIoU(ms+flip): 78.09 + Config: configs/icnet/icnet_r50-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ICNet + - (in1k-pre) + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_in1k-pre_832x832_160k_cityscapes/icnet_r50-d8_in1k-pre_832x832_160k_cityscapes_20210926_042715-ce310aea.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r50-d8_in1k-pre_832x832_160k_cityscapes/icnet_r50-d8_in1k-pre_832x832_160k_cityscapes_20210926_042715.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r101-d8_4xb2-80k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 70.28 + mIoU(ms+flip): 71.95 + Config: configs/icnet/icnet_r101-d8_4xb2-80k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ICNet + Training Resources: 4x V100 GPUS + Memory (GB): 3.08 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_832x832_80k_cityscapes/icnet_r101-d8_832x832_80k_cityscapes_20210926_072447-b52f936e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_832x832_80k_cityscapes/icnet_r101-d8_832x832_80k_cityscapes_20210926_072447.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r101-d8_4xb2-160k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.8 + mIoU(ms+flip): 76.1 + Config: configs/icnet/icnet_r101-d8_4xb2-160k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ICNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_832x832_160k_cityscapes/icnet_r101-d8_832x832_160k_cityscapes_20210926_092350-3a1ebf1a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_832x832_160k_cityscapes/icnet_r101-d8_832x832_160k_cityscapes_20210926_092350.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r101-d8-in1k-pre_4xb2-80k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.57 + mIoU(ms+flip): 77.86 + Config: configs/icnet/icnet_r101-d8-in1k-pre_4xb2-80k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ICNet + - (in1k-pre) + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_in1k-pre_832x832_80k_cityscapes/icnet_r101-d8_in1k-pre_832x832_80k_cityscapes_20210926_020414-7ceb12c5.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_in1k-pre_832x832_80k_cityscapes/icnet_r101-d8_in1k-pre_832x832_80k_cityscapes_20210926_020414.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch +- Name: icnet_r101-d8-in1k-pre_4xb2-160k_cityscapes-832x832 + In Collection: ICNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.15 + mIoU(ms+flip): 77.98 + Config: configs/icnet/icnet_r101-d8-in1k-pre_4xb2-160k_cityscapes-832x832.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ICNet + - (in1k-pre) + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_in1k-pre_832x832_160k_cityscapes/icnet_r101-d8_in1k-pre_832x832_160k_cityscapes_20210925_232612-9484ae8a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/icnet/icnet_r101-d8_in1k-pre_832x832_160k_cityscapes/icnet_r101-d8_in1k-pre_832x832_160k_cityscapes_20210925_232612.log.json + Paper: + Title: ICNet for Real-time Semantic Segmentation on High-resolution Images + URL: https://arxiv.org/abs/1704.08545 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/necks/ic_neck.py#L77 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py new file mode 100644 index 0000000..909412b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + layer_channels=(128, 512), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=10, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=10, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py new file mode 100644 index 0000000..f0dbb78 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + layer_channels=(128, 512), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=13, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=13, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py new file mode 100644 index 0000000..3696141 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + layer_channels=(128, 512), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=11, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=11, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py new file mode 100644 index 0000000..e90ce64 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + layer_channels=(128, 512), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=8, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=8, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py new file mode 100644 index 0000000..7d86910 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + layer_channels=(128, 512), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=8, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=8, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-40k_check_4000_my_dataset_model-832x832-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-40k_check_4000_my_dataset_model-832x832-no_testslide.py new file mode 100644 index 0000000..a928842 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-40k_check_4000_my_dataset_model-832x832-no_testslide.py @@ -0,0 +1,138 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (832, 832) + +data_preprocessor = dict( + size=(832, 832), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=(128, 512), + layer_channels=(128, 512), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(832, 832), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=36, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=36, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(832, 832), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-40k_check_4000_my_dataset_model-832x832-testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-40k_check_4000_my_dataset_model-832x832-testslide.py new file mode 100644 index 0000000..bb0c384 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r18_Pre_g1-40k_check_4000_my_dataset_model-832x832-testslide.py @@ -0,0 +1,146 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (832, 832) + +data_preprocessor = dict( + size=(832, 832), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=18, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet18_v1c.pth', + ), + ), + layer_channels=(128, 512), + norm_cfg=dict( + type='BN', + ), + align_corners=True, + ), + data_preprocessor=dict( + size=(832, 832), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=36, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=True, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=36, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=True, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + mode='slide', + crop_size=(832, 832), + stride=(554, 554), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py new file mode 100644 index 0000000..8de9e06 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + layer_channels=(512, 2048), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=10, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=10, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py new file mode 100644 index 0000000..edc88f3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + layer_channels=(512, 2048), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=13, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=13, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py new file mode 100644 index 0000000..ed169cc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + layer_channels=(512, 2048), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=11, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=11, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py new file mode 100644 index 0000000..4335f32 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + layer_channels=(512, 2048), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=8, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=8, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py new file mode 100644 index 0000000..e968aba --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/icnet/my_icnet_r18_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512-no_testslide.py @@ -0,0 +1,144 @@ +_base_ = [ + '../_base_/models/icnet_r50-d8.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + backbone_cfg=dict( + depth=50, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/resnet50_v1c.pth', + ), + ), + layer_channels=(512, 2048), + norm_cfg=dict( + type='BN', + ), + align_corners=False, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=8, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=False, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=8, + in_index=0, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=128, + num_convs=1, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='SyncBN', + requires_grad=True, + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + ], +) + +test_cfg = dict( + crop_size=(512, 512), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/isanet/README.md b/Seg_All_In_One_MMSeg/configs/isanet/README.md new file mode 100644 index 0000000..c11744f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/README.md @@ -0,0 +1,80 @@ +# ISANet + +> [Interlaced Sparse Self-Attention for Semantic Segmentation](https://arxiv.org/abs/1907.12273) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +In this paper, we present a so-called interlaced sparse self-attention approach to improve the efficiency of the \\emph{self-attention} mechanism for semantic segmentation. The main idea is that we factorize the dense affinity matrix as the product of two sparse affinity matrices. There are two successive attention modules each estimating a sparse affinity matrix. The first attention module is used to estimate the affinities within a subset of positions that have long spatial interval distances and the second attention module is used to estimate the affinities within a subset of positions that have short spatial interval distances. These two attention modules are designed so that each position is able to receive the information from all the other positions. In contrast to the original self-attention module, our approach decreases the computation and memory complexity substantially especially when processing high-resolution feature maps. We empirically verify the effectiveness of our approach on six challenging semantic segmentation benchmarks. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------- | -------: | -------------- | ------ | ----- | ------------: | -----------------------------------------------------------------------------------------------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ISANet | R-50-D8 | 512x1024 | 40000 | 5.869 | 2.91 | V100 | 78.49 | 79.44 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x1024_40k_cityscapes/isanet_r50-d8_512x1024_40k_cityscapes_20210901_054739-981bd763.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x1024_40k_cityscapes/isanet_r50-d8_512x1024_40k_cityscapes_20210901_054739.log.json) | +| ISANet | R-50-D8 | 512x1024 | 80000 | 5.869 | 2.91 | V100 | 78.68 | 80.25 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x1024_80k_cityscapes/isanet_r50-d8_512x1024_80k_cityscapes_20210901_074202-89384497.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x1024_80k_cityscapes/isanet_r50-d8_512x1024_80k_cityscapes_20210901_074202.log.json) | +| ISANet | R-50-D8 | 769x769 | 40000 | 6.759 | 1.54 | V100 | 78.70 | 80.28 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_769x769_40k_cityscapes/isanet_r50-d8_769x769_40k_cityscapes_20210903_050200-4ae7e65b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_769x769_40k_cityscapes/isanet_r50-d8_769x769_40k_cityscapes_20210903_050200.log.json) | +| ISANet | R-50-D8 | 769x769 | 80000 | 6.759 | 1.54 | V100 | 79.29 | 80.53 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_769x769_80k_cityscapes/isanet_r50-d8_769x769_80k_cityscapes_20210903_101126-99b54519.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_769x769_80k_cityscapes/isanet_r50-d8_769x769_80k_cityscapes_20210903_101126.log.json) | +| ISANet | R-101-D8 | 512x1024 | 40000 | 9.425 | 2.35 | V100 | 79.58 | 81.05 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x1024_40k_cityscapes/isanet_r101-d8_512x1024_40k_cityscapes_20210901_145553-293e6bd6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x1024_40k_cityscapes/isanet_r101-d8_512x1024_40k_cityscapes_20210901_145553.log.json) | +| ISANet | R-101-D8 | 512x1024 | 80000 | 9.425 | 2.35 | V100 | 80.32 | 81.58 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x1024_80k_cityscapes/isanet_r101-d8_512x1024_80k_cityscapes_20210901_145243-5b99c9b2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x1024_80k_cityscapes/isanet_r101-d8_512x1024_80k_cityscapes_20210901_145243.log.json) | +| ISANet | R-101-D8 | 769x769 | 40000 | 10.815 | 0.92 | V100 | 79.68 | 80.95 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_769x769_40k_cityscapes/isanet_r101-d8_769x769_40k_cityscapes_20210903_111320-509e7224.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_769x769_40k_cityscapes/isanet_r101-d8_769x769_40k_cityscapes_20210903_111320.log.json) | +| ISANet | R-101-D8 | 769x769 | 80000 | 10.815 | 0.92 | V100 | 80.61 | 81.59 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_769x769_80k_cityscapes/isanet_r101-d8_769x769_80k_cityscapes_20210903_111319-24f71dfa.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_769x769_80k_cityscapes/isanet_r101-d8_769x769_80k_cityscapes_20210903_111319.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------- | -------: | -------------- | ------ | ----- | ------------: | -------------------------------------------------------------------------------------------------------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ISANet | R-50-D8 | 512x512 | 80000 | 9.0 | 22.55 | V100 | 41.12 | 42.35 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_80k_ade20k/isanet_r50-d8_512x512_80k_ade20k_20210903_124557-6ed83a0c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_80k_ade20k/isanet_r50-d8_512x512_80k_ade20k_20210903_124557.log.json) | +| ISANet | R-50-D8 | 512x512 | 160000 | 9.0 | 22.55 | V100 | 42.59 | 43.07 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_160k_ade20k/isanet_r50-d8_512x512_160k_ade20k_20210903_104850-f752d0a3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_160k_ade20k/isanet_r50-d8_512x512_160k_ade20k_20210903_104850.log.json) | +| ISANet | R-101-D8 | 512x512 | 80000 | 12.562 | 10.56 | V100 | 43.51 | 44.38 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_80k_ade20k/isanet_r101-d8_512x512_80k_ade20k_20210903_162056-68b235c2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_80k_ade20k/isanet_r101-d8_512x512_80k_ade20k_20210903_162056.log.json) | +| ISANet | R-101-D8 | 512x512 | 160000 | 12.562 | 10.56 | V100 | 43.80 | 45.4 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_160k_ade20k/isanet_r101-d8_512x512_160k_ade20k_20210903_211431-a7879dcd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_160k_ade20k/isanet_r101-d8_512x512_160k_ade20k_20210903_211431.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------- | -------: | -------------- | ------ | ----- | ------------: | --------------------------------------------------------------------------------------------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ISANet | R-50-D8 | 512x512 | 20000 | 5.9 | 23.08 | V100 | 76.78 | 77.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_20k_voc12aug/isanet_r50-d8_512x512_20k_voc12aug_20210901_164838-79d59b80.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_20k_voc12aug/isanet_r50-d8_512x512_20k_voc12aug_20210901_164838.log.json) | +| ISANet | R-50-D8 | 512x512 | 40000 | 5.9 | 23.08 | V100 | 76.20 | 77.22 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_40k_voc12aug/isanet_r50-d8_512x512_40k_voc12aug_20210901_151349-7d08a54e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_40k_voc12aug/isanet_r50-d8_512x512_40k_voc12aug_20210901_151349.log.json) | +| ISANet | R-101-D8 | 512x512 | 20000 | 9.465 | 7.42 | V100 | 78.46 | 79.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_20k_voc12aug/isanet_r101-d8_512x512_20k_voc12aug_20210901_115805-3ccbf355.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_20k_voc12aug/isanet_r101-d8_512x512_20k_voc12aug_20210901_115805.log.json) | +| ISANet | R-101-D8 | 512x512 | 40000 | 9.465 | 7.42 | V100 | 78.12 | 79.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet/isanet_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_40k_voc12aug/isanet_r101-d8_512x512_40k_voc12aug_20210901_145814-bc71233b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_40k_voc12aug/isanet_r101-d8_512x512_40k_voc12aug_20210901_145814.log.json) | + +## Citation + +```bibetex +@article{huang2019isa, + title={Interlaced Sparse Self-Attention for Semantic Segmentation}, + author={Huang, Lang and Yuan, Yuhui and Guo, Jianyuan and Zhang, Chao and Chen, Xilin and Wang, Jingdong}, + journal={arXiv preprint arXiv:1907.12273}, + year={2019} +} +``` + +The technical report above is also presented at: + +```bibetex +@article{yuan2021ocnet, + title={OCNet: Object Context for Semantic Segmentation}, + author={Yuan, Yuhui and Huang, Lang and Guo, Jianyuan and Zhang, Chao and Chen, Xilin and Wang, Jingdong}, + journal={International Journal of Computer Vision}, + pages={1--24}, + year={2021}, + publisher={Springer} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..6093aeb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './isanet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..dc14c76 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './isanet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..1735f89 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './isanet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..b1a6371 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './isanet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..c2fb09e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './isanet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..7c225cf --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './isanet_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..5e86ee5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './isanet_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..090e86f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './isanet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..f03365e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..f073a7b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..4be445d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..0278ad8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..1f4af8d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..591df42 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..a59879b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..7df05c3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/isanet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/isanet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/isanet/metafile.yaml new file mode 100644 index 0000000..ad394ea --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/metafile.yaml @@ -0,0 +1,399 @@ +Collections: +- Name: ISANet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + README: configs/isanet/README.md + Frameworks: + - PyTorch +Models: +- Name: isanet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.49 + mIoU(ms+flip): 79.44 + Config: configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 5.869 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x1024_40k_cityscapes/isanet_r50-d8_512x1024_40k_cityscapes_20210901_054739-981bd763.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x1024_40k_cityscapes/isanet_r50-d8_512x1024_40k_cityscapes_20210901_054739.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.68 + mIoU(ms+flip): 80.25 + Config: configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 5.869 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x1024_80k_cityscapes/isanet_r50-d8_512x1024_80k_cityscapes_20210901_074202-89384497.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x1024_80k_cityscapes/isanet_r50-d8_512x1024_80k_cityscapes_20210901_074202.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.7 + mIoU(ms+flip): 80.28 + Config: configs/isanet/isanet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 6.759 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_769x769_40k_cityscapes/isanet_r50-d8_769x769_40k_cityscapes_20210903_050200-4ae7e65b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_769x769_40k_cityscapes/isanet_r50-d8_769x769_40k_cityscapes_20210903_050200.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.29 + mIoU(ms+flip): 80.53 + Config: configs/isanet/isanet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 6.759 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_769x769_80k_cityscapes/isanet_r50-d8_769x769_80k_cityscapes_20210903_101126-99b54519.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_769x769_80k_cityscapes/isanet_r50-d8_769x769_80k_cityscapes_20210903_101126.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.58 + mIoU(ms+flip): 81.05 + Config: configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 9.425 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x1024_40k_cityscapes/isanet_r101-d8_512x1024_40k_cityscapes_20210901_145553-293e6bd6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x1024_40k_cityscapes/isanet_r101-d8_512x1024_40k_cityscapes_20210901_145553.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.32 + mIoU(ms+flip): 81.58 + Config: configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 9.425 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x1024_80k_cityscapes/isanet_r101-d8_512x1024_80k_cityscapes_20210901_145243-5b99c9b2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x1024_80k_cityscapes/isanet_r101-d8_512x1024_80k_cityscapes_20210901_145243.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.68 + mIoU(ms+flip): 80.95 + Config: configs/isanet/isanet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 10.815 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_769x769_40k_cityscapes/isanet_r101-d8_769x769_40k_cityscapes_20210903_111320-509e7224.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_769x769_40k_cityscapes/isanet_r101-d8_769x769_40k_cityscapes_20210903_111320.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.61 + mIoU(ms+flip): 81.59 + Config: configs/isanet/isanet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 10.815 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_769x769_80k_cityscapes/isanet_r101-d8_769x769_80k_cityscapes_20210903_111319-24f71dfa.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_769x769_80k_cityscapes/isanet_r101-d8_769x769_80k_cityscapes_20210903_111319.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.12 + mIoU(ms+flip): 42.35 + Config: configs/isanet/isanet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 9.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_80k_ade20k/isanet_r50-d8_512x512_80k_ade20k_20210903_124557-6ed83a0c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_80k_ade20k/isanet_r50-d8_512x512_80k_ade20k_20210903_124557.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.59 + mIoU(ms+flip): 43.07 + Config: configs/isanet/isanet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 9.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_160k_ade20k/isanet_r50-d8_512x512_160k_ade20k_20210903_104850-f752d0a3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_160k_ade20k/isanet_r50-d8_512x512_160k_ade20k_20210903_104850.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.51 + mIoU(ms+flip): 44.38 + Config: configs/isanet/isanet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 12.562 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_80k_ade20k/isanet_r101-d8_512x512_80k_ade20k_20210903_162056-68b235c2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_80k_ade20k/isanet_r101-d8_512x512_80k_ade20k_20210903_162056.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.8 + mIoU(ms+flip): 45.4 + Config: configs/isanet/isanet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 12.562 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_160k_ade20k/isanet_r101-d8_512x512_160k_ade20k_20210903_211431-a7879dcd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_160k_ade20k/isanet_r101-d8_512x512_160k_ade20k_20210903_211431.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.78 + mIoU(ms+flip): 77.79 + Config: configs/isanet/isanet_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 5.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_20k_voc12aug/isanet_r50-d8_512x512_20k_voc12aug_20210901_164838-79d59b80.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_20k_voc12aug/isanet_r50-d8_512x512_20k_voc12aug_20210901_164838.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.2 + mIoU(ms+flip): 77.22 + Config: configs/isanet/isanet_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 5.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_40k_voc12aug/isanet_r50-d8_512x512_40k_voc12aug_20210901_151349-7d08a54e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r50-d8_512x512_40k_voc12aug/isanet_r50-d8_512x512_40k_voc12aug_20210901_151349.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 78.46 + mIoU(ms+flip): 79.16 + Config: configs/isanet/isanet_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 9.465 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_20k_voc12aug/isanet_r101-d8_512x512_20k_voc12aug_20210901_115805-3ccbf355.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_20k_voc12aug/isanet_r101-d8_512x512_20k_voc12aug_20210901_115805.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch +- Name: isanet_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: ISANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 78.12 + mIoU(ms+flip): 79.04 + Config: configs/isanet/isanet_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - ISANet + Training Resources: 4x V100 GPUS + Memory (GB): 9.465 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_40k_voc12aug/isanet_r101-d8_512x512_40k_voc12aug_20210901_145814-bc71233b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/isanet/isanet_r101-d8_512x512_40k_voc12aug/isanet_r101-d8_512x512_40k_voc12aug_20210901_145814.log.json + Paper: + Title: Interlaced Sparse Self-Attention for Semantic Segmentation + URL: https://arxiv.org/abs/1907.12273 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.18.0/mmseg/models/decode_heads/isa_head.py#L58 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/isanet/my_isanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py b/Seg_All_In_One_MMSeg/configs/isanet/my_isanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py new file mode 100644 index 0000000..f3f3d5c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/isanet/my_isanet_r50_r101_Pre_g1-40k_check_4000_my_dataset_model-512x512-testslide.py @@ -0,0 +1,105 @@ +_base_ = [ + '../_base_/models/isanet_r50-d8.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + pretrained='./My_Local_Model/open_mmlab/resnet101_v1c.pth', + backbone=dict( + depth=101, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + align_corners=True, + ), + auxiliary_head=dict( + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + align_corners=True, + ), +) + +test_cfg = dict( + mode='slide', + crop_size=(512, 512), + stride=(341, 341), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/knet/README.md b/Seg_All_In_One_MMSeg/configs/knet/README.md new file mode 100644 index 0000000..1f3f2ae --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/README.md @@ -0,0 +1,52 @@ +# K-Net + +> [K-Net: Towards Unified Image Segmentation](https://arxiv.org/abs/2106.14855) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Semantic, instance, and panoptic segmentations have been addressed using different and specialized frameworks despite their underlying connections. This paper presents a unified, simple, and effective framework for these essentially similar tasks. The framework, named K-Net, segments both instances and semantic categories consistently by a group of learnable kernels, where each kernel is responsible for generating a mask for either a potential instance or a stuff class. To remedy the difficulties of distinguishing various instances, we propose a kernel update strategy that enables each kernel dynamic and conditional on its meaningful group in the input image. K-Net can be trained in an end-to-end manner with bipartite matching, and its training and inference are naturally NMS-free and box-free. Without bells and whistles, K-Net surpasses all previous published state-of-the-art single-model results of panoptic segmentation on MS COCO test-dev split and semantic segmentation on ADE20K val split with 55.2% PQ and 54.3% mIoU, respectively. Its instance segmentation performance is also on par with Cascade Mask R-CNN on MS COCO with 60%-90% faster inference speeds. Code and models will be released at [this https URL](https://github.com/ZwwWayne/K-Net/). + + + +
+ +
+ +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------------- | -------- | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| KNet + FCN | R-50-D8 | 512x512 | 80000 | 7.01 | 19.24 | V100 | 43.60 | 45.12 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/knet/knet-s3_r50-d8_fcn_8xb2-adamw-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_fcn_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_fcn_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_043751-abcab920.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_fcn_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_fcn_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_043751.log.json) | +| KNet + PSPNet | R-50-D8 | 512x512 | 80000 | 6.98 | 20.04 | V100 | 44.18 | 45.58 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/knet/knet-s3_r50-d8_pspnet_8xb2-adamw-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_pspnet_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_pspnet_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_054634-d2c72240.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_pspnet_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_pspnet_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_054634.log.json) | +| KNet + DeepLabV3 | R-50-D8 | 512x512 | 80000 | 7.42 | 12.10 | V100 | 45.06 | 46.11 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/knet/knet-s3_r50-d8_deeplabv3_8xb2-adamw-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_deeplabv3_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_deeplabv3_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_041642-00c8fbeb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_deeplabv3_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_deeplabv3_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_041642.log.json) | +| KNet + UperNet | R-50-D8 | 512x512 | 80000 | 7.34 | 17.11 | V100 | 43.45 | 44.07 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/knet/knet-s3_r50-d8_upernet_8xb2-adamw-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_r50-d8_8x2_512x512_adamw_80k_ade20k_20220304_125657-215753b0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_r50-d8_8x2_512x512_adamw_80k_ade20k_20220304_125657.log.json) | +| KNet + UperNet | Swin-T | 512x512 | 80000 | 7.57 | 15.56 | V100 | 45.84 | 46.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/knet/knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-t_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_swin-t_8x2_512x512_adamw_80k_ade20k_20220303_133059-7545e1dc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-t_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_swin-t_8x2_512x512_adamw_80k_ade20k_20220303_133059.log.json) | +| KNet + UperNet | Swin-L | 512x512 | 80000 | 13.5 | 8.29 | V100 | 52.05 | 53.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-l_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_swin-l_8x2_512x512_adamw_80k_ade20k_20220303_154559-d8da9a90.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-l_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_swin-l_8x2_512x512_adamw_80k_ade20k_20220303_154559.log.json) | +| KNet + UperNet | Swin-L | 640x640 | 80000 | 13.54 | 8.29 | V100 | 52.21 | 53.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-l_8x2_640x640_adamw_80k_ade20k/knet_s3_upernet_swin-l_8x2_640x640_adamw_80k_ade20k_20220301_220747-8787fc71.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-l_8x2_640x640_adamw_80k_ade20k/knet_s3_upernet_swin-l_8x2_640x640_adamw_80k_ade20k_20220301_220747.log.json) | + +Note: + +- All experiments of K-Net are implemented with 8 V100 (32G) GPUs with 2 samplers per GPU. + +# Citation + +```bibtex +@inproceedings{zhang2021knet, + title={{K-Net: Towards} Unified Image Segmentation}, + author={Wenwei Zhang and Jiangmiao Pang and Kai Chen and Chen Change Loy}, + year={2021}, + booktitle={NeurIPS}, +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_deeplabv3_8xb2-adamw-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_deeplabv3_8xb2-adamw-80k_ade20k-512x512.py new file mode 100644 index 0000000..7946cca --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_deeplabv3_8xb2-adamw-80k_ade20k-512x512.py @@ -0,0 +1,111 @@ +_base_ = [ + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + size=crop_size, + seg_pad_val=255) +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +num_stages = 3 +conv_kernel_size = 1 +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='IterativeDecodeHead', + num_stages=num_stages, + kernel_update_head=[ + dict( + type='KernelUpdateHead', + num_classes=150, + num_ffn_fcs=2, + num_heads=8, + num_mask_fcs=1, + feedforward_channels=2048, + in_channels=512, + out_channels=512, + dropout=0.0, + conv_kernel_size=conv_kernel_size, + ffn_act_cfg=dict(type='ReLU', inplace=True), + with_ffn=True, + feat_transform_cfg=dict( + conv_cfg=dict(type='Conv2d'), act_cfg=None), + kernel_updator_cfg=dict( + type='KernelUpdator', + in_channels=256, + feat_channels=256, + out_channels=256, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN'))) for _ in range(num_stages) + ], + kernel_generate_head=dict( + type='ASPPHead', + in_channels=2048, + in_index=3, + channels=512, + dilations=(1, 12, 24, 36), + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0005), + clip_grad=dict(max_norm=1, norm_type=2)) +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=1000, + end=80000, + milestones=[60000, 72000], + by_epoch=False, + ) +] +# In K-Net implementation we use batch size 2 per GPU as default +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_fcn_8xb2-adamw-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_fcn_8xb2-adamw-80k_ade20k-512x512.py new file mode 100644 index 0000000..497cd04 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_fcn_8xb2-adamw-80k_ade20k-512x512.py @@ -0,0 +1,112 @@ +_base_ = [ + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + size=crop_size, + seg_pad_val=255) +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +num_stages = 3 +conv_kernel_size = 1 +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='IterativeDecodeHead', + num_stages=num_stages, + kernel_update_head=[ + dict( + type='KernelUpdateHead', + num_classes=150, + num_ffn_fcs=2, + num_heads=8, + num_mask_fcs=1, + feedforward_channels=2048, + in_channels=512, + out_channels=512, + dropout=0.0, + conv_kernel_size=conv_kernel_size, + ffn_act_cfg=dict(type='ReLU', inplace=True), + with_ffn=True, + feat_transform_cfg=dict( + conv_cfg=dict(type='Conv2d'), act_cfg=None), + kernel_updator_cfg=dict( + type='KernelUpdator', + in_channels=256, + feat_channels=256, + out_channels=256, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN'))) for _ in range(num_stages) + ], + kernel_generate_head=dict( + type='FCNHead', + in_channels=2048, + in_index=3, + channels=512, + num_convs=2, + concat_input=True, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0005), + clip_grad=dict(max_norm=1, norm_type=2)) + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=1000, + end=80000, + milestones=[60000, 72000], + by_epoch=False, + ) +] +# In K-Net implementation we use batch size 2 per GPU as default +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_pspnet_8xb2-adamw-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_pspnet_8xb2-adamw-80k_ade20k-512x512.py new file mode 100644 index 0000000..b918671 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_pspnet_8xb2-adamw-80k_ade20k-512x512.py @@ -0,0 +1,110 @@ +_base_ = [ + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + size=crop_size, + seg_pad_val=255) +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +num_stages = 3 +conv_kernel_size = 1 +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='IterativeDecodeHead', + num_stages=num_stages, + kernel_update_head=[ + dict( + type='KernelUpdateHead', + num_classes=150, + num_ffn_fcs=2, + num_heads=8, + num_mask_fcs=1, + feedforward_channels=2048, + in_channels=512, + out_channels=512, + dropout=0.0, + conv_kernel_size=conv_kernel_size, + ffn_act_cfg=dict(type='ReLU', inplace=True), + with_ffn=True, + feat_transform_cfg=dict( + conv_cfg=dict(type='Conv2d'), act_cfg=None), + kernel_updator_cfg=dict( + type='KernelUpdator', + in_channels=256, + feat_channels=256, + out_channels=256, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN'))) for _ in range(num_stages) + ], + kernel_generate_head=dict( + type='PSPHead', + in_channels=2048, + in_index=3, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0005), + clip_grad=dict(max_norm=1, norm_type=2)) +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=1000, + end=80000, + milestones=[60000, 72000], + by_epoch=False, + ) +] +# In K-Net implementation we use batch size 2 per GPU as default +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_upernet_8xb2-adamw-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_upernet_8xb2-adamw-80k_ade20k-512x512.py new file mode 100644 index 0000000..a0a66c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_r50-d8_upernet_8xb2-adamw-80k_ade20k-512x512.py @@ -0,0 +1,111 @@ +_base_ = [ + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + size=crop_size, + seg_pad_val=255) +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +num_stages = 3 +conv_kernel_size = 1 + +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 1, 1), + strides=(1, 2, 2, 2), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='IterativeDecodeHead', + num_stages=num_stages, + kernel_update_head=[ + dict( + type='KernelUpdateHead', + num_classes=150, + num_ffn_fcs=2, + num_heads=8, + num_mask_fcs=1, + feedforward_channels=2048, + in_channels=512, + out_channels=512, + dropout=0.0, + conv_kernel_size=conv_kernel_size, + ffn_act_cfg=dict(type='ReLU', inplace=True), + with_ffn=True, + feat_transform_cfg=dict( + conv_cfg=dict(type='Conv2d'), act_cfg=None), + kernel_updator_cfg=dict( + type='KernelUpdator', + in_channels=256, + feat_channels=256, + out_channels=256, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN'))) for _ in range(num_stages) + ], + kernel_generate_head=dict( + type='UPerHead', + in_channels=[256, 512, 1024, 2048], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0005), + clip_grad=dict(max_norm=1, norm_type=2)) +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=1000, + end=80000, + milestones=[60000, 72000], + by_epoch=False, + ) +] +# In K-Net implementation we use batch size 2 per GPU as default +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-512x512.py new file mode 100644 index 0000000..c6f4eb6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-512x512.py @@ -0,0 +1,21 @@ +_base_ = 'knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512.py' + +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_large_patch4_window7_224_22k_20220308-d5bdebaf.pth' # noqa +# model settings +model = dict( + pretrained=checkpoint_file, + backbone=dict( + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=7, + use_abs_pos_embed=False, + drop_path_rate=0.3, + patch_norm=True), + decode_head=dict( + kernel_generate_head=dict(in_channels=[192, 384, 768, 1536])), + auxiliary_head=dict(in_channels=768)) +# In K-Net implementation we use batch size 2 per GPU as default +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-640x640.py new file mode 100644 index 0000000..84c3d8c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-640x640.py @@ -0,0 +1,57 @@ +_base_ = 'knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512.py' + +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_large_patch4_window7_224_22k_20220308-d5bdebaf.pth' # noqa +# model settings +crop_size = (640, 640) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + size=crop_size, + seg_pad_val=255) +model = dict( + data_preprocessor=data_preprocessor, + pretrained=checkpoint_file, + backbone=dict( + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=7, + use_abs_pos_embed=False, + drop_path_rate=0.4, + patch_norm=True), + decode_head=dict( + kernel_generate_head=dict(in_channels=[192, 384, 768, 1536])), + auxiliary_head=dict(in_channels=768)) + +crop_size = (640, 640) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2048, 640), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 640), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader +# In K-Net implementation we use batch size 2 per GPU as default +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512.py new file mode 100644 index 0000000..a7acec4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512.py @@ -0,0 +1,63 @@ +_base_ = 'knet-s3_r50-d8_upernet_8xb2-adamw-80k_ade20k-512x512.py' + +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_tiny_patch4_window7_224_20220308-f41b89d3.pth' # noqa + +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +num_stages = 3 +conv_kernel_size = 1 + +model = dict( + type='EncoderDecoder', + pretrained=checkpoint_file, + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + use_abs_pos_embed=False, + patch_norm=True, + out_indices=(0, 1, 2, 3)), + decode_head=dict( + kernel_generate_head=dict(in_channels=[96, 192, 384, 768])), + auxiliary_head=dict(in_channels=384)) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + # modify learning rate following the official implementation of Swin Transformer # noqa + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.0005), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + }), + clip_grad=dict(max_norm=1, norm_type=2)) + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=1000, + end=80000, + milestones=[60000, 72000], + by_epoch=False, + ) +] +# In K-Net implementation we use batch size 2 per GPU as default +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/knet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/knet/metafile.yaml new file mode 100644 index 0000000..0f4ab79 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/knet/metafile.yaml @@ -0,0 +1,188 @@ +Collections: +- Name: KNet + License: Apache License 2.0 + Metadata: + Training Data: + - ADE20K + Paper: + Title: 'K-Net: Towards Unified Image Segmentation' + URL: https://arxiv.org/abs/2106.14855 + README: configs/knet/README.md + Frameworks: + - PyTorch +Models: +- Name: knet-s3_r50-d8_fcn_8xb2-adamw-80k_ade20k-512x512 + In Collection: KNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.6 + mIoU(ms+flip): 45.12 + Config: configs/knet/knet-s3_r50-d8_fcn_8xb2-adamw-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - KNet + - FCN + Training Resources: 8x V100 GPUS + Memory (GB): 7.01 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_fcn_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_fcn_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_043751-abcab920.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_fcn_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_fcn_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_043751.log.json + Paper: + Title: 'K-Net: Towards Unified Image Segmentation' + URL: https://arxiv.org/abs/2106.14855 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/decode_heads/knet_head.py#L392 + Framework: PyTorch +- Name: knet-s3_r50-d8_pspnet_8xb2-adamw-80k_ade20k-512x512 + In Collection: KNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.18 + mIoU(ms+flip): 45.58 + Config: configs/knet/knet-s3_r50-d8_pspnet_8xb2-adamw-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - KNet + - PSPNet + Training Resources: 8x V100 GPUS + Memory (GB): 6.98 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_pspnet_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_pspnet_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_054634-d2c72240.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_pspnet_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_pspnet_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_054634.log.json + Paper: + Title: 'K-Net: Towards Unified Image Segmentation' + URL: https://arxiv.org/abs/2106.14855 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/decode_heads/knet_head.py#L392 + Framework: PyTorch +- Name: knet-s3_r50-d8_deeplabv3_8xb2-adamw-80k_ade20k-512x512 + In Collection: KNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.06 + mIoU(ms+flip): 46.11 + Config: configs/knet/knet-s3_r50-d8_deeplabv3_8xb2-adamw-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - KNet + - DeepLabV3 + Training Resources: 8x V100 GPUS + Memory (GB): 7.42 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_deeplabv3_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_deeplabv3_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_041642-00c8fbeb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_deeplabv3_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_deeplabv3_r50-d8_8x2_512x512_adamw_80k_ade20k_20220228_041642.log.json + Paper: + Title: 'K-Net: Towards Unified Image Segmentation' + URL: https://arxiv.org/abs/2106.14855 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/decode_heads/knet_head.py#L392 + Framework: PyTorch +- Name: knet-s3_r50-d8_upernet_8xb2-adamw-80k_ade20k-512x512 + In Collection: KNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.45 + mIoU(ms+flip): 44.07 + Config: configs/knet/knet-s3_r50-d8_upernet_8xb2-adamw-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - KNet + - UperNet + Training Resources: 8x V100 GPUS + Memory (GB): 7.34 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_r50-d8_8x2_512x512_adamw_80k_ade20k_20220304_125657-215753b0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_r50-d8_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_r50-d8_8x2_512x512_adamw_80k_ade20k_20220304_125657.log.json + Paper: + Title: 'K-Net: Towards Unified Image Segmentation' + URL: https://arxiv.org/abs/2106.14855 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/decode_heads/knet_head.py#L392 + Framework: PyTorch +- Name: knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512 + In Collection: KNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.84 + mIoU(ms+flip): 46.27 + Config: configs/knet/knet-s3_swin-t_upernet_8xb2-adamw-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-T + - KNet + - UperNet + Training Resources: 8x V100 GPUS + Memory (GB): 7.57 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-t_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_swin-t_8x2_512x512_adamw_80k_ade20k_20220303_133059-7545e1dc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-t_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_swin-t_8x2_512x512_adamw_80k_ade20k_20220303_133059.log.json + Paper: + Title: 'K-Net: Towards Unified Image Segmentation' + URL: https://arxiv.org/abs/2106.14855 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/decode_heads/knet_head.py#L392 + Framework: PyTorch +- Name: knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-512x512 + In Collection: KNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 52.05 + mIoU(ms+flip): 53.24 + Config: configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-L + - KNet + - UperNet + Training Resources: 8x V100 GPUS + Memory (GB): 13.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-l_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_swin-l_8x2_512x512_adamw_80k_ade20k_20220303_154559-d8da9a90.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-l_8x2_512x512_adamw_80k_ade20k/knet_s3_upernet_swin-l_8x2_512x512_adamw_80k_ade20k_20220303_154559.log.json + Paper: + Title: 'K-Net: Towards Unified Image Segmentation' + URL: https://arxiv.org/abs/2106.14855 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/decode_heads/knet_head.py#L392 + Framework: PyTorch +- Name: knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-640x640 + In Collection: KNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 52.21 + mIoU(ms+flip): 53.34 + Config: configs/knet/knet-s3_swin-l_upernet_8xb2-adamw-80k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-L + - KNet + - UperNet + Training Resources: 8x V100 GPUS + Memory (GB): 13.54 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-l_8x2_640x640_adamw_80k_ade20k/knet_s3_upernet_swin-l_8x2_640x640_adamw_80k_ade20k_20220301_220747-8787fc71.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/knet/knet_s3_upernet_swin-l_8x2_640x640_adamw_80k_ade20k/knet_s3_upernet_swin-l_8x2_640x640_adamw_80k_ade20k_20220301_220747.log.json + Paper: + Title: 'K-Net: Towards Unified Image Segmentation' + URL: https://arxiv.org/abs/2106.14855 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.23.0/mmseg/models/decode_heads/knet_head.py#L392 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/mae/README.md b/Seg_All_In_One_MMSeg/configs/mae/README.md new file mode 100644 index 0000000..d14e383 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mae/README.md @@ -0,0 +1,82 @@ +# MAE + +> [Masked Autoencoders Are Scalable Vision Learners](https://arxiv.org/abs/2111.06377) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +This paper shows that masked autoencoders (MAE) are scalable self-supervised learners for computer vision. Our MAE approach is simple: we mask random patches of the input image and reconstruct the missing pixels. It is based on two core designs. First, we develop an asymmetric encoder-decoder architecture, with an encoder that operates only on the visible subset of patches (without mask tokens), along with a lightweight decoder that reconstructs the original image from the latent representation and mask tokens. Second, we find that masking a high proportion of the input image, e.g., 75%, yields a nontrivial and meaningful self-supervisory task. Coupling these two designs enables us to train large models efficiently and effectively: we accelerate training (by 3x or more) and improve accuracy. Our scalable approach allows for learning high-capacity models that generalize well: e.g., a vanilla ViT-Huge model achieves the best accuracy (87.8%) among methods that use only ImageNet-1K data. Transfer performance in downstream tasks outperforms supervised pre-training and shows promising scaling behavior. + + + +
+ +
+ +## Usage + +To use other repositories' pre-trained models, it is necessary to convert keys. + +We provide a script [`beit2mmseg.py`](../../tools/model_converters/beit2mmseg.py) in the tools directory to convert the key of MAE model from [the official repo](https://github.com/facebookresearch/mae) to MMSegmentation style. + +```shell +python tools/model_converters/beit2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +E.g. + +```shell +python tools/model_converters/beit2mmseg.py https://dl.fbaipublicfiles.com/mae/pretrain/mae_pretrain_vit_base.pth pretrain/mae_pretrain_vit_base_mmcls.pth +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +In our default setting, pretrained models could be defined below: + +| pretrained models | original models | +| ------------------------------- | ------------------------------------------------------------------------------------------------ | +| mae_pretrain_vit_base_mmcls.pth | ['mae_pretrain_vit_base'](https://dl.fbaipublicfiles.com/mae/pretrain/mae_pretrain_vit_base.pth) | + +Verify the single-scale results of the model: + +```shell +sh tools/dist_test.sh \ +configs/mae/upernet_mae-base_fp16_8x2_512x512_160k_ade20k.py \ +upernet_mae-base_fp16_8x2_512x512_160k_ade20k_20220426_174752-f92a2975.pth $GPUS --eval mIoU +``` + +Since relative position embedding requires the input length and width to be equal, the sliding window is adopted for multi-scale inference. So we set min_size=512, that is, the shortest edge is 512. So the multi-scale inference of config is performed separately, instead of '--aug-test'. For multi-scale inference: + +```shell +sh tools/dist_test.sh \ +configs/mae/upernet_mae-base_fp16_512x512_160k_ade20k_ms.py \ +upernet_mae-base_fp16_8x2_512x512_160k_ade20k_20220426_174752-f92a2975.pth $GPUS --eval mIoU +``` + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | pretrain | pretrain img size | Batch Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | -------- | --------- | ----------- | ----------------- | ---------- | ------- | -------- | -------------- | ------ | ----- | ------------: | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| UPerNet | ViT-B | 512x512 | ImageNet-1K | 224x224 | 16 | 160000 | 9.96 | 7.14 | V100 | 48.13 | 48.70 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mae/upernet_mae-base_fp16_8x2_512x512_160k_ade20k/upernet_mae-base_fp16_8x2_512x512_160k_ade20k_20220426_174752-f92a2975.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mae/upernet_mae-base_fp16_8x2_512x512_160k_ade20k/upernet_mae-base_fp16_8x2_512x512_160k_ade20k_20220426_174752.log.json) | + +## Citation + +```bibtex +@article{he2021masked, + title={Masked autoencoders are scalable vision learners}, + author={He, Kaiming and Chen, Xinlei and Xie, Saining and Li, Yanghao and Doll{\'a}r, Piotr and Girshick, Ross}, + journal={arXiv preprint arXiv:2111.06377}, + year={2021} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512-ms.py b/Seg_All_In_One_MMSeg/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512-ms.py new file mode 100644 index 0000000..ec32fea --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512-ms.py @@ -0,0 +1,16 @@ +_base_ = './mae-base_upernet_8xb2-amp-160k_ade20k-512x512.py' + +test_pipeline = [ + dict(type='LoadImageFromFile'), + # TODO: Refactor 'MultiScaleFlipAug' which supports + # `min_size` feature in `Resize` class + # img_ratios is [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] + # original image scale is (2048, 512) + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +val_dataloader = dict(batch_size=1, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512.py new file mode 100644 index 0000000..6934468 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512.py @@ -0,0 +1,53 @@ +_base_ = [ + '../_base_/models/upernet_mae.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='./pretrain/mae_pretrain_vit_base_mmcls.pth', + backbone=dict( + type='MAE', + img_size=(512, 512), + patch_size=16, # + embed_dims=768, # + num_layers=12, # + num_heads=12, # + mlp_ratio=4, # + init_values=1.0, + drop_path_rate=0.1, # + out_indices=[3, 5, 7, 11]), + neck=dict(embed_dim=768, rescales=[4, 2, 1, 0.5]), + decode_head=dict(in_channels=[768, 768, 768, 768], num_classes=150, channels=768), + auxiliary_head=dict(in_channels=768, num_classes=150), + test_cfg=dict(mode='slide', crop_size=(512, 512), stride=(341, 341))) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=1e-4, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg=dict(num_layers=12, layer_decay_rate=0.65), + constructor='LayerDecayOptimizerConstructor') + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] + +# mixed precision +fp16 = dict(loss_scale='dynamic') + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=4) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/mae/metafile.yaml b/Seg_All_In_One_MMSeg/configs/mae/metafile.yaml new file mode 100644 index 0000000..567eafe --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mae/metafile.yaml @@ -0,0 +1,25 @@ +Models: +- Name: mae-base_upernet_8xb2-amp-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.13 + mIoU(ms+flip): 48.7 + Config: configs/mae/mae-base_upernet_8xb2-amp-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ViT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 9.96 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mae/upernet_mae-base_fp16_8x2_512x512_160k_ade20k/upernet_mae-base_fp16_8x2_512x512_160k_ade20k_20220426_174752-f92a2975.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mae/upernet_mae-base_fp16_8x2_512x512_160k_ade20k/upernet_mae-base_fp16_8x2_512x512_160k_ade20k_20220426_174752.log.json + Paper: + Title: Masked Autoencoders Are Scalable Vision Learners + URL: https://arxiv.org/abs/2111.06377 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.24.0/mmseg/models/backbones/mae.py#L46 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/mae/my_mae_upernet-base_b2_g1-40k_check_4000_my_dataset_model-512x512-testslide.py b/Seg_All_In_One_MMSeg/configs/mae/my_mae_upernet-base_b2_g1-40k_check_4000_my_dataset_model-512x512-testslide.py new file mode 100644 index 0000000..7cca5f8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mae/my_mae_upernet-base_b2_g1-40k_check_4000_my_dataset_model-512x512-testslide.py @@ -0,0 +1,130 @@ +_base_ = [ + '../_base_/models/upernet_mae.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + type='MAE', + img_size=(512, 512), + init_values=1.0, + # out_indices=[3, 5, 7, 11] + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/mae_pretrain_vit_base_mmcls.pth', + decode_head=dict( + in_channels=[ + 768, + 768, + 768, + 768, + ], + channels=768, + num_classes=36, + norm_cfg=dict( + type='BN', + ), + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + neck=dict(embed_dim=768, rescales=[4, 2, 1, 0.5]), # out_indices=[3, 5, 7, 11] + auxiliary_head=dict( + in_channels=768, + norm_cfg=dict( + type='BN', + ), + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + test_cfg=dict( + mode='slide', + crop_size=(512, 512), + stride=(341, 341), + ), +) + +fp16 = dict( + loss_scale='dynamic', +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', + lr=3e-05, + betas=(0.9, 0.999), + weight_decay=0.05, + ), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict( + num_layers=12, + layer_decay_rate=0.9, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=2, +) + diff --git a/Seg_All_In_One_MMSeg/configs/mae/my_mae_upernet-base_b4_g1-40k_check_4000_my_dataset_model-512x512-testslide.py b/Seg_All_In_One_MMSeg/configs/mae/my_mae_upernet-base_b4_g1-40k_check_4000_my_dataset_model-512x512-testslide.py new file mode 100644 index 0000000..0f4f706 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mae/my_mae_upernet-base_b4_g1-40k_check_4000_my_dataset_model-512x512-testslide.py @@ -0,0 +1,137 @@ +_base_ = [ + '../_base_/models/upernet_mae.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + backbone=dict( + type='MAE', + img_size=(512, 512), + init_values=1.0, + ), + data_preprocessor=dict( + size=(512, 512), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + pretrained='./My_Local_Model/pretrain/mae_pretrain_vit_base_mmcls.pth', + decode_head=dict( + in_channels=[ + 768, + 768, + 768, + 768, + ], + channels=768, + num_classes=36, + norm_cfg=dict( + type='BN', + ), + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), + neck=dict( + embed_dim=768, + rescales=[ + 4, + 2, + 1, + 0.5, + ], + ), + auxiliary_head=dict( + in_channels=768, + norm_cfg=dict( + type='BN', + ), + num_classes=36, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=0.4, + ), + ), + test_cfg=dict( + mode='slide', + crop_size=(512, 512), + stride=(341, 341), + ), +) + +fp16 = dict( + loss_scale='dynamic', +) + +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', + lr=3e-05, + betas=(0.9, 0.999), + weight_decay=0.05, + ), + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=dict( + num_layers=12, + layer_decay_rate=0.9, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + +train_dataloader = dict( + batch_size=4, +) + diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/README.md b/Seg_All_In_One_MMSeg/configs/mask2former/README.md new file mode 100644 index 0000000..c21ab0d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/README.md @@ -0,0 +1,74 @@ +# Mask2Former + +> [Masked-attention Mask Transformer for Universal Image Segmentation](https://arxiv.org/abs/2112.01527) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Image segmentation is about grouping pixels with different semantics, e.g., category or instance membership, where each choice of semantics defines a task. While only the semantics of each task differ, current research focuses on designing specialized architectures for each task. We present Masked-attention Mask Transformer (Mask2Former), a new architecture capable of addressing any image segmentation task (panoptic, instance or semantic). Its key components include masked attention, which extracts localized features by constraining cross-attention within predicted mask regions. In addition to reducing the research effort by at least three times, it outperforms the best specialized architectures by a significant margin on four popular datasets. Most notably, Mask2Former sets a new state-of-the-art for panoptic segmentation (57.8 PQ on COCO), instance segmentation (50.1 AP on COCO) and semantic segmentation (57.7 mIoU on ADE20K). + +### Usage + +- Mask2Former model needs to install [MMDetection](https://github.com/open-mmlab/mmdetection) first. + +```shell +pip install "mmdet>=3.0.0rc4" +``` + +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ----------- | -------------- | --------- | ------- | -------: | -------------- | ------ | ----- | ------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Mask2Former | R-50-D32 | 512x1024 | 90000 | 5.67 | 9.17 | A100 | 80.44 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024/mask2former_r50_8xb2-90k_cityscapes-512x1024_20221202_140802-ffd9d750.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024/mask2former_r50_8xb2-90k_cityscapes-512x1024_20221202_140802.json) | +| Mask2Former | R-101-D32 | 512x1024 | 90000 | 6.81 | 7.11 | A100 | 80.80 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024/mask2former_r101_8xb2-90k_cityscapes-512x1024_20221130_031628-43e68666.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024/mask2former_r101_8xb2-90k_cityscapes-512x1024_20221130_031628.json)) | +| Mask2Former | Swin-T | 512x1024 | 90000 | 6.36 | 7.18 | A100 | 81.71 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024/mask2former_swin-t_8xb2-90k_cityscapes-512x1024_20221127_144501-36c59341.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024/mask2former_swin-t_8xb2-90k_cityscapes-512x1024_20221127_144501.json)) | +| Mask2Former | Swin-S | 512x1024 | 90000 | 8.09 | 5.57 | A100 | 82.57 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024/mask2former_swin-s_8xb2-90k_cityscapes-512x1024_20221127_143802-9ab177f6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024/mask2former_swin-s_8xb2-90k_cityscapes-512x1024_20221127_143802.json)) | +| Mask2Former | Swin-B (in22k) | 512x1024 | 90000 | 10.89 | 4.32 | A100 | 83.52 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024_20221203_045030-9a86a225.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024_20221203_045030.json)) | +| Mask2Former | Swin-L (in22k) | 512x1024 | 90000 | 15.83 | 2.86 | A100 | 83.65 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024_20221202_141901-28ad20f1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024_20221202_141901.json)) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ----------- | -------------- | --------- | ------- | -------: | -------------- | ------ | ----- | ------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Mask2Former | R-50-D32 | 512x512 | 160000 | 3.31 | 26.59 | A100 | 47.87 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512/mask2former_r50_8xb2-160k_ade20k-512x512_20221204_000055-2d1f55f1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512/mask2former_r50_8xb2-160k_ade20k-512x512_20221204_000055.json)) | +| Mask2Former | R-101-D32 | 512x512 | 160000 | 4.09 | 22.97 | A100 | 48.60 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512/mask2former_r101_8xb2-160k_ade20k-512x512_20221203_233905-b7135890.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512/mask2former_r101_8xb2-160k_ade20k-512x512_20221203_233905.json)) | +| Mask2Former | Swin-T | 512x512 | 160000 | 3826 | 23.82 | A100 | 48.66 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512/mask2former_swin-t_8xb2-160k_ade20k-512x512_20221203_234230-7d64e5dd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512/mask2former_swin-t_8xb2-160k_ade20k-512x512_20221203_234230.json)) | +| Mask2Former | Swin-S | 512x512 | 160000 | 3.74 | 19.69 | A100 | 51.24 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512/mask2former_swin-s_8xb2-160k_ade20k-512x512_20221204_143905-e715144e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512/mask2former_swin-s_8xb2-160k_ade20k-512x512_20221204_143905.json)) | +| Mask2Former | Swin-B | 640x640 | 160000 | 5.66 | 12.48 | A100 | 52.44 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640_20221129_125118-a4a086d2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640_20221129_125118.json)) | +| Mask2Former | Swin-B (in22k) | 640x640 | 160000 | 5.66 | 12.43 | A100 | 53.90 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640_20221203_235230-7ec0f569.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640_20221203_235230.json)) | +| Mask2Former | Swin-L (in22k) | 640x640 | 160000 | 8.86 | 8.81 | A100 | 56.01 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640_20221203_235933-7120c214.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640_20221203_235933.json)) | + +Note: + +- All experiments of Mask2Former are implemented with 8 A100 GPUs with 2 samplers per GPU. +- As mentioned at [the official repo](https://github.com/facebookresearch/Mask2Former/issues/5), the results of Mask2Former are relatively not stable, the result of Mask2Former(swin-s) on ADE20K dataset in the table is the medium result obtained by training 5 times following the suggestion of the author. +- The ResNet backbones utilized in MaskFormer models are standard `ResNet` rather than `ResNetV1c`. +- Test time augmentation is not supported in MMSegmentation 1.x version yet, we would add "ms+flip" results as soon as possible. + +## Citation + +```bibtex +@inproceedings{cheng2021mask2former, + title={Masked-attention Mask Transformer for Universal Image Segmentation}, + author={Bowen Cheng and Ishan Misra and Alexander G. Schwing and Alexander Kirillov and Rohit Girdhar}, + journal={CVPR}, + year={2022} +} +@inproceedings{cheng2021maskformer, + title={Per-Pixel Classification is Not All You Need for Semantic Segmentation}, + author={Bowen Cheng and Alexander G. Schwing and Alexander Kirillov}, + journal={NeurIPS}, + year={2021} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..48f6c12 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,7 @@ +_base_ = ['./mask2former_r50_8xb2-160k_ade20k-512x512.py'] + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024.py new file mode 100644 index 0000000..275a7da --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = ['./mask2former_r50_8xb2-90k_cityscapes-512x1024.py'] + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..78cf605 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,200 @@ +_base_ = ['../_base_/default_runtime.py', '../_base_/datasets/ade20k.py'] + +custom_imports = dict(imports='mmdet.models', allow_failed_imports=False) + +crop_size = (512, 512) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size=crop_size, + test_cfg=dict(size_divisor=32)) +num_classes = 150 +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='ResNet', + depth=50, + deep_stem=False, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=-1, + norm_cfg=dict(type='SyncBN', requires_grad=False), + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + decode_head=dict( + type='Mask2FormerHead', + in_channels=[256, 512, 1024, 2048], + strides=[4, 8, 16, 32], + feat_channels=256, + out_channels=256, + num_classes=num_classes, + num_queries=100, + num_transformer_feat_level=3, + align_corners=False, + pixel_decoder=dict( + type='mmdet.MSDeformAttnPixelDecoder', + num_outs=3, + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU'), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + num_heads=8, + num_levels=3, + num_points=4, + im2col_step=64, + dropout=0.0, + batch_first=True, + norm_cfg=None, + init_cfg=None), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True))), + init_cfg=None), + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + init_cfg=None), + enforce_decoder_input_project=False, + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + transformer_decoder=dict( # Mask2FormerTransformerDecoder + return_intermediate=True, + num_layers=9, + layer_cfg=dict( # Mask2FormerTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + act_cfg=dict(type='ReLU', inplace=True), + ffn_drop=0.0, + dropout_layer=None, + add_identity=True)), + init_cfg=None), + loss_cls=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice=dict( + type='mmdet.DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0), + train_cfg=dict( + num_points=12544, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='mmdet.HungarianAssigner', + match_costs=[ + dict(type='mmdet.ClassificationCost', weight=2.0), + dict( + type='mmdet.CrossEntropyLossCost', + weight=5.0, + use_sigmoid=True), + dict( + type='mmdet.DiceCost', + weight=5.0, + pred_act=True, + eps=1.0) + ]), + sampler=dict(type='mmdet.MaskPseudoSampler'))), + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +# dataset config +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomChoiceResize', + scales=[int(512 * x * 0.1) for x in range(5, 21)], + resize_type='ResizeShortestEdge', + max_size=2048), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +train_dataloader = dict(batch_size=2, dataset=dict(pipeline=train_pipeline)) + +# optimizer +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +optimizer = dict( + type='AdamW', lr=0.0001, weight_decay=0.05, eps=1e-8, betas=(0.9, 0.999)) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi, + }, + norm_decay_mult=0.0)) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0, + power=0.9, + begin=0, + end=160000, + by_epoch=False) +] + +# training schedule for 160k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=160000, val_interval=5000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', by_epoch=False, interval=5000, + save_best='mIoU'), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024.py new file mode 100644 index 0000000..d2211b6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024.py @@ -0,0 +1,197 @@ +_base_ = ['../_base_/default_runtime.py', '../_base_/datasets/cityscapes.py'] + +crop_size = (512, 1024) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size=crop_size, + test_cfg=dict(size_divisor=32)) +num_classes = 19 +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='ResNet', + depth=50, + deep_stem=False, + num_stages=4, + out_indices=(0, 1, 2, 3), + frozen_stages=-1, + norm_cfg=dict(type='SyncBN', requires_grad=False), + style='pytorch', + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + decode_head=dict( + type='Mask2FormerHead', + in_channels=[256, 512, 1024, 2048], + strides=[4, 8, 16, 32], + feat_channels=256, + out_channels=256, + num_classes=num_classes, + num_queries=100, + num_transformer_feat_level=3, + align_corners=False, + pixel_decoder=dict( + type='mmdet.MSDeformAttnPixelDecoder', + num_outs=3, + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU'), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + num_heads=8, + num_levels=3, + num_points=4, + im2col_step=64, + dropout=0.0, + batch_first=True, + norm_cfg=None, + init_cfg=None), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True))), + init_cfg=None), + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + init_cfg=None), + enforce_decoder_input_project=False, + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + transformer_decoder=dict( # Mask2FormerTransformerDecoder + return_intermediate=True, + num_layers=9, + layer_cfg=dict( # Mask2FormerTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + act_cfg=dict(type='ReLU', inplace=True), + ffn_drop=0.0, + dropout_layer=None, + add_identity=True)), + init_cfg=None), + loss_cls=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice=dict( + type='mmdet.DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0), + train_cfg=dict( + num_points=12544, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='mmdet.HungarianAssigner', + match_costs=[ + dict(type='mmdet.ClassificationCost', weight=2.0), + dict( + type='mmdet.CrossEntropyLossCost', + weight=5.0, + use_sigmoid=True), + dict( + type='mmdet.DiceCost', + weight=5.0, + pred_act=True, + eps=1.0) + ]), + sampler=dict(type='mmdet.MaskPseudoSampler'))), + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +# dataset config +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomChoiceResize', + scales=[int(1024 * x * 0.1) for x in range(5, 21)], + resize_type='ResizeShortestEdge', + max_size=4096), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) + +# optimizer +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +optimizer = dict( + type='AdamW', lr=0.0001, weight_decay=0.05, eps=1e-8, betas=(0.9, 0.999)) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict( + custom_keys={ + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi, + }, + norm_decay_mult=0.0)) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0, + power=0.9, + begin=0, + end=90000, + by_epoch=False) +] + +# training schedule for 90k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=90000, val_interval=5000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', by_epoch=False, interval=5000, + save_best='mIoU'), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640.py new file mode 100644 index 0000000..d5c0434 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640.py @@ -0,0 +1,229 @@ +_base_ = [ + '../_base_/default_runtime.py', '../_base_/datasets/ade20k_640x640.py' +] + +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window12_384_20220317-55b0104a.pth' # noqa + +crop_size = (640, 640) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size=crop_size) +num_classes = 150 + +depths = [2, 2, 18, 2] +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=128, + depths=depths, + num_heads=[4, 8, 16, 32], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + decode_head=dict( + type='Mask2FormerHead', + in_channels=[128, 256, 512, 1024], + strides=[4, 8, 16, 32], + feat_channels=256, + out_channels=256, + num_classes=num_classes, + num_queries=100, + num_transformer_feat_level=3, + align_corners=False, + pixel_decoder=dict( + type='mmdet.MSDeformAttnPixelDecoder', + num_outs=3, + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU'), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + num_heads=8, + num_levels=3, + num_points=4, + im2col_step=64, + dropout=0.0, + batch_first=True, + norm_cfg=None, + init_cfg=None), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True))), + init_cfg=None), + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + init_cfg=None), + enforce_decoder_input_project=False, + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + transformer_decoder=dict( # Mask2FormerTransformerDecoder + return_intermediate=True, + num_layers=9, + layer_cfg=dict( # Mask2FormerTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + act_cfg=dict(type='ReLU', inplace=True), + ffn_drop=0.0, + dropout_layer=None, + add_identity=True)), + init_cfg=None), + loss_cls=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice=dict( + type='mmdet.DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0), + train_cfg=dict( + num_points=12544, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='mmdet.HungarianAssigner', + match_costs=[ + dict(type='mmdet.ClassificationCost', weight=2.0), + dict( + type='mmdet.CrossEntropyLossCost', + weight=5.0, + use_sigmoid=True), + dict( + type='mmdet.DiceCost', + weight=5.0, + pred_act=True, + eps=1.0) + ]), + sampler=dict(type='mmdet.MaskPseudoSampler'))), + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +# dataset config +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomChoiceResize', + scales=[int(x * 0.1 * 640) for x in range(5, 21)], + resize_type='ResizeShortestEdge', + max_size=2560), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +train_dataloader = dict(batch_size=2, dataset=dict(pipeline=train_pipeline)) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optimizer = dict( + type='AdamW', lr=0.0001, weight_decay=0.05, eps=1e-8, betas=(0.9, 0.999)) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) + +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0, + power=0.9, + begin=0, + end=160000, + by_epoch=False) +] + +# training schedule for 160k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=160000, val_interval=5000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', by_epoch=False, interval=5000, + save_best='mIoU'), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) + +# Default setting for scaling LR automatically +# - `enable` means enable scaling LR automatically +# or not by default. +# - `base_batch_size` = (8 GPUs) x (2 samples per GPU). +auto_scale_lr = dict(enable=False, base_batch_size=16) # TODO diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py new file mode 100644 index 0000000..f39a3c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py @@ -0,0 +1,5 @@ +_base_ = ['./mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640.py'] + +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window12_384_22k_20220317-e5c09f74.pth' # noqa +model = dict( + backbone=dict(init_cfg=dict(type='Pretrained', checkpoint=pretrained))) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py new file mode 100644 index 0000000..0c229c1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py @@ -0,0 +1,42 @@ +_base_ = ['./mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py'] +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window12_384_22k_20220317-e5c09f74.pth' # noqa + +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + pretrain_img_size=384, + embed_dims=128, + depths=depths, + num_heads=[4, 8, 16, 32], + window_size=12, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + decode_head=dict(in_channels=[128, 256, 512, 1024])) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py new file mode 100644 index 0000000..f2657e8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py @@ -0,0 +1,9 @@ +_base_ = ['./mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640.py'] +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_large_patch4_window12_384_22k_20220412-6580f57d.pth' # noqa + +model = dict( + backbone=dict( + embed_dims=192, + num_heads=[6, 12, 24, 48], + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + decode_head=dict(num_queries=100, in_channels=[192, 384, 768, 1536])) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py new file mode 100644 index 0000000..01a7b99 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py @@ -0,0 +1,42 @@ +_base_ = ['./mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py'] +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_large_patch4_window12_384_22k_20220412-6580f57d.pth' # noqa + +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + pretrain_img_size=384, + embed_dims=192, + depths=depths, + num_heads=[6, 12, 24, 48], + window_size=12, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + decode_head=dict(in_channels=[192, 384, 768, 1536])) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..a7796d5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,37 @@ +_base_ = ['./mask2former_swin-t_8xb2-160k_ade20k-512x512.py'] +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_small_patch4_window7_224_20220317-7ba6d6dd.pth' # noqa + +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + depths=depths, init_cfg=dict(type='Pretrained', + checkpoint=pretrained))) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024.py new file mode 100644 index 0000000..5f75544 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024.py @@ -0,0 +1,37 @@ +_base_ = ['./mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py'] +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_small_patch4_window7_224_20220317-7ba6d6dd.pth' # noqa + +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + depths=depths, init_cfg=dict(type='Pretrained', + checkpoint=pretrained))) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..9de3d24 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,52 @@ +_base_ = ['./mask2former_r50_8xb2-160k_ade20k-512x512.py'] +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_tiny_patch4_window7_224_20220317-1cdeb081.pth' # noqa +depths = [2, 2, 6, 2] +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, + depths=depths, + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + decode_head=dict(in_channels=[96, 192, 384, 768])) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py new file mode 100644 index 0000000..0abda64 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py @@ -0,0 +1,52 @@ +_base_ = ['./mask2former_r50_8xb2-90k_cityscapes-512x1024.py'] +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_tiny_patch4_window7_224_20220317-1cdeb081.pth' # noqa +depths = [2, 2, 6, 2] +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + embed_dims=96, + depths=depths, + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2, 3), + with_cp=False, + frozen_stages=-1, + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + decode_head=dict(in_channels=[96, 192, 384, 768])) + +# set all layers in backbone to lr_mult=0.1 +# set all norm layers, position_embeding, +# query_embeding, level_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=0.1, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=0.1, decay_mult=0.0) +embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=0.1, decay_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'absolute_pos_embed': backbone_embed_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, + 'query_feat': embed_multi, + 'level_embed': embed_multi +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + paramwise_cfg=dict(custom_keys=custom_keys, norm_decay_mult=0.0)) diff --git a/Seg_All_In_One_MMSeg/configs/mask2former/metafile.yaml b/Seg_All_In_One_MMSeg/configs/mask2former/metafile.yaml new file mode 100644 index 0000000..090c95e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mask2former/metafile.yaml @@ -0,0 +1,314 @@ +Collections: +- Name: Mask2Former + License: Apache License 2.0 + Metadata: + Training Data: + - Usage + - Cityscapes + - ADE20K + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + README: configs/mask2former/README.md + Frameworks: + - PyTorch +Models: +- Name: mask2former_r50_8xb2-90k_cityscapes-512x1024 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.44 + Config: configs/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - R-50-D32 + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 5.67 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024/mask2former_r50_8xb2-90k_cityscapes-512x1024_20221202_140802-ffd9d750.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r50_8xb2-90k_cityscapes-512x1024/mask2former_r50_8xb2-90k_cityscapes-512x1024_20221202_140802.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_r101_8xb2-90k_cityscapes-512x1024 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.8 + Config: configs/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - R-101-D32 + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 6.81 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024/mask2former_r101_8xb2-90k_cityscapes-512x1024_20221130_031628-43e68666.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r101_8xb2-90k_cityscapes-512x1024/mask2former_r101_8xb2-90k_cityscapes-512x1024_20221130_031628.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-t_8xb2-90k_cityscapes-512x1024 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 81.71 + Config: configs/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - Swin-T + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 6.36 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024/mask2former_swin-t_8xb2-90k_cityscapes-512x1024_20221127_144501-36c59341.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-t_8xb2-90k_cityscapes-512x1024/mask2former_swin-t_8xb2-90k_cityscapes-512x1024_20221127_144501.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-s_8xb2-90k_cityscapes-512x1024 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 82.57 + Config: configs/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - Swin-S + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 8.09 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024/mask2former_swin-s_8xb2-90k_cityscapes-512x1024_20221127_143802-9ab177f6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-s_8xb2-90k_cityscapes-512x1024/mask2former_swin-s_8xb2-90k_cityscapes-512x1024_20221127_143802.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 83.52 + Config: configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - Swin-B + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 10.89 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024_20221203_045030-9a86a225.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024/mask2former_swin-b-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024_20221203_045030.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 83.65 + Config: configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - Swin-L + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 15.83 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024_20221202_141901-28ad20f1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024/mask2former_swin-l-in22k-384x384-pre_8xb2-90k_cityscapes-512x1024_20221202_141901.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_r50_8xb2-160k_ade20k-512x512 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.87 + Config: configs/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D32 + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 3.31 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512/mask2former_r50_8xb2-160k_ade20k-512x512_20221204_000055-2d1f55f1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r50_8xb2-160k_ade20k-512x512/mask2former_r50_8xb2-160k_ade20k-512x512_20221204_000055.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_r101_8xb2-160k_ade20k-512x512 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.6 + Config: configs/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D32 + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 4.09 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512/mask2former_r101_8xb2-160k_ade20k-512x512_20221203_233905-b7135890.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_r101_8xb2-160k_ade20k-512x512/mask2former_r101_8xb2-160k_ade20k-512x512_20221203_233905.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-t_8xb2-160k_ade20k-512x512 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.66 + Config: configs/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-T + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 3826.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512/mask2former_swin-t_8xb2-160k_ade20k-512x512_20221203_234230-7d64e5dd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-t_8xb2-160k_ade20k-512x512/mask2former_swin-t_8xb2-160k_ade20k-512x512_20221203_234230.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-s_8xb2-160k_ade20k-512x512 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 51.24 + Config: configs/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-S + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 3.74 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512/mask2former_swin-s_8xb2-160k_ade20k-512x512_20221204_143905-e715144e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-s_8xb2-160k_ade20k-512x512/mask2former_swin-s_8xb2-160k_ade20k-512x512_20221204_143905.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 52.44 + Config: configs/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-B + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 5.66 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640_20221129_125118-a4a086d2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-b-in1k-384x384-pre_8xb2-160k_ade20k-640x640_20221129_125118.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 53.9 + Config: configs/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-B + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 5.66 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640_20221203_235230-7ec0f569.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-b-in22k-384x384-pre_8xb2-160k_ade20k-640x640_20221203_235230.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch +- Name: mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640 + In Collection: Mask2Former + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 56.01 + Config: configs/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-L + - Mask2Former + Training Resources: 8x A100 GPUS + Memory (GB): 8.86 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640_20221203_235933-7120c214.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mask2former/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640/mask2former_swin-l-in22k-384x384-pre_8xb2-160k_ade20k-640x640_20221203_235933.json + Paper: + Title: Masked-attention Mask Transformer for Universal Image Segmentation + URL: https://arxiv.org/abs/2112.01527 + Code: https://github.com/open-mmlab/mmdetection/blob/3.x/mmdet/models/dense_heads/mask2former_head.py + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/maskformer/README.md b/Seg_All_In_One_MMSeg/configs/maskformer/README.md new file mode 100644 index 0000000..a899bac --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/maskformer/README.md @@ -0,0 +1,62 @@ +# MaskFormer + +> [MaskFormer: Per-Pixel Classification is Not All You Need for Semantic Segmentation](https://arxiv.org/abs/2107.06278) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Modern approaches typically formulate semantic segmentation as a per-pixel classification task, while instance-level segmentation is handled with an alternative mask classification. Our key insight: mask classification is sufficiently general to solve both semantic- and instance-level segmentation tasks in a unified manner using the exact same model, loss, and training procedure. Following this observation, we propose MaskFormer, a simple mask classification model which predicts a set of binary masks, each associated with a single global class label prediction. Overall, the proposed mask classification-based method simplifies the landscape of effective approaches to semantic and panoptic segmentation tasks and shows excellent empirical results. In particular, we observe that MaskFormer outperforms per-pixel classification baselines when the number of classes is large. Our mask classification-based method outperforms both current state-of-the-art semantic (55.6 mIoU on ADE20K) and panoptic segmentation (52.7 PQ on COCO) models. + + + +
+ +
+ +### Usage + +- MaskFormer model needs to install [MMDetection](https://github.com/open-mmlab/mmdetection) first. + +```shell +pip install "mmdet>=3.0.0rc4" +``` + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | --------- | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| MaskFormer | R-50-D32 | 512x512 | 160000 | 3.29 | A100 | 42.20 | 44.29 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512/maskformer_r50-d32_8xb2-160k_ade20k-512x512_20221030_182724-3a9cfe45.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512/maskformer_r50-d32_8xb2-160k_ade20k-512x512_20221030_182724.json) | +| MaskFormer | R-101-D32 | 512x512 | 160000 | 4.12 | A100 | 34.90 | 45.11 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512/maskformer_r101-d32_8xb2-160k_ade20k-512x512_20221031_223053-84adbfcb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512/maskformer_r101-d32_8xb2-160k_ade20k-512x512_20221031_223053.json) | +| MaskFormer | Swin-T | 512x512 | 160000 | 3.73 | A100 | 40.53 | 46.69 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512_20221114_232813-f14e7ce0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512_20221114_232813.json) | +| MaskFormer | Swin-S | 512x512 | 160000 | 5.33 | A100 | 26.98 | 49.36 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512_20221115_114710-723512c7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512_20221115_114710.json) | + +Note: + +- All experiments of MaskFormer are implemented with 8 V100 (32G) GPUs with 2 samplers per GPU. +- The results of MaskFormer are relatively not stable. The accuracy (mIoU) of model with `R-101-D32` is from 44.7 to 46.0, and with `Swin-S` is from 49.0 to 49.8. +- The ResNet backbones utilized in MaskFormer models are standard `ResNet` rather than `ResNetV1c`. +- Test time augmentation is not supported in MMSegmentation 1.x version yet, we would add "ms+flip" results as soon as possible. + +## Citation + +```bibtex +@article{cheng2021per, + title={Per-pixel classification is not all you need for semantic segmentation}, + author={Cheng, Bowen and Schwing, Alex and Kirillov, Alexander}, + journal={Advances in Neural Information Processing Systems}, + volume={34}, + pages={17864--17875}, + year={2021} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..04bd375 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,7 @@ +_base_ = './maskformer_r50-d32_8xb2-160k_ade20k-512x512.py' + +model = dict( + backbone=dict( + depth=101, + init_cfg=dict(type='Pretrained', + checkpoint='torchvision://resnet101'))) diff --git a/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..2a83746 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,141 @@ +_base_ = [ + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +norm_cfg = dict(type='SyncBN', requires_grad=True) +crop_size = (512, 512) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=crop_size, + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +# model_cfg +num_classes = 150 +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='ResNet', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 1, 1), + strides=(1, 2, 2, 2), + norm_cfg=norm_cfg, + norm_eval=True, + style='pytorch', + contract_dilation=True, + init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), + decode_head=dict( + type='MaskFormerHead', + in_channels=[256, 512, 1024, + 2048], # input channels of pixel_decoder modules + feat_channels=256, + in_index=[0, 1, 2, 3], + num_classes=150, + out_channels=256, + num_queries=100, + pixel_decoder=dict( + type='mmdet.PixelDecoder', + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU')), + enforce_decoder_input_project=False, + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + transformer_decoder=dict( # DetrTransformerDecoder + return_intermediate=True, + num_layers=6, + layer_cfg=dict( # DetrTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.1, + proj_drop=0.1, + dropout_layer=None, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.1, + proj_drop=0.1, + dropout_layer=None, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + act_cfg=dict(type='ReLU', inplace=True), + ffn_drop=0.1, + dropout_layer=None, + add_identity=True)), + init_cfg=None), + loss_cls=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type='mmdet.FocalLoss', + use_sigmoid=True, + gamma=2.0, + alpha=0.25, + reduction='mean', + loss_weight=20.0), + loss_dice=dict( + type='mmdet.DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=1.0), + train_cfg=dict( + assigner=dict( + type='mmdet.HungarianAssigner', + match_costs=[ + dict(type='mmdet.ClassificationCost', weight=1.0), + dict( + type='mmdet.FocalLossCost', + weight=20.0, + binary_input=True), + dict( + type='mmdet.DiceCost', + weight=1.0, + pred_act=True, + eps=1.0) + ]), + sampler=dict(type='mmdet.MaskPseudoSampler'))), + # training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole'), +) +# optimizer +optimizer = dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.0001) +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict(custom_keys={ + 'backbone': dict(lr_mult=0.1), + })) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0, + power=0.9, + begin=0, + end=160000, + by_epoch=False) +] + +# In MaskFormer implementation we use batch size 2 per GPU as default +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..2cbc038 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,79 @@ +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_small_patch4_window7_224_20220317-7ba6d6dd.pth' # noqa +_base_ = './maskformer_r50-d32_8xb2-160k_ade20k-512x512.py' +backbone_norm_cfg = dict(type='LN', requires_grad=True) +depths = [2, 2, 18, 2] +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=224, + embed_dims=96, + patch_size=4, + window_size=7, + mlp_ratio=4, + depths=depths, + num_heads=[3, 6, 12, 24], + strides=(4, 2, 2, 2), + out_indices=(0, 1, 2, 3), + qkv_bias=True, + qk_scale=None, + patch_norm=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + use_abs_pos_embed=False, + act_cfg=dict(type='GELU'), + norm_cfg=backbone_norm_cfg, + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file)), + decode_head=dict( + type='MaskFormerHead', + in_channels=[96, 192, 384, + 768], # input channels of pixel_decoder modules + )) + +# optimizer +optimizer = dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01) +# set all layers in backbone to lr_mult=1.0 +# set all norm layers, position_embeding, +# query_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=1.0, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +embed_multi = dict(decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict(custom_keys=custom_keys)) + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..aa242db --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,81 @@ +_base_ = './maskformer_r50-d32_8xb2-160k_ade20k-512x512.py' + +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_tiny_patch4_window7_224_20220317-1cdeb081.pth' # noqa +backbone_norm_cfg = dict(type='LN', requires_grad=True) +depths = [2, 2, 6, 2] +model = dict( + backbone=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=224, + embed_dims=96, + patch_size=4, + window_size=7, + mlp_ratio=4, + depths=depths, + num_heads=[3, 6, 12, 24], + strides=(4, 2, 2, 2), + out_indices=(0, 1, 2, 3), + qkv_bias=True, + qk_scale=None, + patch_norm=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + use_abs_pos_embed=False, + act_cfg=dict(type='GELU'), + norm_cfg=backbone_norm_cfg, + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file)), + decode_head=dict( + type='MaskFormerHead', + in_channels=[96, 192, 384, + 768], # input channels of pixel_decoder modules + )) + +# optimizer +optimizer = dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01) + +# set all layers in backbone to lr_mult=1.0 +# set all norm layers, position_embeding, +# query_embeding to decay_multi=0.0 +backbone_norm_multi = dict(lr_mult=1.0, decay_mult=0.0) +backbone_embed_multi = dict(lr_mult=1.0, decay_mult=0.0) +embed_multi = dict(decay_mult=0.0) +custom_keys = { + 'backbone': dict(lr_mult=1.0), + 'backbone.patch_embed.norm': backbone_norm_multi, + 'backbone.norm': backbone_norm_multi, + 'relative_position_bias_table': backbone_embed_multi, + 'query_embed': embed_multi, +} +custom_keys.update({ + f'backbone.stages.{stage_id}.blocks.{block_id}.norm': backbone_norm_multi + for stage_id, num_blocks in enumerate(depths) + for block_id in range(num_blocks) +}) +custom_keys.update({ + f'backbone.stages.{stage_id}.downsample.norm': backbone_norm_multi + for stage_id in range(len(depths) - 1) +}) +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2), + paramwise_cfg=dict(custom_keys=custom_keys)) + +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/maskformer/metafile.yaml b/Seg_All_In_One_MMSeg/configs/maskformer/metafile.yaml new file mode 100644 index 0000000..c9853e1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/maskformer/metafile.yaml @@ -0,0 +1,111 @@ +Collections: +- Name: MaskFormer + License: Apache License 2.0 + Metadata: + Training Data: + - Usage + - ADE20K + Paper: + Title: 'MaskFormer: Per-Pixel Classification is Not All You Need for Semantic + Segmentation' + URL: https://arxiv.org/abs/2107.06278 + README: configs/maskformer/README.md + Frameworks: + - PyTorch +Models: +- Name: maskformer_r50-d32_8xb2-160k_ade20k-512x512 + In Collection: MaskFormer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.29 + Config: configs/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D32 + - MaskFormer + Training Resources: 8x 42.20 GPUS + Memory (GB): 3.29 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512/maskformer_r50-d32_8xb2-160k_ade20k-512x512_20221030_182724-3a9cfe45.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512/maskformer_r50-d32_8xb2-160k_ade20k-512x512_20221030_182724.json + Paper: + Title: 'MaskFormer: Per-Pixel Classification is Not All You Need for Semantic + Segmentation' + URL: https://arxiv.org/abs/2107.06278 + Code: https://github.com/open-mmlab/mmdetection/blob/dev-3.x/mmdet/models/dense_heads/maskformer_head.py#L21 + Framework: PyTorch +- Name: maskformer_r101-d32_8xb2-160k_ade20k-512x512 + In Collection: MaskFormer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.11 + Config: configs/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D32 + - MaskFormer + Training Resources: 8x 34.90 GPUS + Memory (GB): 4.12 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512/maskformer_r101-d32_8xb2-160k_ade20k-512x512_20221031_223053-84adbfcb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_r101-d32_8xb2-160k_ade20k-512x512/maskformer_r101-d32_8xb2-160k_ade20k-512x512_20221031_223053.json + Paper: + Title: 'MaskFormer: Per-Pixel Classification is Not All You Need for Semantic + Segmentation' + URL: https://arxiv.org/abs/2107.06278 + Code: https://github.com/open-mmlab/mmdetection/blob/dev-3.x/mmdet/models/dense_heads/maskformer_head.py#L21 + Framework: PyTorch +- Name: maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512 + In Collection: MaskFormer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.69 + Config: configs/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-T + - MaskFormer + Training Resources: 8x 40.53 GPUS + Memory (GB): 3.73 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512_20221114_232813-f14e7ce0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512/maskformer_swin-t_upernet_8xb2-160k_ade20k-512x512_20221114_232813.json + Paper: + Title: 'MaskFormer: Per-Pixel Classification is Not All You Need for Semantic + Segmentation' + URL: https://arxiv.org/abs/2107.06278 + Code: https://github.com/open-mmlab/mmdetection/blob/dev-3.x/mmdet/models/dense_heads/maskformer_head.py#L21 + Framework: PyTorch +- Name: maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512 + In Collection: MaskFormer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 49.36 + Config: configs/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-S + - MaskFormer + Training Resources: 8x 26.98 GPUS + Memory (GB): 5.33 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512_20221115_114710-723512c7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/maskformer/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512/maskformer_swin-s_upernet_8xb2-160k_ade20k-512x512_20221115_114710.json + Paper: + Title: 'MaskFormer: Per-Pixel Classification is Not All You Need for Semantic + Segmentation' + URL: https://arxiv.org/abs/2107.06278 + Code: https://github.com/open-mmlab/mmdetection/blob/dev-3.x/mmdet/models/dense_heads/maskformer_head.py#L21 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/README.md b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/README.md new file mode 100644 index 0000000..bff5259 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/README.md @@ -0,0 +1,56 @@ +# MobileNetV2 + +> [MobileNetV2: Inverted Residuals and Linear Bottlenecks](https://arxiv.org/abs/1801.04381) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +In this paper we describe a new mobile architecture, MobileNetV2, that improves the state of the art performance of mobile models on multiple tasks and benchmarks as well as across a spectrum of different model sizes. We also describe efficient ways of applying these mobile models to object detection in a novel framework we call SSDLite. Additionally, we demonstrate how to build mobile semantic segmentation models through a reduced form of DeepLabv3 which we call Mobile DeepLabv3. +The MobileNetV2 architecture is based on an inverted residual structure where the input and output of the residual block are thin bottleneck layers opposite to traditional residual models which use expanded representations in the input an MobileNetV2 uses lightweight depthwise convolutions to filter features in the intermediate expansion layer. Additionally, we find that it is important to remove non-linearities in the narrow layers in order to maintain representational power. We demonstrate that this improves performance and provide an intuition that led to this design. Finally, our approach allows decoupling of the input/output domains from the expressiveness of the transformation, which provides a convenient framework for further analysis. We measure our performance on Imagenet classification, COCO object detection, VOC image segmentation. We evaluate the trade-offs between accuracy, and number of operations measured by multiply-adds (MAdd), as well as the number of parameters. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | M-V2-D8 | 512x1024 | 80000 | 3.4 | 14.2 | A100 | 71.19 | 73.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024-20230224_185436-13fef4ea.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024_20230224_185436.json) | +| PSPNet | M-V2-D8 | 512x1024 | 80000 | 3.6 | 11.2 | V100 | 70.23 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/pspnet_m-v2-d8_512x1024_80k_cityscapes/pspnet_m-v2-d8_512x1024_80k_cityscapes_20200825_124817-19e81d51.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/pspnet_m-v2-d8_512x1024_80k_cityscapes/pspnet_m-v2-d8_512x1024_80k_cityscapes-20200825_124817.log.json) | +| DeepLabV3 | M-V2-D8 | 512x1024 | 80000 | 3.9 | 8.4 | V100 | 73.84 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3_m-v2-d8_512x1024_80k_cityscapes/deeplabv3_m-v2-d8_512x1024_80k_cityscapes_20200825_124836-bef03590.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3_m-v2-d8_512x1024_80k_cityscapes/deeplabv3_m-v2-d8_512x1024_80k_cityscapes-20200825_124836.log.json) | +| DeepLabV3+ | M-V2-D8 | 512x1024 | 80000 | 5.1 | 8.4 | V100 | 75.20 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3plus_m-v2-d8_512x1024_80k_cityscapes/deeplabv3plus_m-v2-d8_512x1024_80k_cityscapes_20200825_124836-d256dd4b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3plus_m-v2-d8_512x1024_80k_cityscapes/deeplabv3plus_m-v2-d8_512x1024_80k_cityscapes-20200825_124836.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | M-V2-D8 | 512x512 | 160000 | 6.5 | 64.4 | V100 | 19.71 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/fcn_m-v2-d8_512x512_160k_ade20k/fcn_m-v2-d8_512x512_160k_ade20k_20200825_214953-c40e1095.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/fcn_m-v2-d8_512x512_160k_ade20k/fcn_m-v2-d8_512x512_160k_ade20k-20200825_214953.log.json) | +| PSPNet | M-V2-D8 | 512x512 | 160000 | 6.5 | 57.7 | V100 | 29.68 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/pspnet_m-v2-d8_512x512_160k_ade20k/pspnet_m-v2-d8_512x512_160k_ade20k_20200825_214953-f5942f7a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/pspnet_m-v2-d8_512x512_160k_ade20k/pspnet_m-v2-d8_512x512_160k_ade20k-20200825_214953.log.json) | +| DeepLabV3 | M-V2-D8 | 512x512 | 160000 | 6.8 | 39.9 | V100 | 34.08 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3_m-v2-d8_512x512_160k_ade20k/deeplabv3_m-v2-d8_512x512_160k_ade20k_20200825_223255-63986343.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3_m-v2-d8_512x512_160k_ade20k/deeplabv3_m-v2-d8_512x512_160k_ade20k-20200825_223255.log.json) | +| DeepLabV3+ | M-V2-D8 | 512x512 | 160000 | 8.2 | 43.1 | V100 | 34.02 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3plus_m-v2-d8_512x512_160k_ade20k/deeplabv3plus_m-v2-d8_512x512_160k_ade20k_20200825_223255-465a01d4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3plus_m-v2-d8_512x512_160k_ade20k/deeplabv3plus_m-v2-d8_512x512_160k_ade20k-20200825_223255.log.json) | + +## Citation + +```bibtex +@inproceedings{sandler2018mobilenetv2, + title={Mobilenetv2: Inverted residuals and linear bottlenecks}, + author={Sandler, Mark and Howard, Andrew and Zhu, Menglong and Zhmoginov, Andrey and Chen, Liang-Chieh}, + booktitle={Proceedings of the IEEE conference on computer vision and pattern recognition}, + pages={4510--4520}, + year={2018} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/metafile.yaml b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/metafile.yaml new file mode 100644 index 0000000..119c9ae --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/metafile.yaml @@ -0,0 +1,186 @@ +Models: +- Name: mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 71.19 + mIoU(ms+flip): 73.34 + Config: configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - M-V2-D8 + - FCN + Training Resources: 4x A100 GPUS + Memory (GB): 3.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024-20230224_185436-13fef4ea.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024_20230224_185436.json + Paper: + Title: 'MobileNetV2: Inverted Residuals and Linear Bottlenecks' + URL: https://arxiv.org/abs/1801.04381 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v2.py#L14 + Framework: PyTorch +- Name: mobilenet-v2-d8_pspnet_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 70.23 + Config: configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - M-V2-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 3.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/pspnet_m-v2-d8_512x1024_80k_cityscapes/pspnet_m-v2-d8_512x1024_80k_cityscapes_20200825_124817-19e81d51.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/pspnet_m-v2-d8_512x1024_80k_cityscapes/pspnet_m-v2-d8_512x1024_80k_cityscapes-20200825_124817.log.json + Paper: + Title: 'MobileNetV2: Inverted Residuals and Linear Bottlenecks' + URL: https://arxiv.org/abs/1801.04381 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v2.py#L14 + Framework: PyTorch +- Name: mobilenet-v2-d8_deeplabv3_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.84 + Config: configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - M-V2-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 3.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3_m-v2-d8_512x1024_80k_cityscapes/deeplabv3_m-v2-d8_512x1024_80k_cityscapes_20200825_124836-bef03590.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3_m-v2-d8_512x1024_80k_cityscapes/deeplabv3_m-v2-d8_512x1024_80k_cityscapes-20200825_124836.log.json + Paper: + Title: 'MobileNetV2: Inverted Residuals and Linear Bottlenecks' + URL: https://arxiv.org/abs/1801.04381 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v2.py#L14 + Framework: PyTorch +- Name: mobilenet-v2-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.2 + Config: configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - M-V2-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 5.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3plus_m-v2-d8_512x1024_80k_cityscapes/deeplabv3plus_m-v2-d8_512x1024_80k_cityscapes_20200825_124836-d256dd4b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3plus_m-v2-d8_512x1024_80k_cityscapes/deeplabv3plus_m-v2-d8_512x1024_80k_cityscapes-20200825_124836.log.json + Paper: + Title: 'MobileNetV2: Inverted Residuals and Linear Bottlenecks' + URL: https://arxiv.org/abs/1801.04381 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v2.py#L14 + Framework: PyTorch +- Name: mobilenet-v2-d8_fcn_4xb4-160k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 19.71 + Config: configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - M-V2-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 6.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/fcn_m-v2-d8_512x512_160k_ade20k/fcn_m-v2-d8_512x512_160k_ade20k_20200825_214953-c40e1095.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/fcn_m-v2-d8_512x512_160k_ade20k/fcn_m-v2-d8_512x512_160k_ade20k-20200825_214953.log.json + Paper: + Title: 'MobileNetV2: Inverted Residuals and Linear Bottlenecks' + URL: https://arxiv.org/abs/1801.04381 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v2.py#L14 + Framework: PyTorch +- Name: mobilenet-v2-d8_pspnet_4xb4-160k_ade20k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 29.68 + Config: configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - M-V2-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/pspnet_m-v2-d8_512x512_160k_ade20k/pspnet_m-v2-d8_512x512_160k_ade20k_20200825_214953-f5942f7a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/pspnet_m-v2-d8_512x512_160k_ade20k/pspnet_m-v2-d8_512x512_160k_ade20k-20200825_214953.log.json + Paper: + Title: 'MobileNetV2: Inverted Residuals and Linear Bottlenecks' + URL: https://arxiv.org/abs/1801.04381 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v2.py#L14 + Framework: PyTorch +- Name: mobilenet-v2-d8_deeplabv3_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 34.08 + Config: configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - M-V2-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 6.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3_m-v2-d8_512x512_160k_ade20k/deeplabv3_m-v2-d8_512x512_160k_ade20k_20200825_223255-63986343.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3_m-v2-d8_512x512_160k_ade20k/deeplabv3_m-v2-d8_512x512_160k_ade20k-20200825_223255.log.json + Paper: + Title: 'MobileNetV2: Inverted Residuals and Linear Bottlenecks' + URL: https://arxiv.org/abs/1801.04381 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v2.py#L14 + Framework: PyTorch +- Name: mobilenet-v2-d8_deeplabv3plus_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 34.02 + Config: configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - M-V2-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 8.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3plus_m-v2-d8_512x512_160k_ade20k/deeplabv3plus_m-v2-d8_512x512_160k_ade20k_20200825_223255-465a01d4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v2/deeplabv3plus_m-v2-d8_512x512_160k_ade20k/deeplabv3plus_m-v2-d8_512x512_160k_ade20k-20200825_223255.log.json + Paper: + Title: 'MobileNetV2: Inverted Residuals and Linear Bottlenecks' + URL: https://arxiv.org/abs/1801.04381 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v2.py#L14 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..ece9b0b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,13 @@ +_base_ = '../deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='mmcls://mobilenet_v2', + backbone=dict( + _delete_=True, + type='MobileNetV2', + widen_factor=1., + strides=(1, 2, 2, 1, 1, 1, 1), + dilations=(1, 1, 1, 2, 2, 4, 4), + out_indices=(1, 2, 4, 6), + norm_cfg=dict(type='SyncBN', requires_grad=True)), + decode_head=dict(in_channels=320), + auxiliary_head=dict(in_channels=96)) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..86eec0d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,13 @@ +_base_ = '../deeplabv3/deeplabv3_r101-d8_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='mmcls://mobilenet_v2', + backbone=dict( + _delete_=True, + type='MobileNetV2', + widen_factor=1., + strides=(1, 2, 2, 1, 1, 1, 1), + dilations=(1, 1, 1, 2, 2, 4, 4), + out_indices=(1, 2, 4, 6), + norm_cfg=dict(type='SyncBN', requires_grad=True)), + decode_head=dict(in_channels=320), + auxiliary_head=dict(in_channels=96)) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..195046e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,15 @@ +_base_ = [ + '../deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py' +] +model = dict( + pretrained='mmcls://mobilenet_v2', + backbone=dict( + _delete_=True, + type='MobileNetV2', + widen_factor=1., + strides=(1, 2, 2, 1, 1, 1, 1), + dilations=(1, 1, 1, 2, 2, 4, 4), + out_indices=(1, 2, 4, 6), + norm_cfg=dict(type='SyncBN', requires_grad=True)), + decode_head=dict(in_channels=320, c1_in_channels=24), + auxiliary_head=dict(in_channels=96)) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..d4f669f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,13 @@ +_base_ = '../deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='mmcls://mobilenet_v2', + backbone=dict( + _delete_=True, + type='MobileNetV2', + widen_factor=1., + strides=(1, 2, 2, 1, 1, 1, 1), + dilations=(1, 1, 1, 2, 2, 4, 4), + out_indices=(1, 2, 4, 6), + norm_cfg=dict(type='SyncBN', requires_grad=True)), + decode_head=dict(in_channels=320, c1_in_channels=24), + auxiliary_head=dict(in_channels=96)) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..0829f43 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,13 @@ +_base_ = '../fcn/fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='mmcls://mobilenet_v2', + backbone=dict( + _delete_=True, + type='MobileNetV2', + widen_factor=1., + strides=(1, 2, 2, 1, 1, 1, 1), + dilations=(1, 1, 1, 2, 2, 4, 4), + out_indices=(1, 2, 4, 6), + norm_cfg=dict(type='SyncBN', requires_grad=True)), + decode_head=dict(in_channels=320), + auxiliary_head=dict(in_channels=96)) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..015fa6f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_fcn_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,13 @@ +_base_ = '../fcn/fcn_r101-d8_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='mmcls://mobilenet_v2', + backbone=dict( + _delete_=True, + type='MobileNetV2', + widen_factor=1., + strides=(1, 2, 2, 1, 1, 1, 1), + dilations=(1, 1, 1, 2, 2, 4, 4), + out_indices=(1, 2, 4, 6), + norm_cfg=dict(type='SyncBN', requires_grad=True)), + decode_head=dict(in_channels=320), + auxiliary_head=dict(in_channels=96)) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..8542e02 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,13 @@ +_base_ = '../pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='mmcls://mobilenet_v2', + backbone=dict( + _delete_=True, + type='MobileNetV2', + widen_factor=1., + strides=(1, 2, 2, 1, 1, 1, 1), + dilations=(1, 1, 1, 2, 2, 4, 4), + out_indices=(1, 2, 4, 6), + norm_cfg=dict(type='SyncBN', requires_grad=True)), + decode_head=dict(in_channels=320), + auxiliary_head=dict(in_channels=96)) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..73db59b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v2/mobilenet-v2-d8_pspnet_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,13 @@ +_base_ = '../pspnet/pspnet_r101-d8_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='mmcls://mobilenet_v2', + backbone=dict( + _delete_=True, + type='MobileNetV2', + widen_factor=1., + strides=(1, 2, 2, 1, 1, 1, 1), + dilations=(1, 1, 1, 2, 2, 4, 4), + out_indices=(1, 2, 4, 6), + norm_cfg=dict(type='SyncBN', requires_grad=True)), + decode_head=dict(in_channels=320), + auxiliary_head=dict(in_channels=96)) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v3/README.md b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/README.md new file mode 100644 index 0000000..8ed0a56 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/README.md @@ -0,0 +1,50 @@ +# MobileNetV3 + +> [Searching for MobileNetV3](https://arxiv.org/abs/1905.02244) + +## Introduction + + + + + +Official Repo + +Code Snippet + +## Abstract + + + +We present the next generation of MobileNets based on a combination of complementary search techniques as well as a novel architecture design. MobileNetV3 is tuned to mobile phone CPUs through a combination of hardware-aware network architecture search (NAS) complemented by the NetAdapt algorithm and then subsequently improved through novel architecture advances. This paper starts the exploration of how automated search algorithms and network design can work together to harness complementary approaches improving the overall state of the art. Through this process we create two new MobileNet models for release: MobileNetV3-Large and MobileNetV3-Small which are targeted for high and low resource use cases. These models are then adapted and applied to the tasks of object detection and semantic segmentation. For the task of semantic segmentation (or any dense pixel prediction), we propose a new efficient segmentation decoder Lite Reduced Atrous Spatial Pyramid Pooling (LR-ASPP). We achieve new state of the art results for mobile classification, detection and segmentation. MobileNetV3-Large is 3.2% more accurate on ImageNet classification while reducing latency by 15% compared to MobileNetV2. MobileNetV3-Small is 4.6% more accurate while reducing latency by 5% compared to MobileNetV2. MobileNetV3-Large detection is 25% faster at roughly the same accuracy as MobileNetV2 on COCO detection. MobileNetV3-Large LR-ASPP is 30% faster than MobileNetV2 R-ASPP at similar accuracy for Cityscapes segmentation. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| LRASPP | M-V3-D8 | 512x1024 | 320000 | 8.9 | 15.22 | V100 | 69.54 | 70.89 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v3/mobilenet-v3-d8_lraspp_4xb4-320k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3-d8_512x1024_320k_cityscapes/lraspp_m-v3-d8_512x1024_320k_cityscapes_20201224_220337-cfe8fb07.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3-d8_512x1024_320k_cityscapes/lraspp_m-v3-d8_512x1024_320k_cityscapes-20201224_220337.log.json) | +| LRASPP | M-V3-D8 (scratch) | 512x1024 | 320000 | 8.9 | 14.77 | V100 | 67.87 | 69.78 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v3/mobilenet-v3-d8-scratch_lraspp_4xb4-320k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3-d8_scratch_512x1024_320k_cityscapes/lraspp_m-v3-d8_scratch_512x1024_320k_cityscapes_20201224_220337-9f29cd72.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3-d8_scratch_512x1024_320k_cityscapes/lraspp_m-v3-d8_scratch_512x1024_320k_cityscapes-20201224_220337.log.json) | +| LRASPP | M-V3s-D8 | 512x1024 | 320000 | 5.3 | 23.64 | V100 | 64.11 | 66.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v3/mobilenet-v3-d8-s_lraspp_4xb4-320k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3s-d8_512x1024_320k_cityscapes/lraspp_m-v3s-d8_512x1024_320k_cityscapes_20201224_223935-61565b34.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3s-d8_512x1024_320k_cityscapes/lraspp_m-v3s-d8_512x1024_320k_cityscapes-20201224_223935.log.json) | +| LRASPP | M-V3s-D8 (scratch) | 512x1024 | 320000 | 5.3 | 24.50 | V100 | 62.74 | 65.01 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v3/mobilenet-v3-d8-scratch-s_lraspp_4xb4-320k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3s-d8_scratch_512x1024_320k_cityscapes/lraspp_m-v3s-d8_scratch_512x1024_320k_cityscapes_20201224_223935-03daeabb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3s-d8_scratch_512x1024_320k_cityscapes/lraspp_m-v3s-d8_scratch_512x1024_320k_cityscapes-20201224_223935.log.json) | + +## Citation + +```bibtex +@inproceedings{Howard_2019_ICCV, + title={Searching for MobileNetV3}, + author={Howard, Andrew and Sandler, Mark and Chu, Grace and Chen, Liang-Chieh and Chen, Bo and Tan, Mingxing and Wang, Weijun and Zhu, Yukun and Pang, Ruoming and Vasudevan, Vijay and Le, Quoc V. and Adam, Hartwig}, + booktitle={The IEEE International Conference on Computer Vision (ICCV)}, + pages={1314-1324}, + month={October}, + year={2019}, + doi={10.1109/ICCV.2019.00140}} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v3/metafile.yaml b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/metafile.yaml new file mode 100644 index 0000000..0351d3b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/metafile.yaml @@ -0,0 +1,109 @@ +Collections: +- Name: LRASPP + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: Searching for MobileNetV3 + URL: https://arxiv.org/abs/1905.02244 + README: configs/mobilenet_v3/README.md + Frameworks: + - PyTorch +Models: +- Name: mobilenet-v3-d8_lraspp_4xb4-320k_cityscapes-512x1024 + In Collection: LRASPP + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 69.54 + mIoU(ms+flip): 70.89 + Config: configs/mobilenet_v3/mobilenet-v3-d8_lraspp_4xb4-320k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - M-V3-D8 + - LRASPP + Training Resources: 4x V100 GPUS + Memory (GB): 8.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3-d8_512x1024_320k_cityscapes/lraspp_m-v3-d8_512x1024_320k_cityscapes_20201224_220337-cfe8fb07.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3-d8_512x1024_320k_cityscapes/lraspp_m-v3-d8_512x1024_320k_cityscapes-20201224_220337.log.json + Paper: + Title: Searching for MobileNetV3 + URL: https://arxiv.org/abs/1905.02244 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v3.py#L15 + Framework: PyTorch +- Name: mobilenet-v3-d8-scratch_lraspp_4xb4-320k_cityscapes-512x1024 + In Collection: LRASPP + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 67.87 + mIoU(ms+flip): 69.78 + Config: configs/mobilenet_v3/mobilenet-v3-d8-scratch_lraspp_4xb4-320k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - M-V3-D8 + - LRASPP + Training Resources: 4x V100 GPUS + Memory (GB): 8.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3-d8_scratch_512x1024_320k_cityscapes/lraspp_m-v3-d8_scratch_512x1024_320k_cityscapes_20201224_220337-9f29cd72.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3-d8_scratch_512x1024_320k_cityscapes/lraspp_m-v3-d8_scratch_512x1024_320k_cityscapes-20201224_220337.log.json + Paper: + Title: Searching for MobileNetV3 + URL: https://arxiv.org/abs/1905.02244 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v3.py#L15 + Framework: PyTorch +- Name: mobilenet-v3-d8-s_lraspp_4xb4-320k_cityscapes-512x1024 + In Collection: LRASPP + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 64.11 + mIoU(ms+flip): 66.42 + Config: configs/mobilenet_v3/mobilenet-v3-d8-s_lraspp_4xb4-320k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - M-V3s-D8 + - LRASPP + Training Resources: 4x V100 GPUS + Memory (GB): 5.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3s-d8_512x1024_320k_cityscapes/lraspp_m-v3s-d8_512x1024_320k_cityscapes_20201224_223935-61565b34.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3s-d8_512x1024_320k_cityscapes/lraspp_m-v3s-d8_512x1024_320k_cityscapes-20201224_223935.log.json + Paper: + Title: Searching for MobileNetV3 + URL: https://arxiv.org/abs/1905.02244 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v3.py#L15 + Framework: PyTorch +- Name: mobilenet-v3-d8-scratch-s_lraspp_4xb4-320k_cityscapes-512x1024 + In Collection: LRASPP + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 62.74 + mIoU(ms+flip): 65.01 + Config: configs/mobilenet_v3/mobilenet-v3-d8-scratch-s_lraspp_4xb4-320k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - M-V3s-D8 + - LRASPP + Training Resources: 4x V100 GPUS + Memory (GB): 5.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3s-d8_scratch_512x1024_320k_cityscapes/lraspp_m-v3s-d8_scratch_512x1024_320k_cityscapes_20201224_223935-03daeabb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/mobilenet_v3/lraspp_m-v3s-d8_scratch_512x1024_320k_cityscapes/lraspp_m-v3s-d8_scratch_512x1024_320k_cityscapes-20201224_223935.log.json + Paper: + Title: Searching for MobileNetV3 + URL: https://arxiv.org/abs/1905.02244 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mobilenet_v3.py#L15 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-s_lraspp_4xb4-320k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-s_lraspp_4xb4-320k_cityscapes-512x1024.py new file mode 100644 index 0000000..bc6322f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-s_lraspp_4xb4-320k_cityscapes-512x1024.py @@ -0,0 +1,23 @@ +_base_ = './mobilenet-v3-d8_lraspp_4xb4-320k_cityscapes-512x1024.py' +norm_cfg = dict(type='SyncBN', eps=0.001, requires_grad=True) +model = dict( + type='EncoderDecoder', + pretrained='open-mmlab://contrib/mobilenet_v3_small', + backbone=dict( + type='MobileNetV3', + arch='small', + out_indices=(0, 1, 12), + norm_cfg=norm_cfg), + decode_head=dict( + type='LRASPPHead', + in_channels=(16, 16, 576), + in_index=(0, 1, 2), + channels=128, + input_transform='multiple_select', + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-scratch-s_lraspp_4xb4-320k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-scratch-s_lraspp_4xb4-320k_cityscapes-512x1024.py new file mode 100644 index 0000000..7260936 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-scratch-s_lraspp_4xb4-320k_cityscapes-512x1024.py @@ -0,0 +1,22 @@ +_base_ = './mobilenet-v3-d8-scratch_lraspp_4xb4-320k_cityscapes-512x1024.py' +norm_cfg = dict(type='SyncBN', eps=0.001, requires_grad=True) +model = dict( + type='EncoderDecoder', + backbone=dict( + type='MobileNetV3', + arch='small', + out_indices=(0, 1, 12), + norm_cfg=norm_cfg), + decode_head=dict( + type='LRASPPHead', + in_channels=(16, 16, 576), + in_index=(0, 1, 2), + channels=128, + input_transform='multiple_select', + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-scratch_lraspp_4xb4-320k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-scratch_lraspp_4xb4-320k_cityscapes-512x1024.py new file mode 100644 index 0000000..8dcbc33 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8-scratch_lraspp_4xb4-320k_cityscapes-512x1024.py @@ -0,0 +1,13 @@ +_base_ = [ + '../_base_/models/lraspp_m-v3-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +# Re-config the data sampler. +model = dict(data_preprocessor=data_preprocessor) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader + +runner = dict(type='IterBasedRunner', max_iters=320000) diff --git a/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8_lraspp_4xb4-320k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8_lraspp_4xb4-320k_cityscapes-512x1024.py new file mode 100644 index 0000000..cd84265 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/mobilenet_v3/mobilenet-v3-d8_lraspp_4xb4-320k_cityscapes-512x1024.py @@ -0,0 +1,16 @@ +_base_ = [ + '../_base_/models/lraspp_m-v3-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://contrib/mobilenet_v3_large') + +# Re-config the data sampler. +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader + +runner = dict(type='IterBasedRunner', max_iters=320000) diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..aadeefa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,160 @@ +_base_ = [ + '../_base_/models/my_bisnetv2_A1_add_A2.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=10, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=10, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=10, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=10, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}]} + + diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..33dce14 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,158 @@ +_base_ = [ + '../_base_/models/my_bisnetv2_A1_add_A2.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py',# TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=13, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=13, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=13, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=13, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..47efa82 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisnetv2_A1_add_A2.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=11, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=11, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=11, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=11, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..f3869f8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisnetv2_A1_add_A2.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..c8529c2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisnetv2_A1_add_A2.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisnetv2_A1_add_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..90e7f0a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,160 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A1.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=10, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=10, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=10, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=10, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}]} + + diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..6d5dd7f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,158 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A1.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py',# TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=13, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=13, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=13, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=13, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..8fcd495 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A1.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=11, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=11, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=11, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=11, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..06e15a6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A1.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..ff0d39b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A1.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + # '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A1_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..09903a8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,160 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A2.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + # '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=10, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=10, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=10, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=10, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=10, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +# optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +# optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}]} + + diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..a1f30dc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,158 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A2.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py',# TODO + # '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=13, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=13, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=13, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=13, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=13, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) + +# TODO +# optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +# optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..2a9ab2a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A2.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + # '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=11, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=11, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=11, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=11, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=11, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +# optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +# optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_dresden-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..c99239c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A2.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + # '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +# optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +# optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..130d44b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/my_bisenetv2/my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,157 @@ +_base_ = [ + '../_base_/models/my_bisenetv2_A2.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', # TODO + # '../_base_/schedules/schedule_300e_val1_check10_SGD.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + type='FCNHead', + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + num_classes=8, + ), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=16, + channels=16, + num_convs=2, + num_classes=8, + in_index=1, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=32, + channels=64, + num_convs=2, + num_classes=8, + in_index=2, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=64, + channels=256, + num_convs=2, + num_classes=8, + in_index=3, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + dict( + type='FCNHead', + in_channels=128, + channels=1024, + num_convs=2, + num_classes=8, + in_index=4, + norm_cfg=dict( + type='BN', + ), + concat_input=False, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + reduction='none', + ), + ), + ], +) +# TODO +# optimizer = dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005) +# optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_bisenetv2_A2_fcn_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}]} diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/README.md b/Seg_All_In_One_MMSeg/configs/nonlocal_net/README.md new file mode 100644 index 0000000..4c3f49f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/README.md @@ -0,0 +1,68 @@ +# NonLocal Net + +> [Non-local Neural Networks](https://arxiv.org/abs/1711.07971) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Both convolutional and recurrent operations are building blocks that process one local neighborhood at a time. In this paper, we present non-local operations as a generic family of building blocks for capturing long-range dependencies. Inspired by the classical non-local means method in computer vision, our non-local operation computes the response at a position as a weighted sum of the features at all positions. This building block can be plugged into many computer vision architectures. On the task of video classification, even without any bells and whistles, our non-local models can compete or outperform current competition winners on both Kinetics and Charades datasets. In static image recognition, our non-local models improve object detection/segmentation and pose estimation on the COCO suite of tasks. Code is available at [this https URL](https://github.com/facebookresearch/video-nonlocal-net). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ----------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| NonLocalNet | R-50-D8 | 512x1024 | 40000 | 7.4 | 2.72 | V100 | 78.24 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x1024_40k_cityscapes/nonlocal_r50-d8_512x1024_40k_cityscapes_20200605_210748-c75e81e3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x1024_40k_cityscapes/nonlocal_r50-d8_512x1024_40k_cityscapes_20200605_210748.log.json) | +| NonLocalNet | R-101-D8 | 512x1024 | 40000 | 10.9 | 1.95 | V100 | 78.66 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x1024_40k_cityscapes/nonlocal_r101-d8_512x1024_40k_cityscapes_20200605_210748-d63729fa.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x1024_40k_cityscapes/nonlocal_r101-d8_512x1024_40k_cityscapes_20200605_210748.log.json) | +| NonLocalNet | R-50-D8 | 769x769 | 40000 | 8.9 | 1.52 | V100 | 78.33 | 79.92 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_769x769_40k_cityscapes/nonlocal_r50-d8_769x769_40k_cityscapes_20200530_045243-82ef6749.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_769x769_40k_cityscapes/nonlocal_r50-d8_769x769_40k_cityscapes_20200530_045243.log.json) | +| NonLocalNet | R-101-D8 | 769x769 | 40000 | 12.8 | 1.05 | V100 | 78.57 | 80.29 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_769x769_40k_cityscapes/nonlocal_r101-d8_769x769_40k_cityscapes_20200530_045348-8fe9a9dc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_769x769_40k_cityscapes/nonlocal_r101-d8_769x769_40k_cityscapes_20200530_045348.log.json) | +| NonLocalNet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 78.01 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x1024_80k_cityscapes/nonlocal_r50-d8_512x1024_80k_cityscapes_20200607_193518-d6839fae.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x1024_80k_cityscapes/nonlocal_r50-d8_512x1024_80k_cityscapes_20200607_193518.log.json) | +| NonLocalNet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 78.93 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x1024_80k_cityscapes/nonlocal_r101-d8_512x1024_80k_cityscapes_20200607_183411-32700183.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x1024_80k_cityscapes/nonlocal_r101-d8_512x1024_80k_cityscapes_20200607_183411.log.json) | +| NonLocalNet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.05 | 80.68 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_769x769_80k_cityscapes/nonlocal_r50-d8_769x769_80k_cityscapes_20200607_193506-1f9792f6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_769x769_80k_cityscapes/nonlocal_r50-d8_769x769_80k_cityscapes_20200607_193506.log.json) | +| NonLocalNet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 79.40 | 80.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_769x769_80k_cityscapes/nonlocal_r101-d8_769x769_80k_cityscapes_20200607_183428-0e1fa4f9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_769x769_80k_cityscapes/nonlocal_r101-d8_769x769_80k_cityscapes_20200607_183428.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ----------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| NonLocalNet | R-50-D8 | 512x512 | 80000 | 9.1 | 21.37 | V100 | 40.75 | 42.05 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_80k_ade20k/nonlocal_r50-d8_512x512_80k_ade20k_20200615_015801-5ae0aa33.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_80k_ade20k/nonlocal_r50-d8_512x512_80k_ade20k_20200615_015801.log.json) | +| NonLocalNet | R-101-D8 | 512x512 | 80000 | 12.6 | 13.97 | V100 | 42.90 | 44.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_80k_ade20k/nonlocal_r101-d8_512x512_80k_ade20k_20200615_015758-24105919.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_80k_ade20k/nonlocal_r101-d8_512x512_80k_ade20k_20200615_015758.log.json) | +| NonLocalNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 42.03 | 43.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_160k_ade20k/nonlocal_r50-d8_512x512_160k_ade20k_20200616_005410-baef45e3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_160k_ade20k/nonlocal_r50-d8_512x512_160k_ade20k_20200616_005410.log.json) | +| NonLocalNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 44.63 | 45.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_160k_ade20k/nonlocal_r101-d8_512x512_160k_ade20k_20210827_221502-7881aa1a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_160k_ade20k/nonlocal_r101-d8_512x512_160k_ade20k_20210827_221502.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ----------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| NonLocalNet | R-50-D8 | 512x512 | 20000 | 6.4 | 21.21 | V100 | 76.20 | 77.12 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_20k_voc12aug/nonlocal_r50-d8_512x512_20k_voc12aug_20200617_222613-07f2a57c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_20k_voc12aug/nonlocal_r50-d8_512x512_20k_voc12aug_20200617_222613.log.json) | +| NonLocalNet | R-101-D8 | 512x512 | 20000 | 9.8 | 14.01 | V100 | 78.15 | 78.86 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_20k_voc12aug/nonlocal_r101-d8_512x512_20k_voc12aug_20200617_222615-948c68ab.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_20k_voc12aug/nonlocal_r101-d8_512x512_20k_voc12aug_20200617_222615.log.json) | +| NonLocalNet | R-50-D8 | 512x512 | 40000 | - | - | V100 | 76.65 | 77.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_40k_voc12aug/nonlocal_r50-d8_512x512_40k_voc12aug_20200614_000028-0139d4a9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_40k_voc12aug/nonlocal_r50-d8_512x512_40k_voc12aug_20200614_000028.log.json) | +| NonLocalNet | R-101-D8 | 512x512 | 40000 | - | - | V100 | 78.27 | 79.12 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net/nonlocal_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_40k_voc12aug/nonlocal_r101-d8_512x512_40k_voc12aug_20200614_000028-7e5ff470.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_40k_voc12aug/nonlocal_r101-d8_512x512_40k_voc12aug_20200614_000028.log.json) | + +## Citation + +```bibtex +@inproceedings{wang2018non, + title={Non-local neural networks}, + author={Wang, Xiaolong and Girshick, Ross and Gupta, Abhinav and He, Kaiming}, + booktitle={Proceedings of the IEEE conference on computer vision and pattern recognition}, + pages={7794--7803}, + year={2018} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/metafile.yaml b/Seg_All_In_One_MMSeg/configs/nonlocal_net/metafile.yaml new file mode 100644 index 0000000..69bd725 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/metafile.yaml @@ -0,0 +1,387 @@ +Collections: +- Name: NonLocalNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + README: configs/nonlocal_net/README.md + Frameworks: + - PyTorch +Models: +- Name: nonlocal_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.24 + Config: configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x1024_40k_cityscapes/nonlocal_r50-d8_512x1024_40k_cityscapes_20200605_210748-c75e81e3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x1024_40k_cityscapes/nonlocal_r50-d8_512x1024_40k_cityscapes_20200605_210748.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.66 + Config: configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x1024_40k_cityscapes/nonlocal_r101-d8_512x1024_40k_cityscapes_20200605_210748-d63729fa.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x1024_40k_cityscapes/nonlocal_r101-d8_512x1024_40k_cityscapes_20200605_210748.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.33 + mIoU(ms+flip): 79.92 + Config: configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_769x769_40k_cityscapes/nonlocal_r50-d8_769x769_40k_cityscapes_20200530_045243-82ef6749.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_769x769_40k_cityscapes/nonlocal_r50-d8_769x769_40k_cityscapes_20200530_045243.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.57 + mIoU(ms+flip): 80.29 + Config: configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_769x769_40k_cityscapes/nonlocal_r101-d8_769x769_40k_cityscapes_20200530_045348-8fe9a9dc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_769x769_40k_cityscapes/nonlocal_r101-d8_769x769_40k_cityscapes_20200530_045348.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.01 + Config: configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x1024_80k_cityscapes/nonlocal_r50-d8_512x1024_80k_cityscapes_20200607_193518-d6839fae.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x1024_80k_cityscapes/nonlocal_r50-d8_512x1024_80k_cityscapes_20200607_193518.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.93 + Config: configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x1024_80k_cityscapes/nonlocal_r101-d8_512x1024_80k_cityscapes_20200607_183411-32700183.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x1024_80k_cityscapes/nonlocal_r101-d8_512x1024_80k_cityscapes_20200607_183411.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.05 + mIoU(ms+flip): 80.68 + Config: configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_769x769_80k_cityscapes/nonlocal_r50-d8_769x769_80k_cityscapes_20200607_193506-1f9792f6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_769x769_80k_cityscapes/nonlocal_r50-d8_769x769_80k_cityscapes_20200607_193506.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.4 + mIoU(ms+flip): 80.85 + Config: configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_769x769_80k_cityscapes/nonlocal_r101-d8_769x769_80k_cityscapes_20200607_183428-0e1fa4f9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_769x769_80k_cityscapes/nonlocal_r101-d8_769x769_80k_cityscapes_20200607_183428.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 40.75 + mIoU(ms+flip): 42.05 + Config: configs/nonlocal_net/nonlocal_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_80k_ade20k/nonlocal_r50-d8_512x512_80k_ade20k_20200615_015801-5ae0aa33.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_80k_ade20k/nonlocal_r50-d8_512x512_80k_ade20k_20200615_015801.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.9 + mIoU(ms+flip): 44.27 + Config: configs/nonlocal_net/nonlocal_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_80k_ade20k/nonlocal_r101-d8_512x512_80k_ade20k_20200615_015758-24105919.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_80k_ade20k/nonlocal_r101-d8_512x512_80k_ade20k_20200615_015758.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.03 + mIoU(ms+flip): 43.04 + Config: configs/nonlocal_net/nonlocal_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_160k_ade20k/nonlocal_r50-d8_512x512_160k_ade20k_20200616_005410-baef45e3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_160k_ade20k/nonlocal_r50-d8_512x512_160k_ade20k_20200616_005410.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.63 + mIoU(ms+flip): 45.79 + Config: configs/nonlocal_net/nonlocal_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_160k_ade20k/nonlocal_r101-d8_512x512_160k_ade20k_20210827_221502-7881aa1a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_160k_ade20k/nonlocal_r101-d8_512x512_160k_ade20k_20210827_221502.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.2 + mIoU(ms+flip): 77.12 + Config: configs/nonlocal_net/nonlocal_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_20k_voc12aug/nonlocal_r50-d8_512x512_20k_voc12aug_20200617_222613-07f2a57c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_20k_voc12aug/nonlocal_r50-d8_512x512_20k_voc12aug_20200617_222613.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 78.15 + mIoU(ms+flip): 78.86 + Config: configs/nonlocal_net/nonlocal_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_20k_voc12aug/nonlocal_r101-d8_512x512_20k_voc12aug_20200617_222615-948c68ab.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_20k_voc12aug/nonlocal_r101-d8_512x512_20k_voc12aug_20200617_222615.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.65 + mIoU(ms+flip): 77.47 + Config: configs/nonlocal_net/nonlocal_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_40k_voc12aug/nonlocal_r50-d8_512x512_40k_voc12aug_20200614_000028-0139d4a9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r50-d8_512x512_40k_voc12aug/nonlocal_r50-d8_512x512_40k_voc12aug_20200614_000028.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch +- Name: nonlocal_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: NonLocalNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 78.27 + mIoU(ms+flip): 79.12 + Config: configs/nonlocal_net/nonlocal_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - NonLocalNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_40k_voc12aug/nonlocal_r101-d8_512x512_40k_voc12aug_20200614_000028-7e5ff470.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/nonlocal_net/nonlocal_r101-d8_512x512_40k_voc12aug/nonlocal_r101-d8_512x512_40k_voc12aug_20200614_000028.log.json + Paper: + Title: Non-local Neural Networks + URL: https://arxiv.org/abs/1711.07971 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/nl_head.py#L10 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..5fcf7bc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './nonlocal_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..ee984c2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './nonlocal_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..aca80d6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './nonlocal_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..8a7aeea --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './nonlocal_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..0cdb3ca --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './nonlocal_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..a7cacea --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './nonlocal_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..ec47544 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './nonlocal_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..ca79f6f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './nonlocal_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..f4d5fd2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/nonlocal_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..17423f2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/nonlocal_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..7cc752c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/nonlocal_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..f855a81 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/nonlocal_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..848be4a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/nonlocal_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..cd840a0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/nonlocal_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..0efb9d0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/nonlocal_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..52783bc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/nonlocal_net/nonlocal_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/nonlocal_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/README.md b/Seg_All_In_One_MMSeg/configs/ocrnet/README.md new file mode 100644 index 0000000..628a3b1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/README.md @@ -0,0 +1,89 @@ +# OCRNet + +> [Object-Contextual Representations for Semantic Segmentation](https://arxiv.org/abs/1909.11065) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +In this paper, we address the problem of semantic segmentation and focus on the context aggregation strategy for robust segmentation. Our motivation is that the label of a pixel is the category of the object that the pixel belongs to. We present a simple yet effective approach, object-contextual representations, characterizing a pixel by exploiting the representation of the corresponding object class. First, we construct object regions based on a feature map supervised by the ground-truth segmentation, and then compute the object region representations. Second, we compute the representation similarity between each pixel and each object region, and augment the representation of each pixel with an object contextual representation, which is a weighted aggregation of all the object region representations according to their similarities with the pixel. We empirically demonstrate that the proposed approach achieves competitive performance on six challenging semantic segmentation benchmarks: Cityscapes, ADE20K, LIP, PASCAL VOC 2012, PASCAL-Context and COCO-Stuff. Notably, we achieved the \\nth{2} place on the Cityscapes leader-board with a single model. + + + +
+ +
+ +## Results and models + +### Cityscapes + +#### HRNet backbone + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| OCRNet | HRNetV2p-W18-Small | 512x1024 | 40000 | 3.5 | 10.45 | A100 | 76.61 | 78.01 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024_20230227_145026-6c052a14.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024_20230227_145026.json) | +| OCRNet | HRNetV2p-W18 | 512x1024 | 40000 | 4.7 | 7.50 | V100 | 77.72 | 79.49 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_40k_cityscapes/ocrnet_hr18_512x1024_40k_cityscapes_20200601_033320-401c5bdd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_40k_cityscapes/ocrnet_hr18_512x1024_40k_cityscapes_20200601_033320.log.json) | +| OCRNet | HRNetV2p-W48 | 512x1024 | 40000 | 8 | 4.22 | V100 | 80.58 | 81.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr48_4xb2-40k_cityscapes-512x1024.pyy) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_40k_cityscapes/ocrnet_hr48_512x1024_40k_cityscapes_20200601_033336-55b32491.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_40k_cityscapes/ocrnet_hr48_512x1024_40k_cityscapes_20200601_033336.log.json) | +| OCRNet | HRNetV2p-W18-Small | 512x1024 | 80000 | - | - | V100 | 77.16 | 78.66 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18s_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x1024_80k_cityscapes/ocrnet_hr18s_512x1024_80k_cityscapes_20200601_222735-55979e63.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x1024_80k_cityscapes/ocrnet_hr18s_512x1024_80k_cityscapes_20200601_222735.log.json) | +| OCRNet | HRNetV2p-W18 | 512x1024 | 80000 | - | - | V100 | 78.57 | 80.46 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_80k_cityscapes/ocrnet_hr18_512x1024_80k_cityscapes_20200614_230521-c2e1dd4a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_80k_cityscapes/ocrnet_hr18_512x1024_80k_cityscapes_20200614_230521.log.json) | +| OCRNet | HRNetV2p-W48 | 512x1024 | 80000 | - | - | V100 | 80.70 | 81.87 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr48_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_80k_cityscapes/ocrnet_hr48_512x1024_80k_cityscapes_20200601_222752-9076bcdf.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_80k_cityscapes/ocrnet_hr48_512x1024_80k_cityscapes_20200601_222752.log.json) | +| OCRNet | HRNetV2p-W18-Small | 512x1024 | 160000 | - | - | V100 | 78.45 | 79.97 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18s_4xb2-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x1024_160k_cityscapes/ocrnet_hr18s_512x1024_160k_cityscapes_20200602_191005-f4a7af28.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x1024_160k_cityscapes/ocrnet_hr18s_512x1024_160k_cityscapes_20200602_191005.log.json) | +| OCRNet | HRNetV2p-W18 | 512x1024 | 160000 | - | - | V100 | 79.47 | 80.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18_4xb2-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_160k_cityscapes/ocrnet_hr18_512x1024_160k_cityscapes_20200602_191001-b9172d0c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_160k_cityscapes/ocrnet_hr18_512x1024_160k_cityscapes_20200602_191001.log.json) | +| OCRNet | HRNetV2p-W48 | 512x1024 | 160000 | - | - | V100 | 81.35 | 82.70 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr48_4xb2-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_160k_cityscapes/ocrnet_hr48_512x1024_160k_cityscapes_20200602_191037-dfbf1b0c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_160k_cityscapes/ocrnet_hr48_512x1024_160k_cityscapes_20200602_191037.log.json) | + +#### ResNet backbone + +| Method | Backbone | Crop Size | Batch Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ---------- | ------- | -------- | -------------- | ------ | ----- | ------------: | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| OCRNet | R-101-D8 | 512x1024 | 8 | 40000 | - | - | V100 | 80.09 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_40k_b8_cityscapes/ocrnet_r101-d8_512x1024_40k_b8_cityscapes_20200717_110721-02ac0f13.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_40k_b8_cityscapes/ocrnet_r101-d8_512x1024_40k_b8_cityscapes_20200717_110721.log.json) | +| OCRNet | R-101-D8 | 512x1024 | 16 | 40000 | 8.8 | 3.02 | V100 | 80.30 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_r101-d8_8xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_40k_b16_cityscapes/ocrnet_r101-d8_512x1024_40k_b16_cityscapes_20200723_193726-db500f80.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_40k_b16_cityscapes/ocrnet_r101-d8_512x1024_40k_b16_cityscapes_20200723_193726.log.json) | +| OCRNet | R-101-D8 | 512x1024 | 16 | 80000 | 8.8 | 3.02 | V100 | 80.81 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_r101-d8_8xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_80k_b16_cityscapes/ocrnet_r101-d8_512x1024_80k_b16_cityscapes_20200723_192421-78688424.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_80k_b16_cityscapes/ocrnet_r101-d8_512x1024_80k_b16_cityscapes_20200723_192421.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| OCRNet | HRNetV2p-W18-Small | 512x512 | 80000 | 6.7 | 28.98 | V100 | 35.06 | 35.80 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18s_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_80k_ade20k/ocrnet_hr18s_512x512_80k_ade20k_20200615_055600-e80b62af.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_80k_ade20k/ocrnet_hr18s_512x512_80k_ade20k_20200615_055600.log.json) | +| OCRNet | HRNetV2p-W18 | 512x512 | 80000 | 7.9 | 18.93 | V100 | 37.79 | 39.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_80k_ade20k/ocrnet_hr18_512x512_80k_ade20k_20200615_053157-d173d83b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_80k_ade20k/ocrnet_hr18_512x512_80k_ade20k_20200615_053157.log.json) | +| OCRNet | HRNetV2p-W48 | 512x512 | 80000 | 11.2 | 16.99 | V100 | 43.00 | 44.30 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr48_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_80k_ade20k/ocrnet_hr48_512x512_80k_ade20k_20200615_021518-d168c2d1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_80k_ade20k/ocrnet_hr48_512x512_80k_ade20k_20200615_021518.log.json) | +| OCRNet | HRNetV2p-W18-Small | 512x512 | 160000 | - | - | V100 | 37.19 | 38.40 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18s_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_160k_ade20k/ocrnet_hr18s_512x512_160k_ade20k_20200615_184505-8e913058.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_160k_ade20k/ocrnet_hr18s_512x512_160k_ade20k_20200615_184505.log.json) | +| OCRNet | HRNetV2p-W18 | 512x512 | 160000 | - | - | V100 | 39.32 | 40.80 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_160k_ade20k/ocrnet_hr18_512x512_160k_ade20k_20200615_200940-d8fcd9d1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_160k_ade20k/ocrnet_hr18_512x512_160k_ade20k_20200615_200940.log.json) | +| OCRNet | HRNetV2p-W48 | 512x512 | 160000 | - | - | V100 | 43.25 | 44.88 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr48_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_160k_ade20k/ocrnet_hr48_512x512_160k_ade20k_20200615_184705-a073726d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_160k_ade20k/ocrnet_hr48_512x512_160k_ade20k_20200615_184705.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | ------------------ | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| OCRNet | HRNetV2p-W18-Small | 512x512 | 20000 | 3.5 | 31.55 | V100 | 71.70 | 73.84 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18s_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_20k_voc12aug/ocrnet_hr18s_512x512_20k_voc12aug_20200617_233913-02b04fcb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_20k_voc12aug/ocrnet_hr18s_512x512_20k_voc12aug_20200617_233913.log.json) | +| OCRNet | HRNetV2p-W18 | 512x512 | 20000 | 4.7 | 19.91 | V100 | 74.75 | 77.11 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_20k_voc12aug/ocrnet_hr18_512x512_20k_voc12aug_20200617_233932-8954cbb7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_20k_voc12aug/ocrnet_hr18_512x512_20k_voc12aug_20200617_233932.log.json) | +| OCRNet | HRNetV2p-W48 | 512x512 | 20000 | 8.1 | 17.83 | V100 | 77.72 | 79.87 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr48_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_20k_voc12aug/ocrnet_hr48_512x512_20k_voc12aug_20200617_233932-9e82080a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_20k_voc12aug/ocrnet_hr48_512x512_20k_voc12aug_20200617_233932.log.json) | +| OCRNet | HRNetV2p-W18-Small | 512x512 | 40000 | - | - | V100 | 72.76 | 74.60 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18s_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_40k_voc12aug/ocrnet_hr18s_512x512_40k_voc12aug_20200614_002025-42b587ac.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_40k_voc12aug/ocrnet_hr18s_512x512_40k_voc12aug_20200614_002025.log.json) | +| OCRNet | HRNetV2p-W18 | 512x512 | 40000 | - | - | V100 | 74.98 | 77.40 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr18_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_40k_voc12aug/ocrnet_hr18_512x512_40k_voc12aug_20200614_015958-714302be.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_40k_voc12aug/ocrnet_hr18_512x512_40k_voc12aug_20200614_015958.log.json) | +| OCRNet | HRNetV2p-W48 | 512x512 | 40000 | - | - | V100 | 77.14 | 79.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet/ocrnet_hr48_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_40k_voc12aug/ocrnet_hr48_512x512_40k_voc12aug_20200614_015958-255bc5ce.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_40k_voc12aug/ocrnet_hr48_512x512_40k_voc12aug_20200614_015958.log.json) | + +## Citation + +```bibtex +@article{YuanW18, + title={Ocnet: Object context network for scene parsing}, + author={Yuhui Yuan and Jingdong Wang}, + booktitle={arXiv preprint arXiv:1809.00916}, + year={2018} +} + +@article{YuanCW20, + title={Object-Contextual Representations for Semantic Segmentation}, + author={Yuhui Yuan and Xilin Chen and Jingdong Wang}, + booktitle={ECCV}, + year={2020} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/ocrnet/metafile.yaml new file mode 100644 index 0000000..5467feb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/metafile.yaml @@ -0,0 +1,577 @@ +Collections: +- Name: OCRNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - '# HRNet backbone' + - '# ResNet backbone' + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + README: configs/ocrnet/README.md + Frameworks: + - PyTorch +Models: +- Name: ocrnet_hr18s_4xb2-40k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 76.61 + mIoU(ms+flip): 78.01 + Config: configs/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W18-Small + - OCRNet + Training Resources: 4x A100 GPUS + Memory (GB): 3.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024_20230227_145026-6c052a14.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024_20230227_145026.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18_4xb2-40k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 77.72 + mIoU(ms+flip): 79.49 + Config: configs/ocrnet/ocrnet_hr18_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W18 + - OCRNet + Training Resources: 4x V100 GPUS + Memory (GB): 4.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_40k_cityscapes/ocrnet_hr18_512x1024_40k_cityscapes_20200601_033320-401c5bdd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_40k_cityscapes/ocrnet_hr18_512x1024_40k_cityscapes_20200601_033320.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr48_4xb2-40k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 80.58 + mIoU(ms+flip): 81.79 + Config: configs/ocrnet/ocrnet_hr48_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W48 + - OCRNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_40k_cityscapes/ocrnet_hr48_512x1024_40k_cityscapes_20200601_033336-55b32491.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_40k_cityscapes/ocrnet_hr48_512x1024_40k_cityscapes_20200601_033336.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18s_4xb2-80k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 77.16 + mIoU(ms+flip): 78.66 + Config: configs/ocrnet/ocrnet_hr18s_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W18-Small + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x1024_80k_cityscapes/ocrnet_hr18s_512x1024_80k_cityscapes_20200601_222735-55979e63.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x1024_80k_cityscapes/ocrnet_hr18s_512x1024_80k_cityscapes_20200601_222735.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18_4xb2-80k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 78.57 + mIoU(ms+flip): 80.46 + Config: configs/ocrnet/ocrnet_hr18_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W18 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_80k_cityscapes/ocrnet_hr18_512x1024_80k_cityscapes_20200614_230521-c2e1dd4a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_80k_cityscapes/ocrnet_hr18_512x1024_80k_cityscapes_20200614_230521.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr48_4xb2-80k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 80.7 + mIoU(ms+flip): 81.87 + Config: configs/ocrnet/ocrnet_hr48_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W48 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_80k_cityscapes/ocrnet_hr48_512x1024_80k_cityscapes_20200601_222752-9076bcdf.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_80k_cityscapes/ocrnet_hr48_512x1024_80k_cityscapes_20200601_222752.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18s_4xb2-160k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 78.45 + mIoU(ms+flip): 79.97 + Config: configs/ocrnet/ocrnet_hr18s_4xb2-160k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W18-Small + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x1024_160k_cityscapes/ocrnet_hr18s_512x1024_160k_cityscapes_20200602_191005-f4a7af28.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x1024_160k_cityscapes/ocrnet_hr18s_512x1024_160k_cityscapes_20200602_191005.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18_4xb2-160k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 79.47 + mIoU(ms+flip): 80.91 + Config: configs/ocrnet/ocrnet_hr18_4xb2-160k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W18 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_160k_cityscapes/ocrnet_hr18_512x1024_160k_cityscapes_20200602_191001-b9172d0c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x1024_160k_cityscapes/ocrnet_hr18_512x1024_160k_cityscapes_20200602_191001.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr48_4xb2-160k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# HRNet backbone' + Metrics: + mIoU: 81.35 + mIoU(ms+flip): 82.7 + Config: configs/ocrnet/ocrnet_hr48_4xb2-160k_cityscapes-512x1024.py + Metadata: + Training Data: '# HRNet backbone' + Batch Size: 8 + Architecture: + - HRNetV2p-W48 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_160k_cityscapes/ocrnet_hr48_512x1024_160k_cityscapes_20200602_191037-dfbf1b0c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x1024_160k_cityscapes/ocrnet_hr48_512x1024_160k_cityscapes_20200602_191037.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# ResNet backbone' + Metrics: + mIoU: 80.09 + Config: configs/ocrnet/ocrnet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: '# ResNet backbone' + Batch Size: 8 + Architecture: + - R-101-D8 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_40k_b8_cityscapes/ocrnet_r101-d8_512x1024_40k_b8_cityscapes_20200717_110721-02ac0f13.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_40k_b8_cityscapes/ocrnet_r101-d8_512x1024_40k_b8_cityscapes_20200717_110721.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_r101-d8_8xb2-40k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# ResNet backbone' + Metrics: + mIoU: 80.3 + Config: configs/ocrnet/ocrnet_r101-d8_8xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: '# ResNet backbone' + Batch Size: 16 + Architecture: + - R-101-D8 + - OCRNet + Training Resources: 8x V100 GPUS + Memory (GB): 8.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_40k_b16_cityscapes/ocrnet_r101-d8_512x1024_40k_b16_cityscapes_20200723_193726-db500f80.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_40k_b16_cityscapes/ocrnet_r101-d8_512x1024_40k_b16_cityscapes_20200723_193726.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_r101-d8_8xb2-80k_cityscapes-512x1024 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: '# ResNet backbone' + Metrics: + mIoU: 80.81 + Config: configs/ocrnet/ocrnet_r101-d8_8xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: '# ResNet backbone' + Batch Size: 16 + Architecture: + - R-101-D8 + - OCRNet + Training Resources: 8x V100 GPUS + Memory (GB): 8.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_80k_b16_cityscapes/ocrnet_r101-d8_512x1024_80k_b16_cityscapes_20200723_192421-78688424.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_r101-d8_512x1024_80k_b16_cityscapes/ocrnet_r101-d8_512x1024_80k_b16_cityscapes_20200723_192421.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18s_4xb4-80k_ade20k-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 35.06 + mIoU(ms+flip): 35.8 + Config: configs/ocrnet/ocrnet_hr18s_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - OCRNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_80k_ade20k/ocrnet_hr18s_512x512_80k_ade20k_20200615_055600-e80b62af.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_80k_ade20k/ocrnet_hr18s_512x512_80k_ade20k_20200615_055600.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18_4xb4-80k_ade20k-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 37.79 + mIoU(ms+flip): 39.16 + Config: configs/ocrnet/ocrnet_hr18_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - OCRNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_80k_ade20k/ocrnet_hr18_512x512_80k_ade20k_20200615_053157-d173d83b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_80k_ade20k/ocrnet_hr18_512x512_80k_ade20k_20200615_053157.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr48_4xb4-80k_ade20k-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.0 + mIoU(ms+flip): 44.3 + Config: configs/ocrnet/ocrnet_hr48_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - OCRNet + Training Resources: 4x V100 GPUS + Memory (GB): 11.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_80k_ade20k/ocrnet_hr48_512x512_80k_ade20k_20200615_021518-d168c2d1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_80k_ade20k/ocrnet_hr48_512x512_80k_ade20k_20200615_021518.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18s_4xb4-80k_ade20k-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 37.19 + mIoU(ms+flip): 38.4 + Config: configs/ocrnet/ocrnet_hr18s_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_160k_ade20k/ocrnet_hr18s_512x512_160k_ade20k_20200615_184505-8e913058.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_160k_ade20k/ocrnet_hr18s_512x512_160k_ade20k_20200615_184505.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18_4xb4-80k_ade20k-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 39.32 + mIoU(ms+flip): 40.8 + Config: configs/ocrnet/ocrnet_hr18_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_160k_ade20k/ocrnet_hr18_512x512_160k_ade20k_20200615_200940-d8fcd9d1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_160k_ade20k/ocrnet_hr18_512x512_160k_ade20k_20200615_200940.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr48_4xb4-160k_ade20k-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.25 + mIoU(ms+flip): 44.88 + Config: configs/ocrnet/ocrnet_hr48_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_160k_ade20k/ocrnet_hr48_512x512_160k_ade20k_20200615_184705-a073726d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_160k_ade20k/ocrnet_hr48_512x512_160k_ade20k_20200615_184705.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18s_4xb4-20k_voc12aug-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 71.7 + mIoU(ms+flip): 73.84 + Config: configs/ocrnet/ocrnet_hr18s_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - OCRNet + Training Resources: 4x V100 GPUS + Memory (GB): 3.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_20k_voc12aug/ocrnet_hr18s_512x512_20k_voc12aug_20200617_233913-02b04fcb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_20k_voc12aug/ocrnet_hr18s_512x512_20k_voc12aug_20200617_233913.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18_4xb4-20k_voc12aug-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 74.75 + mIoU(ms+flip): 77.11 + Config: configs/ocrnet/ocrnet_hr18_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - OCRNet + Training Resources: 4x V100 GPUS + Memory (GB): 4.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_20k_voc12aug/ocrnet_hr18_512x512_20k_voc12aug_20200617_233932-8954cbb7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_20k_voc12aug/ocrnet_hr18_512x512_20k_voc12aug_20200617_233932.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr48_4xb4-20k_voc12aug-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.72 + mIoU(ms+flip): 79.87 + Config: configs/ocrnet/ocrnet_hr48_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - OCRNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_20k_voc12aug/ocrnet_hr48_512x512_20k_voc12aug_20200617_233932-9e82080a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_20k_voc12aug/ocrnet_hr48_512x512_20k_voc12aug_20200617_233932.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18s_4xb4-40k_voc12aug-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 72.76 + mIoU(ms+flip): 74.6 + Config: configs/ocrnet/ocrnet_hr18s_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W18-Small + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_40k_voc12aug/ocrnet_hr18s_512x512_40k_voc12aug_20200614_002025-42b587ac.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18s_512x512_40k_voc12aug/ocrnet_hr18s_512x512_40k_voc12aug_20200614_002025.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr18_4xb4-40k_voc12aug-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 74.98 + mIoU(ms+flip): 77.4 + Config: configs/ocrnet/ocrnet_hr18_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W18 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_40k_voc12aug/ocrnet_hr18_512x512_40k_voc12aug_20200614_015958-714302be.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr18_512x512_40k_voc12aug/ocrnet_hr18_512x512_40k_voc12aug_20200614_015958.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch +- Name: ocrnet_hr48_4xb4-40k_voc12aug-512x512 + In Collection: OCRNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.14 + mIoU(ms+flip): 79.71 + Config: configs/ocrnet/ocrnet_hr48_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - HRNetV2p-W48 + - OCRNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_40k_voc12aug/ocrnet_hr48_512x512_40k_voc12aug_20200614_015958-255bc5ce.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/ocrnet/ocrnet_hr48_512x512_40k_voc12aug/ocrnet_hr48_512x512_40k_voc12aug_20200614_015958.log.json + Paper: + Title: Object-Contextual Representations for Semantic Segmentation + URL: https://arxiv.org/abs/1909.11065 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/ocr_head.py#L86 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..659217c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-160k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/ocrnet_hr18.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..d401c4b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/ocrnet_hr18.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..44426a2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/ocrnet_hr18.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..353005b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,39 @@ +_base_ = [ + '../_base_/models/ocrnet_hr18.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=[ + dict( + type='FCNHead', + in_channels=[18, 36, 72, 144], + channels=sum([18, 36, 72, 144]), + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + kernel_size=1, + num_convs=1, + concat_input=False, + dropout_ratio=-1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[18, 36, 72, 144], + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + channels=512, + ocr_channels=256, + dropout_ratio=-1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..c696c21 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,40 @@ +_base_ = [ + '../_base_/models/ocrnet_hr18.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=[ + dict( + type='FCNHead', + in_channels=[18, 36, 72, 144], + channels=sum([18, 36, 72, 144]), + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + kernel_size=1, + num_convs=1, + concat_input=False, + dropout_ratio=-1, + num_classes=21, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[18, 36, 72, 144], + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + channels=512, + ocr_channels=256, + dropout_ratio=-1, + num_classes=21, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..c6b69ea --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,40 @@ +_base_ = [ + '../_base_/models/ocrnet_hr18.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=[ + dict( + type='FCNHead', + in_channels=[18, 36, 72, 144], + channels=sum([18, 36, 72, 144]), + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + kernel_size=1, + num_convs=1, + concat_input=False, + dropout_ratio=-1, + num_classes=21, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[18, 36, 72, 144], + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + channels=512, + ocr_channels=256, + dropout_ratio=-1, + num_classes=21, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..ceca8df --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,39 @@ +_base_ = [ + '../_base_/models/ocrnet_hr18.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=[ + dict( + type='FCNHead', + in_channels=[18, 36, 72, 144], + channels=sum([18, 36, 72, 144]), + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + kernel_size=1, + num_convs=1, + concat_input=False, + dropout_ratio=-1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[18, 36, 72, 144], + in_index=(0, 1, 2, 3), + input_transform='resize_concat', + channels=512, + ocr_channels=256, + dropout_ratio=-1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..c5388fb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-160k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './ocrnet_hr18_4xb2-160k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..2335f3b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './ocrnet_hr18_4xb2-40k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..b2d1a8f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './ocrnet_hr18_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..fabf582 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = './ocrnet_hr18_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..0eca655 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,9 @@ +_base_ = './ocrnet_hr18_4xb4-20k_voc12aug-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..13b02b9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,9 @@ +_base_ = './ocrnet_hr18_4xb4-40k_voc12aug-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..60c79c2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr18s_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = './ocrnet_hr18_4xb4-80k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w18_small', + backbone=dict( + extra=dict( + stage1=dict(num_blocks=(2, )), + stage2=dict(num_blocks=(2, 2)), + stage3=dict(num_modules=3, num_blocks=(2, 2, 2)), + stage4=dict(num_modules=2, num_blocks=(2, 2, 2, 2))))) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..184d38d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-160k_cityscapes-512x1024.py @@ -0,0 +1,39 @@ +_base_ = './ocrnet_hr18_4xb2-160k_cityscapes-512x1024.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=[ + dict( + type='FCNHead', + in_channels=[48, 96, 192, 384], + channels=sum([48, 96, 192, 384]), + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + kernel_size=1, + num_convs=1, + norm_cfg=norm_cfg, + concat_input=False, + dropout_ratio=-1, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[48, 96, 192, 384], + channels=512, + ocr_channels=256, + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + norm_cfg=norm_cfg, + dropout_ratio=-1, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..7025ee9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,39 @@ +_base_ = './ocrnet_hr18_4xb2-40k_cityscapes-512x1024.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=[ + dict( + type='FCNHead', + in_channels=[48, 96, 192, 384], + channels=sum([48, 96, 192, 384]), + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + kernel_size=1, + num_convs=1, + norm_cfg=norm_cfg, + concat_input=False, + dropout_ratio=-1, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[48, 96, 192, 384], + channels=512, + ocr_channels=256, + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + norm_cfg=norm_cfg, + dropout_ratio=-1, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..9c68a15 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,39 @@ +_base_ = './ocrnet_hr18_4xb2-80k_cityscapes-512x1024.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=[ + dict( + type='FCNHead', + in_channels=[48, 96, 192, 384], + channels=sum([48, 96, 192, 384]), + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + kernel_size=1, + num_convs=1, + norm_cfg=norm_cfg, + concat_input=False, + dropout_ratio=-1, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[48, 96, 192, 384], + channels=512, + ocr_channels=256, + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + norm_cfg=norm_cfg, + dropout_ratio=-1, + num_classes=19, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..e74976c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,39 @@ +_base_ = './ocrnet_hr18_4xb4-160k_ade20k-512x512.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=[ + dict( + type='FCNHead', + in_channels=[48, 96, 192, 384], + channels=sum([48, 96, 192, 384]), + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + kernel_size=1, + num_convs=1, + norm_cfg=norm_cfg, + concat_input=False, + dropout_ratio=-1, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[48, 96, 192, 384], + channels=512, + ocr_channels=256, + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + norm_cfg=norm_cfg, + dropout_ratio=-1, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..f015b92 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,39 @@ +_base_ = './ocrnet_hr18_4xb4-20k_voc12aug-512x512.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=[ + dict( + type='FCNHead', + in_channels=[48, 96, 192, 384], + channels=sum([48, 96, 192, 384]), + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + kernel_size=1, + num_convs=1, + norm_cfg=norm_cfg, + concat_input=False, + dropout_ratio=-1, + num_classes=21, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[48, 96, 192, 384], + channels=512, + ocr_channels=256, + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + norm_cfg=norm_cfg, + dropout_ratio=-1, + num_classes=21, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..baafa38 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,39 @@ +_base_ = './ocrnet_hr18_4xb4-40k_voc12aug-512x512.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=[ + dict( + type='FCNHead', + in_channels=[48, 96, 192, 384], + channels=sum([48, 96, 192, 384]), + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + kernel_size=1, + num_convs=1, + norm_cfg=norm_cfg, + concat_input=False, + dropout_ratio=-1, + num_classes=21, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[48, 96, 192, 384], + channels=512, + ocr_channels=256, + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + norm_cfg=norm_cfg, + dropout_ratio=-1, + num_classes=21, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..85514b9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_hr48_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,39 @@ +_base_ = './ocrnet_hr18_4xb4-80k_ade20k-512x512.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w48', + backbone=dict( + extra=dict( + stage2=dict(num_channels=(48, 96)), + stage3=dict(num_channels=(48, 96, 192)), + stage4=dict(num_channels=(48, 96, 192, 384)))), + decode_head=[ + dict( + type='FCNHead', + in_channels=[48, 96, 192, 384], + channels=sum([48, 96, 192, 384]), + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + kernel_size=1, + num_convs=1, + norm_cfg=norm_cfg, + concat_input=False, + dropout_ratio=-1, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='OCRHead', + in_channels=[48, 96, 192, 384], + channels=512, + ocr_channels=256, + input_transform='resize_concat', + in_index=(0, 1, 2, 3), + norm_cfg=norm_cfg, + dropout_ratio=-1, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ]) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..a94597b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/ocrnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_8xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_8xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..88e5ad0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_8xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,21 @@ +_base_ = [ + '../_base_/models/ocrnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101)) +optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +param_scheduler = [ + dict( + type='PolyLR', + eta_min=2e-4, + power=0.9, + begin=0, + end=40000, + by_epoch=False) +] diff --git a/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_8xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_8xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..a3b4209 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/ocrnet/ocrnet_r101-d8_8xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,21 @@ +_base_ = [ + '../_base_/models/ocrnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101)) +optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +param_scheduler = [ + dict( + type='PolyLR', + eta_min=2e-4, + power=0.9, + begin=0, + end=40000, + by_epoch=False) +] diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/README.md b/Seg_All_In_One_MMSeg/configs/pidnet/README.md new file mode 100644 index 0000000..e23efbd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/README.md @@ -0,0 +1,50 @@ +# PIDNet + +> [PIDNet: A Real-time Semantic Segmentation Network Inspired from PID Controller](https://arxiv.org/pdf/2206.02066.pdf) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Two-branch network architecture has shown its efficiency and effectiveness for real-time semantic segmentation tasks. However, direct fusion of low-level details and high-level semantics will lead to a phenomenon that the detailed features are easily overwhelmed by surrounding contextual information, namely overshoot in this paper, which limits the improvement of the accuracy of existed two-branch models. In this paper, we bridge a connection between Convolutional Neural Network (CNN) and Proportional-IntegralDerivative (PID) controller and reveal that the two-branch network is nothing but a Proportional-Integral (PI) controller, which inherently suffers from the similar overshoot issue. To alleviate this issue, we propose a novel threebranch network architecture: PIDNet, which possesses three branches to parse the detailed, context and boundary information (derivative of semantics), respectively, and employs boundary attention to guide the fusion of detailed and context branches in final stage. The family of PIDNets achieve the best trade-off between inference speed and accuracy and their test accuracy surpasses all the existed models with similar inference speed on Cityscapes, CamVid and COCO-Stuff datasets. Especially, PIDNet-S achieves 78.6% mIOU with inference speed of 93.2 FPS on Cityscapes test set and 80.1% mIOU with speed of 153.7 FPS on CamVid test set. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PIDNet | PIDNet-S | 1024x1024 | 120000 | 3.38 | 80.82 | A100 | 78.74 | 80.87 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes/pidnet-s_2xb6-120k_1024x1024-cityscapes_20230302_191700-bb8e3bcc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes/pidnet-s_2xb6-120k_1024x1024-cityscapes_20230302_191700.json) | +| PIDNet | PIDNet-M | 1024x1024 | 120000 | 5.14 | 71.98 | A100 | 80.22 | 82.05 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes/pidnet-m_2xb6-120k_1024x1024-cityscapes_20230301_143452-f9bcdbf3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes/pidnet-m_2xb6-120k_1024x1024-cityscapes_20230301_143452.json) | +| PIDNet | PIDNet-L | 1024x1024 | 120000 | 5.83 | 60.06 | A100 | 80.89 | 82.37 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes/pidnet-l_2xb6-120k_1024x1024-cityscapes_20230303_114514-0783ca6b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes/pidnet-l_2xb6-120k_1024x1024-cityscapes_20230303_114514.json) | + +## Notes + +The pretrained weights in config files are converted from [the official repo](https://github.com/XuJiacong/PIDNet#models). + +## Citation + +```bibtex +@misc{xu2022pidnet, + title={PIDNet: A Real-time Semantic Segmentation Network Inspired from PID Controller}, + author={Jiacong Xu and Zixiang Xiong and Shankar P. Bhattacharyya}, + year={2022}, + eprint={2206.02066}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/pidnet/metafile.yaml new file mode 100644 index 0000000..51b514a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/metafile.yaml @@ -0,0 +1,85 @@ +Collections: +- Name: PIDNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: 'PIDNet: A Real-time Semantic Segmentation Network Inspired from PID Controller' + URL: https://arxiv.org/pdf/2206.02066.pdf + README: configs/pidnet/README.md + Frameworks: + - PyTorch +Models: +- Name: pidnet-s_2xb6-120k_1024x1024-cityscapes + In Collection: PIDNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.74 + mIoU(ms+flip): 80.87 + Config: configs/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes.py + Metadata: + Training Data: Cityscapes + Batch Size: 12 + Architecture: + - PIDNet-S + - PIDNet + Training Resources: 2x A100 GPUS + Memory (GB): 3.38 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes/pidnet-s_2xb6-120k_1024x1024-cityscapes_20230302_191700-bb8e3bcc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes/pidnet-s_2xb6-120k_1024x1024-cityscapes_20230302_191700.json + Paper: + Title: 'PIDNet: A Real-time Semantic Segmentation Network Inspired from PID Controller' + URL: https://arxiv.org/pdf/2206.02066.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/backbones/pidnet.py + Framework: PyTorch +- Name: pidnet-m_2xb6-120k_1024x1024-cityscapes + In Collection: PIDNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.22 + mIoU(ms+flip): 82.05 + Config: configs/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes.py + Metadata: + Training Data: Cityscapes + Batch Size: 12 + Architecture: + - PIDNet-M + - PIDNet + Training Resources: 2x A100 GPUS + Memory (GB): 5.14 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes/pidnet-m_2xb6-120k_1024x1024-cityscapes_20230301_143452-f9bcdbf3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes/pidnet-m_2xb6-120k_1024x1024-cityscapes_20230301_143452.json + Paper: + Title: 'PIDNet: A Real-time Semantic Segmentation Network Inspired from PID Controller' + URL: https://arxiv.org/pdf/2206.02066.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/backbones/pidnet.py + Framework: PyTorch +- Name: pidnet-l_2xb6-120k_1024x1024-cityscapes + In Collection: PIDNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.89 + mIoU(ms+flip): 82.37 + Config: configs/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes.py + Metadata: + Training Data: Cityscapes + Batch Size: 12 + Architecture: + - PIDNet-L + - PIDNet + Training Resources: 2x A100 GPUS + Memory (GB): 5.83 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes/pidnet-l_2xb6-120k_1024x1024-cityscapes_20230303_114514-0783ca6b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes/pidnet-l_2xb6-120k_1024x1024-cityscapes_20230303_114514.json + Paper: + Title: 'PIDNet: A Real-time Semantic Segmentation Network Inspired from PID Controller' + URL: https://arxiv.org/pdf/2206.02066.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/backbones/pidnet.py + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..9aaa82d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=112, + num_stem_blocks=3, + num_branch_blocks=4, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-l.pth', + ), + ), + decode_head=dict( + num_classes=10, + in_channels=256, + channels=256, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..98435ee --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=112, + num_stem_blocks=3, + num_branch_blocks=4, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-l.pth', + ), + ), + decode_head=dict( + num_classes=13, + in_channels=256, + channels=256, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..93e6545 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=112, + num_stem_blocks=3, + num_branch_blocks=4, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-l.pth', + ), + ), + decode_head=dict( + num_classes=11, + in_channels=256, + channels=256, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..d13b29b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=112, + num_stem_blocks=3, + num_branch_blocks=4, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-l.pth', + ), + ), + decode_head=dict( + num_classes=8, + in_channels=256, + channels=256, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..13fb1c2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_large_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=112, + num_stem_blocks=3, + num_branch_blocks=4, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-l.pth', + ), + ), + decode_head=dict( + num_classes=8, + in_channels=256, + channels=256, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..6e2ef0e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-m.pth', + ), + ), + decode_head=dict( + num_classes=10, + in_channels=256, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..0db9722 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-m.pth', + ), + ), + decode_head=dict( + num_classes=13, + in_channels=256, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..4e3da92 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-m.pth', + ), + ), + decode_head=dict( + num_classes=11, + in_channels=256, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..7dfdc2f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-m.pth', + ), + ), + decode_head=dict( + num_classes=8, + in_channels=256, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..87b46c2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_middle_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=64, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-m.pth', + ), + ), + decode_head=dict( + num_classes=8, + in_channels=256, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..998fd23 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-s.pth', + ), + ), + decode_head=dict( + num_classes=10, + in_channels=128, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..69c9b1f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(1920, 1080), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-s.pth', + ), + ), + decode_head=dict( + num_classes=13, + in_channels=128, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..4caba03 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-s.pth', + ), + ), + decode_head=dict( + num_classes=11, + in_channels=128, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..64da275 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-s.pth', + ), + ), + decode_head=dict( + num_classes=8, + in_channels=128, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..55af52d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/my_pidnet_small_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,162 @@ +_base_ = [ + '../_base_/models/pidnet.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +train_pipeline = [ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), +] + +train_dataloader = dict( + dataset=dict( + pipeline=[ + dict( + type='LoadImageFromFile', + ), + dict( + type='LoadAnnotations', + ), + dict( + type='RandomResize', + scale=(512, 512), + ratio_range=(0.5, 2.0), + ), + dict( + type='RandomCrop', + crop_size=(256, 256), + cat_max_ratio=0.75, + ), + dict( + type='RandomFlip', + prob=0.5, + ), + dict( + type='PhotoMetricDistortion', + ), + dict( + type='GenerateEdge', + edge_width=4, + ), + dict( + type='PackSegInputs', + ), + ], + ), +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/pidnet-s.pth', + ), + ), + decode_head=dict( + num_classes=8, + in_channels=128, + channels=128, + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes.py b/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes.py new file mode 100644 index 0000000..1955c91 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-l_2xb6-120k_1024x1024-cityscapes.py @@ -0,0 +1,10 @@ +_base_ = './pidnet-s_2xb6-120k_1024x1024-cityscapes.py' +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/pidnet/pidnet-l_imagenet1k_20230306-67889109.pth' # noqa +model = dict( + backbone=dict( + channels=64, + ppm_channels=112, + num_stem_blocks=3, + num_branch_blocks=4, + init_cfg=dict(checkpoint=checkpoint_file)), + decode_head=dict(in_channels=256, channels=256)) diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes.py b/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes.py new file mode 100644 index 0000000..38a69c1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-m_2xb6-120k_1024x1024-cityscapes.py @@ -0,0 +1,5 @@ +_base_ = './pidnet-s_2xb6-120k_1024x1024-cityscapes.py' +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/pidnet/pidnet-m_imagenet1k_20230306-39893c52.pth' # noqa +model = dict( + backbone=dict(channels=64, init_cfg=dict(checkpoint=checkpoint_file)), + decode_head=dict(in_channels=256)) diff --git a/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes.py b/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes.py new file mode 100644 index 0000000..f70ca42 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pidnet/pidnet-s_2xb6-120k_1024x1024-cityscapes.py @@ -0,0 +1,113 @@ +_base_ = [ + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py' +] + +# The class_weight is borrowed from https://github.com/openseg-group/OCNet.pytorch/issues/14 # noqa +# Licensed under the MIT License +class_weight = [ + 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, 1.0489, 0.8786, + 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, 1.0865, 1.0955, 1.0865, 1.1529, + 1.0507 +] +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/pidnet/pidnet-s_imagenet1k_20230306-715e6273.pth' # noqa +crop_size = (1024, 1024) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='PIDNet', + in_channels=3, + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + align_corners=False, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU', inplace=True), + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file)), + decode_head=dict( + type='PIDHead', + in_channels=128, + channels=128, + num_classes=19, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU', inplace=True), + align_corners=True, + loss_decode=[ + dict( + type='CrossEntropyLoss', + use_sigmoid=False, + class_weight=class_weight, + loss_weight=0.4), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + class_weight=class_weight, + loss_weight=1.0), + dict(type='BoundaryLoss', loss_weight=20.0), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + class_weight=class_weight, + loss_weight=1.0) + ]), + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='GenerateEdge', edge_width=4), + dict(type='PackSegInputs') +] +train_dataloader = dict(batch_size=6, dataset=dict(pipeline=train_pipeline)) + +iters = 120000 +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0, + power=0.9, + begin=0, + end=iters, + by_epoch=False) +] +# training schedule for 120k +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=iters, val_interval=iters // 10) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict( + type='CheckpointHook', by_epoch=False, interval=iters // 10), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) + +randomness = dict(seed=304) diff --git a/Seg_All_In_One_MMSeg/configs/point_rend/README.md b/Seg_All_In_One_MMSeg/configs/point_rend/README.md new file mode 100644 index 0000000..487d3bc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/point_rend/README.md @@ -0,0 +1,51 @@ +# PointRend + +> [PointRend: Image Segmentation as Rendering](https://arxiv.org/abs/1912.08193) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +We present a new method for efficient high-quality image segmentation of objects and scenes. By analogizing classical computer graphics methods for efficient rendering with over- and undersampling challenges faced in pixel labeling tasks, we develop a unique perspective of image segmentation as a rendering problem. From this vantage, we present the PointRend (Point-based Rendering) neural network module: a module that performs point-based segmentation predictions at adaptively selected locations based on an iterative subdivision algorithm. PointRend can be flexibly applied to both instance and semantic segmentation tasks by building on top of existing state-of-the-art models. While many concrete implementations of the general idea are possible, we show that a simple design already achieves excellent results. Qualitatively, PointRend outputs crisp object boundaries in regions that are over-smoothed by previous methods. Quantitatively, PointRend yields significant gains on COCO and Cityscapes, for both instance and semantic segmentation. PointRend's efficiency enables output resolutions that are otherwise impractical in terms of memory or computation compared to existing approaches. Code has been made available at [this https URL](https://github.com/facebookresearch/detectron2/tree/main/projects/PointRend). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PointRend | R-50 | 512x1024 | 80000 | 3.1 | 8.48 | V100 | 76.47 | 78.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r50_512x1024_80k_cityscapes/pointrend_r50_512x1024_80k_cityscapes_20200711_015821-bb1ff523.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r50_512x1024_80k_cityscapes/pointrend_r50_512x1024_80k_cityscapes-20200715_214714.log.json) | +| PointRend | R-101 | 512x1024 | 80000 | 4.2 | 7.00 | V100 | 78.30 | 79.97 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/point_rend/pointrend_r101_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r101_512x1024_80k_cityscapes/pointrend_r101_512x1024_80k_cityscapes_20200711_170850-d0ca84be.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r101_512x1024_80k_cityscapes/pointrend_r101_512x1024_80k_cityscapes-20200715_214824.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| PointRend | R-50 | 512x512 | 160000 | 5.1 | 17.31 | V100 | 37.64 | 39.17 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/point_rend/pointrend_r50_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r50_512x512_160k_ade20k/pointrend_r50_512x512_160k_ade20k_20200807_232644-ac3febf2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r50_512x512_160k_ade20k/pointrend_r50_512x512_160k_ade20k-20200807_232644.log.json) | +| PointRend | R-101 | 512x512 | 160000 | 6.1 | 15.50 | V100 | 40.02 | 41.60 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/point_rend/pointrend_r101_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r101_512x512_160k_ade20k/pointrend_r101_512x512_160k_ade20k_20200808_030852-8834902a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r101_512x512_160k_ade20k/pointrend_r101_512x512_160k_ade20k-20200808_030852.log.json) | + +## Citation + +```bibtex +@inproceedings{kirillov2020pointrend, + title={Pointrend: Image segmentation as rendering}, + author={Kirillov, Alexander and Wu, Yuxin and He, Kaiming and Girshick, Ross}, + booktitle={Proceedings of the IEEE/CVF conference on computer vision and pattern recognition}, + pages={9799--9808}, + year={2020} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/point_rend/metafile.yaml b/Seg_All_In_One_MMSeg/configs/point_rend/metafile.yaml new file mode 100644 index 0000000..064717c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/point_rend/metafile.yaml @@ -0,0 +1,110 @@ +Collections: +- Name: PointRend + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + Paper: + Title: 'PointRend: Image Segmentation as Rendering' + URL: https://arxiv.org/abs/1912.08193 + README: configs/point_rend/README.md + Frameworks: + - PyTorch +Models: +- Name: pointrend_r50_4xb2-80k_cityscapes-512x1024 + In Collection: PointRend + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.47 + mIoU(ms+flip): 78.13 + Config: configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50 + - PointRend + Training Resources: 4x V100 GPUS + Memory (GB): 3.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r50_512x1024_80k_cityscapes/pointrend_r50_512x1024_80k_cityscapes_20200711_015821-bb1ff523.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r50_512x1024_80k_cityscapes/pointrend_r50_512x1024_80k_cityscapes-20200715_214714.log.json + Paper: + Title: 'PointRend: Image Segmentation as Rendering' + URL: https://arxiv.org/abs/1912.08193 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/point_head.py#L36 + Framework: PyTorch +- Name: pointrend_r101_4xb2-80k_cityscapes-512x1024 + In Collection: PointRend + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.3 + mIoU(ms+flip): 79.97 + Config: configs/point_rend/pointrend_r101_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101 + - PointRend + Training Resources: 4x V100 GPUS + Memory (GB): 4.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r101_512x1024_80k_cityscapes/pointrend_r101_512x1024_80k_cityscapes_20200711_170850-d0ca84be.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r101_512x1024_80k_cityscapes/pointrend_r101_512x1024_80k_cityscapes-20200715_214824.log.json + Paper: + Title: 'PointRend: Image Segmentation as Rendering' + URL: https://arxiv.org/abs/1912.08193 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/point_head.py#L36 + Framework: PyTorch +- Name: pointrend_r50_4xb4-160k_ade20k-512x512 + In Collection: PointRend + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 37.64 + mIoU(ms+flip): 39.17 + Config: configs/point_rend/pointrend_r50_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50 + - PointRend + Training Resources: 4x V100 GPUS + Memory (GB): 5.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r50_512x512_160k_ade20k/pointrend_r50_512x512_160k_ade20k_20200807_232644-ac3febf2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r50_512x512_160k_ade20k/pointrend_r50_512x512_160k_ade20k-20200807_232644.log.json + Paper: + Title: 'PointRend: Image Segmentation as Rendering' + URL: https://arxiv.org/abs/1912.08193 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/point_head.py#L36 + Framework: PyTorch +- Name: pointrend_r101_4xb4-160k_ade20k-512x512 + In Collection: PointRend + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 40.02 + mIoU(ms+flip): 41.6 + Config: configs/point_rend/pointrend_r101_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101 + - PointRend + Training Resources: 4x V100 GPUS + Memory (GB): 6.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r101_512x512_160k_ade20k/pointrend_r101_512x512_160k_ade20k_20200808_030852-8834902a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/point_rend/pointrend_r101_512x512_160k_ade20k/pointrend_r101_512x512_160k_ade20k-20200808_030852.log.json + Paper: + Title: 'PointRend: Image Segmentation as Rendering' + URL: https://arxiv.org/abs/1912.08193 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/point_head.py#L36 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r101_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r101_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..ca2a19a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r101_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './pointrend_r50_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r101_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r101_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..6729d3b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r101_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pointrend_r50_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..fb005d8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r50_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,18 @@ +_base_ = [ + '../_base_/models/pointrend_r50.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=200), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=200, + end=80000, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r50_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r50_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..d350fa6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/point_rend/pointrend_r50_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,46 @@ +_base_ = [ + '../_base_/models/pointrend_r50.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=[ + dict( + type='FPNHead', + in_channels=[256, 256, 256, 256], + in_index=[0, 1, 2, 3], + feature_strides=[4, 8, 16, 32], + channels=128, + dropout_ratio=-1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + dict( + type='PointHead', + in_channels=[256], + in_index=[0], + channels=256, + num_fcs=3, + coarse_pred_each_layer=True, + dropout_ratio=-1, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + ]) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=200), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=200, + end=160000, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/poolformer/README.md b/Seg_All_In_One_MMSeg/configs/poolformer/README.md new file mode 100644 index 0000000..e6e2eac --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/poolformer/README.md @@ -0,0 +1,65 @@ +# PoolFormer + +> [MetaFormer is Actually What You Need for Vision](https://arxiv.org/abs/2111.11418) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Transformers have shown great potential in computer vision tasks. A common belief is their attention-based token mixer module contributes most to their competence. However, recent works show the attention-based module in transformers can be replaced by spatial MLPs and the resulted models still perform quite well. Based on this observation, we hypothesize that the general architecture of the transformers, instead of the specific token mixer module, is more essential to the model's performance. To verify this, we deliberately replace the attention module in transformers with an embarrassingly simple spatial pooling operator to conduct only the most basic token mixing. Surprisingly, we observe that the derived model, termed as PoolFormer, achieves competitive performance on multiple computer vision tasks. For example, on ImageNet-1K, PoolFormer achieves 82.1% top-1 accuracy, surpassing well-tuned vision transformer/MLP-like baselines DeiT-B/ResMLP-B24 by 0.3%/1.1% accuracy with 35%/52% fewer parameters and 48%/60% fewer MACs. The effectiveness of PoolFormer verifies our hypothesis and urges us to initiate the concept of "MetaFormer", a general architecture abstracted from transformers without specifying the token mixer. Based on the extensive experiments, we argue that MetaFormer is the key player in achieving superior results for recent transformer and MLP-like models on vision tasks. This work calls for more future research dedicated to improving MetaFormer instead of focusing on the token mixer modules. Additionally, our proposed PoolFormer could serve as a starting baseline for future MetaFormer architecture design. Code is available at [this https URL](https://github.com/sail-sg/poolformer) + + + +
+ +
+ +## Citation + +```bibtex +@inproceedings{yu2022metaformer, + title={Metaformer is actually what you need for vision}, + author={Yu, Weihao and Luo, Mi and Zhou, Pan and Si, Chenyang and Zhou, Yichen and Wang, Xinchao and Feng, Jiashi and Yan, Shuicheng}, + booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition}, + pages={10819--10829}, + year={2022} +} +``` + +### Usage + +- PoolFormer backbone needs to install [MMClassification](https://github.com/open-mmlab/mmclassification) first, which has abundant backbones for downstream tasks. + +```shell +pip install "mmpretrain>=1.0.0rc7" +``` + +- The pretrained models could also be downloaded from [PoolFormer config of MMClassification](https://github.com/open-mmlab/mmclassification/tree/master/configs/poolformer). + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | pretrain | Batch Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | mIoU\* | mIoU\*(ms+flip) | config | download | +| ------ | -------------- | --------- | ----------- | ---------- | ------- | -------- | -------------- | ------ | ----- | ------------: | ------ | --------------: | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FPN | PoolFormer-S12 | 512x512 | ImageNet-1K | 32 | 40000 | 4.17 | 23.48 | V100 | 36.68 | - | 37.07 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/poolformer/fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s12_8x4_512x512_40k_ade20k/fpn_poolformer_s12_8x4_512x512_40k_ade20k_20220501_115154-b5aa2f49.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s12_8x4_512x512_40k_ade20k/fpn_poolformer_s12_8x4_512x512_40k_ade20k_20220501_115154.log.json) | +| FPN | PoolFormer-S24 | 512x512 | ImageNet-1K | 32 | 40000 | 5.47 | 15.74 | V100 | 40.12 | - | 40.36 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/poolformer/fpn_poolformer_s24_8xb4-40k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s24_8x4_512x512_40k_ade20k/fpn_poolformer_s24_8x4_512x512_40k_ade20k_20220503_222049-394a7cf7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s24_8x4_512x512_40k_ade20k/fpn_poolformer_s24_8x4_512x512_40k_ade20k_20220503_222049.log.json) | +| FPN | PoolFormer-S36 | 512x512 | ImageNet-1K | 32 | 40000 | 6.77 | 11.34 | V100 | 41.61 | - | 41.81 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/poolformer/fpn_poolformer_s36_8xb4-40k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s36_8x4_512x512_40k_ade20k/fpn_poolformer_s36_8x4_512x512_40k_ade20k_20220501_151122-b47e607d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s36_8x4_512x512_40k_ade20k/fpn_poolformer_s36_8x4_512x512_40k_ade20k_20220501_151122.log.json) | +| FPN | PoolFormer-M36 | 512x512 | ImageNet-1K | 32 | 40000 | 8.59 | 8.97 | V100 | 41.95 | - | 42.35 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/poolformer/fpn_poolformer_m36_8xb4-40k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_m36_8x4_512x512_40k_ade20k/fpn_poolformer_m36_8x4_512x512_40k_ade20k_20220501_164230-3dc83921.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_m36_8x4_512x512_40k_ade20k/fpn_poolformer_m36_8x4_512x512_40k_ade20k_20220501_164230.log.json) | +| FPN | PoolFormer-M48 | 512x512 | ImageNet-1K | 32 | 40000 | 10.48 | 6.69 | V100 | 42.43 | - | 42.76 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/poolformer/fpn_poolformer_m48_8xb4-40k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_m48_8x4_512x512_40k_ade20k/fpn_poolformer_m48_8x4_512x512_40k_ade20k_20220504_003923-64168d3b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_m48_8x4_512x512_40k_ade20k/fpn_poolformer_m48_8x4_512x512_40k_ade20k_20220504_003923.log.json) | + +Note: + +- We replace `AlignedResize` in original PoolFormer implementation to `Resize + ResizeToMultiple`. + +- `mIoU` with * is collected when `Resize + ResizeToMultiple` is adopted in `test_pipeline`, so do `mIoU` in logs. + +- The Test Time Augmentation i.e., "ms+flip" in MMSegmentation v1.x is developing, stay tuned! diff --git a/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_m36_8xb4-40k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_m36_8xb4-40k_ade20k-512x512.py new file mode 100644 index 0000000..4100eb9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_m36_8xb4-40k_ade20k-512x512.py @@ -0,0 +1,11 @@ +_base_ = './fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py' +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/poolformer/poolformer-m36_3rdparty_32xb128_in1k_20220414-c55e0949.pth' # noqa + +# model settings +model = dict( + backbone=dict( + arch='m36', + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + neck=dict(in_channels=[96, 192, 384, 768])) diff --git a/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_m48_8xb4-40k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_m48_8xb4-40k_ade20k-512x512.py new file mode 100644 index 0000000..cfc49cc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_m48_8xb4-40k_ade20k-512x512.py @@ -0,0 +1,11 @@ +_base_ = './fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py' +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/poolformer/poolformer-m48_3rdparty_32xb128_in1k_20220414-9378f3eb.pth' # noqa + +# model settings +model = dict( + backbone=dict( + arch='m48', + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.')), + neck=dict(in_channels=[96, 192, 384, 768])) diff --git a/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py new file mode 100644 index 0000000..c0b1531 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py @@ -0,0 +1,91 @@ +_base_ = [ + '../_base_/models/fpn_poolformer_s12.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] + +# dataset settings +dataset_type = 'ADE20KDataset' +data_root = 'data/ade/ADEChallengeData2016' +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + dict(type='ResizeToMultiple', size_divisor=32), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] + +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type='RepeatDataset', + times=50, + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', + seg_map_path='annotations/training'), + pipeline=train_pipeline))) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator + +# model settings +model = dict( + data_preprocessor=data_preprocessor, + neck=dict(in_channels=[64, 128, 320, 512]), + decode_head=dict(num_classes=150)) + +# optimizer +# optimizer = dict(_delete_=True, type='AdamW', lr=0.0002, weight_decay=0.0001) +# optimizer_config = dict() +# # learning policy +# lr_config = dict(policy='poly', power=0.9, min_lr=0.0, by_epoch=False) +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict(type='AdamW', lr=0.0002, weight_decay=0.0001)) +param_scheduler = [ + dict( + type='PolyLR', + power=0.9, + begin=0, + end=40000, + eta_min=0.0, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s24_8xb4-40k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s24_8xb4-40k_ade20k-512x512.py new file mode 100644 index 0000000..1f9d24c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s24_8xb4-40k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = './fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py' +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/poolformer/poolformer-s24_3rdparty_32xb128_in1k_20220414-d7055904.pth' # noqa +# model settings +model = dict( + backbone=dict( + arch='s24', + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.'))) diff --git a/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s36_8x4_512x512_40k_ade20k.py b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s36_8x4_512x512_40k_ade20k.py new file mode 100644 index 0000000..231dcf6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/poolformer/fpn_poolformer_s36_8x4_512x512_40k_ade20k.py @@ -0,0 +1,10 @@ +_base_ = './fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py' +checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/poolformer/poolformer-s36_3rdparty_32xb128_in1k_20220414-d78ff3e8.pth' # noqa + +# model settings +model = dict( + backbone=dict( + arch='s36', + init_cfg=dict( + type='Pretrained', checkpoint=checkpoint_file, + prefix='backbone.'))) diff --git a/Seg_All_In_One_MMSeg/configs/poolformer/metafile.yaml b/Seg_All_In_One_MMSeg/configs/poolformer/metafile.yaml new file mode 100644 index 0000000..12f402b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/poolformer/metafile.yaml @@ -0,0 +1,116 @@ +Models: +- Name: fpn_poolformer_s12_8xb4-40k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 36.68 + Config: configs/poolformer/fpn_poolformer_s12_8xb4-40k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - PoolFormer-S12 + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 4.17 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s12_8x4_512x512_40k_ade20k/fpn_poolformer_s12_8x4_512x512_40k_ade20k_20220501_115154-b5aa2f49.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s12_8x4_512x512_40k_ade20k/fpn_poolformer_s12_8x4_512x512_40k_ade20k_20220501_115154.log.json + Paper: + Title: MetaFormer is Actually What You Need for Vision + URL: https://arxiv.org/abs/2111.11418 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.23.0/mmcls/models/backbones/poolformer.py#L198 + Framework: PyTorch +- Name: fpn_poolformer_s24_8xb4-40k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 40.12 + Config: configs/poolformer/fpn_poolformer_s24_8xb4-40k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - PoolFormer-S24 + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 5.47 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s24_8x4_512x512_40k_ade20k/fpn_poolformer_s24_8x4_512x512_40k_ade20k_20220503_222049-394a7cf7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s24_8x4_512x512_40k_ade20k/fpn_poolformer_s24_8x4_512x512_40k_ade20k_20220503_222049.log.json + Paper: + Title: MetaFormer is Actually What You Need for Vision + URL: https://arxiv.org/abs/2111.11418 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.23.0/mmcls/models/backbones/poolformer.py#L198 + Framework: PyTorch +- Name: fpn_poolformer_s36_8xb4-40k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.61 + Config: configs/poolformer/fpn_poolformer_s36_8xb4-40k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - PoolFormer-S36 + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 6.77 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s36_8x4_512x512_40k_ade20k/fpn_poolformer_s36_8x4_512x512_40k_ade20k_20220501_151122-b47e607d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_s36_8x4_512x512_40k_ade20k/fpn_poolformer_s36_8x4_512x512_40k_ade20k_20220501_151122.log.json + Paper: + Title: MetaFormer is Actually What You Need for Vision + URL: https://arxiv.org/abs/2111.11418 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.23.0/mmcls/models/backbones/poolformer.py#L198 + Framework: PyTorch +- Name: fpn_poolformer_m36_8xb4-40k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.95 + Config: configs/poolformer/fpn_poolformer_m36_8xb4-40k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - PoolFormer-M36 + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 8.59 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_m36_8x4_512x512_40k_ade20k/fpn_poolformer_m36_8x4_512x512_40k_ade20k_20220501_164230-3dc83921.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_m36_8x4_512x512_40k_ade20k/fpn_poolformer_m36_8x4_512x512_40k_ade20k_20220501_164230.log.json + Paper: + Title: MetaFormer is Actually What You Need for Vision + URL: https://arxiv.org/abs/2111.11418 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.23.0/mmcls/models/backbones/poolformer.py#L198 + Framework: PyTorch +- Name: fpn_poolformer_m48_8xb4-40k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.43 + Config: configs/poolformer/fpn_poolformer_m48_8xb4-40k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - PoolFormer-M48 + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 10.48 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_m48_8x4_512x512_40k_ade20k/fpn_poolformer_m48_8x4_512x512_40k_ade20k_20220504_003923-64168d3b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/poolformer/fpn_poolformer_m48_8x4_512x512_40k_ade20k/fpn_poolformer_m48_8x4_512x512_40k_ade20k_20220504_003923.log.json + Paper: + Title: MetaFormer is Actually What You Need for Vision + URL: https://arxiv.org/abs/2111.11418 + Code: https://github.com/open-mmlab/mmclassification/blob/v0.23.0/mmcls/models/backbones/poolformer.py#L198 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/psanet/README.md b/Seg_All_In_One_MMSeg/configs/psanet/README.md new file mode 100644 index 0000000..1f5680f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/README.md @@ -0,0 +1,68 @@ +# PSANet + +> [PSANet: Point-wise Spatial Attention Network for Scene Parsing](https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +We notice information flow in convolutional neural networksis restricted inside local neighborhood regions due to the physical de-sign of convolutional filters, which limits the overall understanding ofcomplex scenes. In this paper, we propose thepoint-wise spatial atten-tion network(PSANet) to relax the local neighborhood constraint. Eachposition on the feature map is connected to all the other ones througha self-adaptively learned attention mask. Moreover, information propa-gation in bi-direction for scene parsing is enabled. Information at otherpositions can be collected to help the prediction of the current positionand vice versa, information at the current position can be distributedto assist the prediction of other ones. Our proposed approach achievestop performance on various competitive scene parsing datasets, includ-ing ADE20K, PASCAL VOC 2012 and Cityscapes, demonstrating itseffectiveness and generality. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSANet | R-50-D8 | 512x1024 | 40000 | 7 | 3.17 | V100 | 77.63 | 79.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x1024_40k_cityscapes/psanet_r50-d8_512x1024_40k_cityscapes_20200606_103117-99fac37c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x1024_40k_cityscapes/psanet_r50-d8_512x1024_40k_cityscapes_20200606_103117.log.json) | +| PSANet | R-101-D8 | 512x1024 | 40000 | 10.5 | 2.20 | V100 | 79.14 | 80.19 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x1024_40k_cityscapes/psanet_r101-d8_512x1024_40k_cityscapes_20200606_001418-27b9cfa7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x1024_40k_cityscapes/psanet_r101-d8_512x1024_40k_cityscapes_20200606_001418.log.json) | +| PSANet | R-50-D8 | 769x769 | 40000 | 7.9 | 1.40 | V100 | 77.99 | 79.64 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_769x769_40k_cityscapes/psanet_r50-d8_769x769_40k_cityscapes_20200530_033717-d5365506.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_769x769_40k_cityscapes/psanet_r50-d8_769x769_40k_cityscapes_20200530_033717.log.json) | +| PSANet | R-101-D8 | 769x769 | 40000 | 11.9 | 0.98 | V100 | 78.43 | 80.26 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_769x769_40k_cityscapes/psanet_r101-d8_769x769_40k_cityscapes_20200530_035107-997da1e6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_769x769_40k_cityscapes/psanet_r101-d8_769x769_40k_cityscapes_20200530_035107.log.json) | +| PSANet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 77.24 | 78.69 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x1024_80k_cityscapes/psanet_r50-d8_512x1024_80k_cityscapes_20200606_161842-ab60a24f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x1024_80k_cityscapes/psanet_r50-d8_512x1024_80k_cityscapes_20200606_161842.log.json) | +| PSANet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 79.31 | 80.53 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x1024_80k_cityscapes/psanet_r101-d8_512x1024_80k_cityscapes_20200606_161823-0f73a169.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x1024_80k_cityscapes/psanet_r101-d8_512x1024_80k_cityscapes_20200606_161823.log.json) | +| PSANet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.31 | 80.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_769x769_80k_cityscapes/psanet_r50-d8_769x769_80k_cityscapes_20200606_225134-fe42f49e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_769x769_80k_cityscapes/psanet_r50-d8_769x769_80k_cityscapes_20200606_225134.log.json) | +| PSANet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 79.69 | 80.89 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_769x769_80k_cityscapes/psanet_r101-d8_769x769_80k_cityscapes_20200606_214550-7665827b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_769x769_80k_cityscapes/psanet_r101-d8_769x769_80k_cityscapes_20200606_214550.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSANet | R-50-D8 | 512x512 | 80000 | 9 | 18.91 | V100 | 41.14 | 41.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_80k_ade20k/psanet_r50-d8_512x512_80k_ade20k_20200614_144141-835e4b97.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_80k_ade20k/psanet_r50-d8_512x512_80k_ade20k_20200614_144141.log.json) | +| PSANet | R-101-D8 | 512x512 | 80000 | 12.5 | 13.13 | V100 | 43.80 | 44.75 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_80k_ade20k/psanet_r101-d8_512x512_80k_ade20k_20200614_185117-1fab60d4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_80k_ade20k/psanet_r101-d8_512x512_80k_ade20k_20200614_185117.log.json) | +| PSANet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 41.67 | 42.95 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_160k_ade20k/psanet_r50-d8_512x512_160k_ade20k_20200615_161258-148077dd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_160k_ade20k/psanet_r50-d8_512x512_160k_ade20k_20200615_161258.log.json) | +| PSANet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 43.74 | 45.38 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_160k_ade20k/psanet_r101-d8_512x512_160k_ade20k_20200615_161537-dbfa564c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_160k_ade20k/psanet_r101-d8_512x512_160k_ade20k_20200615_161537.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSANet | R-50-D8 | 512x512 | 20000 | 6.9 | 18.24 | V100 | 76.39 | 77.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_20k_voc12aug/psanet_r50-d8_512x512_20k_voc12aug_20200617_102413-2f1bbaa1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_20k_voc12aug/psanet_r50-d8_512x512_20k_voc12aug_20200617_102413.log.json) | +| PSANet | R-101-D8 | 512x512 | 20000 | 10.4 | 12.63 | V100 | 77.91 | 79.30 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_20k_voc12aug/psanet_r101-d8_512x512_20k_voc12aug_20200617_110624-946fef11.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_20k_voc12aug/psanet_r101-d8_512x512_20k_voc12aug_20200617_110624.log.json) | +| PSANet | R-50-D8 | 512x512 | 40000 | - | - | V100 | 76.30 | 77.35 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_40k_voc12aug/psanet_r50-d8_512x512_40k_voc12aug_20200613_161946-f596afb5.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_40k_voc12aug/psanet_r50-d8_512x512_40k_voc12aug_20200613_161946.log.json) | +| PSANet | R-101-D8 | 512x512 | 40000 | - | - | V100 | 77.73 | 79.05 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet/psanet_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_40k_voc12aug/psanet_r101-d8_512x512_40k_voc12aug_20200613_161946-1f560f9e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_40k_voc12aug/psanet_r101-d8_512x512_40k_voc12aug_20200613_161946.log.json) | + +## Citation + +```bibtex +@inproceedings{zhao2018psanet, + title={Psanet: Point-wise spatial attention network for scene parsing}, + author={Zhao, Hengshuang and Zhang, Yi and Liu, Shu and Shi, Jianping and Change Loy, Chen and Lin, Dahua and Jia, Jiaya}, + booktitle={Proceedings of the European Conference on Computer Vision (ECCV)}, + pages={267--283}, + year={2018} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/psanet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/psanet/metafile.yaml new file mode 100644 index 0000000..3fbe6f6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/metafile.yaml @@ -0,0 +1,391 @@ +Collections: +- Name: PSANet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + README: configs/psanet/README.md + Frameworks: + - PyTorch +Models: +- Name: psanet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.63 + mIoU(ms+flip): 79.04 + Config: configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - PSANet + Training Resources: 4x V100 GPUS + Memory (GB): 7.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x1024_40k_cityscapes/psanet_r50-d8_512x1024_40k_cityscapes_20200606_103117-99fac37c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x1024_40k_cityscapes/psanet_r50-d8_512x1024_40k_cityscapes_20200606_103117.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.14 + mIoU(ms+flip): 80.19 + Config: configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSANet + Training Resources: 4x V100 GPUS + Memory (GB): 10.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x1024_40k_cityscapes/psanet_r101-d8_512x1024_40k_cityscapes_20200606_001418-27b9cfa7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x1024_40k_cityscapes/psanet_r101-d8_512x1024_40k_cityscapes_20200606_001418.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.99 + mIoU(ms+flip): 79.64 + Config: configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - PSANet + Training Resources: 4x V100 GPUS + Memory (GB): 7.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_769x769_40k_cityscapes/psanet_r50-d8_769x769_40k_cityscapes_20200530_033717-d5365506.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_769x769_40k_cityscapes/psanet_r50-d8_769x769_40k_cityscapes_20200530_033717.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.43 + mIoU(ms+flip): 80.26 + Config: configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSANet + Training Resources: 4x V100 GPUS + Memory (GB): 11.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_769x769_40k_cityscapes/psanet_r101-d8_769x769_40k_cityscapes_20200530_035107-997da1e6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_769x769_40k_cityscapes/psanet_r101-d8_769x769_40k_cityscapes_20200530_035107.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.24 + mIoU(ms+flip): 78.69 + Config: configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - PSANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x1024_80k_cityscapes/psanet_r50-d8_512x1024_80k_cityscapes_20200606_161842-ab60a24f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x1024_80k_cityscapes/psanet_r50-d8_512x1024_80k_cityscapes_20200606_161842.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.31 + mIoU(ms+flip): 80.53 + Config: configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x1024_80k_cityscapes/psanet_r101-d8_512x1024_80k_cityscapes_20200606_161823-0f73a169.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x1024_80k_cityscapes/psanet_r101-d8_512x1024_80k_cityscapes_20200606_161823.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.31 + mIoU(ms+flip): 80.91 + Config: configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - PSANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_769x769_80k_cityscapes/psanet_r50-d8_769x769_80k_cityscapes_20200606_225134-fe42f49e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_769x769_80k_cityscapes/psanet_r50-d8_769x769_80k_cityscapes_20200606_225134.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.69 + mIoU(ms+flip): 80.89 + Config: configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_769x769_80k_cityscapes/psanet_r101-d8_769x769_80k_cityscapes_20200606_214550-7665827b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_769x769_80k_cityscapes/psanet_r101-d8_769x769_80k_cityscapes_20200606_214550.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.14 + mIoU(ms+flip): 41.91 + Config: configs/psanet/psanet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - PSANet + Training Resources: 4x V100 GPUS + Memory (GB): 9.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_80k_ade20k/psanet_r50-d8_512x512_80k_ade20k_20200614_144141-835e4b97.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_80k_ade20k/psanet_r50-d8_512x512_80k_ade20k_20200614_144141.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.8 + mIoU(ms+flip): 44.75 + Config: configs/psanet/psanet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - PSANet + Training Resources: 4x V100 GPUS + Memory (GB): 12.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_80k_ade20k/psanet_r101-d8_512x512_80k_ade20k_20200614_185117-1fab60d4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_80k_ade20k/psanet_r101-d8_512x512_80k_ade20k_20200614_185117.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.67 + mIoU(ms+flip): 42.95 + Config: configs/psanet/psanet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - PSANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_160k_ade20k/psanet_r50-d8_512x512_160k_ade20k_20200615_161258-148077dd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_160k_ade20k/psanet_r50-d8_512x512_160k_ade20k_20200615_161258.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.74 + mIoU(ms+flip): 45.38 + Config: configs/psanet/psanet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - PSANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_160k_ade20k/psanet_r101-d8_512x512_160k_ade20k_20200615_161537-dbfa564c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_160k_ade20k/psanet_r101-d8_512x512_160k_ade20k_20200615_161537.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.39 + mIoU(ms+flip): 77.34 + Config: configs/psanet/psanet_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - PSANet + Training Resources: 4x V100 GPUS + Memory (GB): 6.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_20k_voc12aug/psanet_r50-d8_512x512_20k_voc12aug_20200617_102413-2f1bbaa1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_20k_voc12aug/psanet_r50-d8_512x512_20k_voc12aug_20200617_102413.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.91 + mIoU(ms+flip): 79.3 + Config: configs/psanet/psanet_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - PSANet + Training Resources: 4x V100 GPUS + Memory (GB): 10.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_20k_voc12aug/psanet_r101-d8_512x512_20k_voc12aug_20200617_110624-946fef11.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_20k_voc12aug/psanet_r101-d8_512x512_20k_voc12aug_20200617_110624.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.3 + mIoU(ms+flip): 77.35 + Config: configs/psanet/psanet_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - PSANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_40k_voc12aug/psanet_r50-d8_512x512_40k_voc12aug_20200613_161946-f596afb5.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r50-d8_512x512_40k_voc12aug/psanet_r50-d8_512x512_40k_voc12aug_20200613_161946.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch +- Name: psanet_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: PSANet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.73 + mIoU(ms+flip): 79.05 + Config: configs/psanet/psanet_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - PSANet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_40k_voc12aug/psanet_r101-d8_512x512_40k_voc12aug_20200613_161946-1f560f9e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/psanet/psanet_r101-d8_512x512_40k_voc12aug/psanet_r101-d8_512x512_40k_voc12aug_20200613_161946.log.json + Paper: + Title: 'PSANet: Point-wise Spatial Attention Network for Scene Parsing' + URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Hengshuang_Zhao_PSANet_Point-wise_Spatial_ECCV_2018_paper.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psa_head.py#L18 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..e69cf42 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './psanet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..e543099 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './psanet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..b863638 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './psanet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..097b1c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './psanet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..ac86306 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './psanet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..abd8e56 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './psanet_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..d3154a8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './psanet_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..b34d424 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './psanet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..82463aa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/psanet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..af44b30 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/psanet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..5e5052f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/psanet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..0eaf830 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/psanet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..de13296 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/psanet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(mask_size=(66, 66), num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..45d8762 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/psanet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..b5d99d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/psanet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..c3b6528 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/psanet/psanet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/psanet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(mask_size=(66, 66), num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/README.md b/Seg_All_In_One_MMSeg/configs/pspnet/README.md new file mode 100644 index 0000000..4209d25 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/README.md @@ -0,0 +1,182 @@ +# PSPNet + +> [Pyramid Scene Parsing Network](https://arxiv.org/abs/1612.01105) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Scene parsing is challenging for unrestricted open vocabulary and diverse scenes. In this paper, we exploit the capability of global context information by different-region-based context aggregation through our pyramid pooling module together with the proposed pyramid scene parsing network (PSPNet). Our global prior representation is effective to produce good quality results on the scene parsing task, while PSPNet provides a superior framework for pixel-level prediction tasks. The proposed approach achieves state-of-the-art performance on various datasets. It came first in ImageNet scene parsing challenge 2016, PASCAL VOC 2012 benchmark and Cityscapes benchmark. A single PSPNet yields new record of mIoU accuracy 85.4% on PASCAL VOC 2012 and accuracy 80.2% on Cityscapes. + + + +
+ +
+ +
+PSPNet-R50-D8 +PSPNet-R50 D8 model structure +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------------- | ------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| PSPNet | R-50-D8 | 512x1024 | 40000 | 6.1 | 4.07 | V100 | 77.85 | 79.18 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338.log.json) | +| PSPNet | R-101-D8 | 512x1024 | 40000 | 9.6 | 2.68 | V100 | 78.34 | 79.74 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751-467e7cf4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751.log.json) | +| PSPNet | R-50-D8 | 769x769 | 40000 | 6.9 | 1.76 | V100 | 78.26 | 79.88 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_769x769_40k_cityscapes/pspnet_r50-d8_769x769_40k_cityscapes_20200606_112725-86638686.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_769x769_40k_cityscapes/pspnet_r50-d8_769x769_40k_cityscapes_20200606_112725.log.json) | +| PSPNet | R-101-D8 | 769x769 | 40000 | 10.9 | 1.15 | V100 | 79.08 | 80.28 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_769x769_40k_cityscapes/pspnet_r101-d8_769x769_40k_cityscapes_20200606_112753-61c6f5be.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_769x769_40k_cityscapes/pspnet_r101-d8_769x769_40k_cityscapes_20200606_112753.log.json) | +| PSPNet | R-18-D8 | 512x1024 | 80000 | 1.7 | 15.71 | V100 | 74.87 | 76.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_512x1024_80k_cityscapes/pspnet_r18-d8_512x1024_80k_cityscapes_20201225_021458-09ffa746.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_512x1024_80k_cityscapes/pspnet_r18-d8_512x1024_80k_cityscapes-20201225_021458.log.json) | +| PSPNet | R-50-D8 | 512x1024 | 80000 | - | - | V100 | 78.55 | 79.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_80k_cityscapes/pspnet_r50-d8_512x1024_80k_cityscapes_20200606_112131-2376f12b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_80k_cityscapes/pspnet_r50-d8_512x1024_80k_cityscapes_20200606_112131.log.json) | +| PSPNet | R-50b-D8 rsb | 512x1024 | 80000 | 6.2 | 3.82 | V100 | 78.47 | 79.45 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8-rsb_4xb2-adamw-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_80k_cityscapes/pspnet_r50-d8_rsb-pretrain_512x1024_adamw_80k_cityscapes_20220315_123238-588c30be.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_80k_cityscapes/pspnet_r50-d8_rsb-pretrain_512x1024_adamw_80k_cityscapes_20220315_123238.log.json) | +| PSPNet | R-101-D8 | 512x1024 | 80000 | - | - | V100 | 79.76 | 81.01 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_80k_cityscapes/pspnet_r101-d8_512x1024_80k_cityscapes_20200606_112211-e1e1100f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_80k_cityscapes/pspnet_r101-d8_512x1024_80k_cityscapes_20200606_112211.log.json) | +| PSPNet (FP16) | R-101-D8 | 512x1024 | 80000 | 5.34 | 8.77 | V100 | 79.46 | - | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_fp16_512x1024_80k_cityscapes/pspnet_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230919-a0875e5c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_fp16_512x1024_80k_cityscapes/pspnet_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230919.log.json) | +| PSPNet | R-18-D8 | 769x769 | 80000 | 1.9 | 6.20 | V100 | 75.90 | 77.86 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_769x769_80k_cityscapes/pspnet_r18-d8_769x769_80k_cityscapes_20201225_021458-3deefc62.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_769x769_80k_cityscapes/pspnet_r18-d8_769x769_80k_cityscapes-20201225_021458.log.json) | +| PSPNet | R-50-D8 | 769x769 | 80000 | - | - | V100 | 79.59 | 80.69 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_769x769_80k_cityscapes/pspnet_r50-d8_769x769_80k_cityscapes_20200606_210121-5ccf03dd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_769x769_80k_cityscapes/pspnet_r50-d8_769x769_80k_cityscapes_20200606_210121.log.json) | +| PSPNet | R-101-D8 | 769x769 | 80000 | - | - | V100 | 79.77 | 81.06 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.oz1z1penmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_769x769_80k_cityscapes/pspnet_r101-d8_769x769_80k_cityscapes_20200606_225055-dba412fa.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_769x769_80k_cityscapes/pspnet_r101-d8_769x769_80k_cityscapes_20200606_225055.log.json) | +| PSPNet | R-18b-D8 | 512x1024 | 80000 | 1.5 | 16.28 | V100 | 74.23 | 75.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18b-d8_512x1024_80k_cityscapes/pspnet_r18b-d8_512x1024_80k_cityscapes_20201226_063116-26928a60.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18b-d8_512x1024_80k_cityscapes/pspnet_r18b-d8_512x1024_80k_cityscapes-20201226_063116.log.json) | +| PSPNet | R-50b-D8 | 512x1024 | 80000 | 6.0 | 4.30 | V100 | 78.22 | 79.46 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d8_512x1024_80k_cityscapes/pspnet_r50b-d8_512x1024_80k_cityscapes_20201225_094315-6344287a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d8_512x1024_80k_cityscapes/pspnet_r50b-d8_512x1024_80k_cityscapes-20201225_094315.log.json) | +| PSPNet | R-101b-D8 | 512x1024 | 80000 | 9.5 | 2.76 | V100 | 79.69 | 80.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes_20201226_170012-3a4d38ab.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes-20201226_170012.log.json) | +| PSPNet | R-18b-D8 | 769x769 | 80000 | 1.7 | 6.41 | V100 | 74.92 | 76.90 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18b-d8_769x769_80k_cityscapes/pspnet_r18b-d8_769x769_80k_cityscapes_20201226_080942-bf98d186.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18b-d8_769x769_80k_cityscapes/pspnet_r18b-d8_769x769_80k_cityscapes-20201226_080942.log.json) | +| PSPNet | R-50b-D8 | 769x769 | 80000 | 6.8 | 1.88 | V100 | 78.50 | 79.96 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d8_769x769_80k_cityscapes/pspnet_r50b-d8_769x769_80k_cityscapes_20201225_094316-4c643cf6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d8_769x769_80k_cityscapes/pspnet_r50b-d8_769x769_80k_cityscapes-20201225_094316.log.json) | +| PSPNet | R-101b-D8 | 769x769 | 80000 | 10.8 | 1.17 | V100 | 78.87 | 80.04 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_769x769_80k_cityscapes/pspnet_r101b-d8_769x769_80k_cityscapes_20201226_171823-f0e7c293.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_769x769_80k_cityscapes/pspnet_r101b-d8_769x769_80k_cityscapes-20201226_171823.log.json) | +| PSPNet | R-50-D32 | 512x1024 | 80000 | 3.0 | 15.21 | V100 | 73.88 | 76.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d32_512x1024_80k_cityscapes/pspnet_r50-d32_512x1024_80k_cityscapes_20220316_224840-9092b254.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d32_512x1024_80k_cityscapes/pspnet_r50-d32_512x1024_80k_cityscapes_20220316_224840.log.json) | +| PSPNet | R-50b-D32 rsb | 512x1024 | 80000 | 3.1 | 16.08 | V100 | 74.09 | 77.18 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d32_rsb_4xb2-adamw-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d32_rsb-pretrain_512x1024_adamw_80k_cityscapes/pspnet_r50-d32_rsb-pretrain_512x1024_adamw_80k_cityscapes_20220316_141229-dd9c9610.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d32_rsb-pretrain_512x1024_adamw_80k_cityscapes/pspnet_r50-d32_rsb-pretrain_512x1024_adamw_80k_cityscapes_20220316_141229.log.json) | +| PSPNet | R-50b-D32 | 512x1024 | 80000 | 2.9 | 15.41 | V100 | 72.61 | 75.51 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d32_512x1024_80k_cityscapes/pspnet_r50b-d32_512x1024_80k_cityscapes_20220311_152152-23bcaf8c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d32_512x1024_80k_cityscapes/pspnet_r50b-d32_512x1024_80k_cityscapes_20220311_152152.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-50-D8 | 512x512 | 80000 | 8.5 | 23.53 | V100 | 41.13 | 41.94 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_80k_ade20k/pspnet_r50-d8_512x512_80k_ade20k_20200615_014128-15a8b914.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_80k_ade20k/pspnet_r50-d8_512x512_80k_ade20k_20200615_014128.log.json) | +| PSPNet | R-101-D8 | 512x512 | 80000 | 12 | 15.30 | V100 | 43.57 | 44.35 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_80k_ade20k/pspnet_r101-d8_512x512_80k_ade20k_20200614_031423-b6e782f0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_80k_ade20k/pspnet_r101-d8_512x512_80k_ade20k_20200614_031423.log.json) | +| PSPNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 42.48 | 43.44 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_160k_ade20k/pspnet_r50-d8_512x512_160k_ade20k_20200615_184358-1890b0bd.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_160k_ade20k/pspnet_r50-d8_512x512_160k_ade20k_20200615_184358.log.json) | +| PSPNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 44.39 | 45.35 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_160k_ade20k/pspnet_r101-d8_512x512_160k_ade20k_20200615_100650-967c316f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_160k_ade20k/pspnet_r101-d8_512x512_160k_ade20k_20200615_100650.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-50-D8 | 512x512 | 20000 | 6.1 | 23.59 | V100 | 76.78 | 77.61 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_20k_voc12aug/pspnet_r50-d8_512x512_20k_voc12aug_20200617_101958-ed5dfbd9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_20k_voc12aug/pspnet_r50-d8_512x512_20k_voc12aug_20200617_101958.log.json) | +| PSPNet | R-101-D8 | 512x512 | 20000 | 9.6 | 15.02 | V100 | 78.47 | 79.25 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_20k_voc12aug/pspnet_r101-d8_512x512_20k_voc12aug_20200617_102003-4aef3c9a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_20k_voc12aug/pspnet_r101-d8_512x512_20k_voc12aug_20200617_102003.log.json) | +| PSPNet | R-50-D8 | 512x512 | 40000 | - | - | V100 | 77.29 | 78.48 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_40k_voc12aug/pspnet_r50-d8_512x512_40k_voc12aug_20200613_161222-ae9c1b8c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_40k_voc12aug/pspnet_r50-d8_512x512_40k_voc12aug_20200613_161222.log.json) | +| PSPNet | R-101-D8 | 512x512 | 40000 | - | - | V100 | 78.52 | 79.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_40k_voc12aug/pspnet_r101-d8_512x512_40k_voc12aug_20200613_161222-bc933b18.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_40k_voc12aug/pspnet_r101-d8_512x512_40k_voc12aug_20200613_161222.log.json) | + +### Pascal Context + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-101-D8 | 480x480 | 40000 | 8.8 | 9.68 | V100 | 46.60 | 47.78 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_40k_pascal_context/pspnet_r101-d8_480x480_40k_pascal_context_20200911_211210-bf0f5d7c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_40k_pascal_context/pspnet_r101-d8_480x480_40k_pascal_context-20200911_211210.log.json) | +| PSPNet | R-101-D8 | 480x480 | 80000 | - | - | V100 | 46.03 | 47.15 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_80k_pascal_context/pspnet_r101-d8_480x480_80k_pascal_context_20200911_190530-c86d6233.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_80k_pascal_context/pspnet_r101-d8_480x480_80k_pascal_context-20200911_190530.log.json) | + +### Pascal Context 59 + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-101-D8 | 480x480 | 40000 | - | - | V100 | 52.02 | 53.54 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_40k_pascal_context_59/pspnet_r101-d8_480x480_40k_pascal_context_59_20210416_114524-86d44cd4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_40k_pascal_context_59/pspnet_r101-d8_480x480_40k_pascal_context_59-20210416_114524.log.json) | +| PSPNet | R-101-D8 | 480x480 | 80000 | - | - | V100 | 52.47 | 53.99 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-59-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_80k_pascal_context_59/pspnet_r101-d8_480x480_80k_pascal_context_59_20210416_114418-fa6caaa2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_80k_pascal_context_59/pspnet_r101-d8_480x480_80k_pascal_context_59-20210416_114418.log.json) | + +### Dark Zurich and Nighttime Driving + +We support evaluation results on these two datasets using models above trained on Cityscapes training set. + +| Method | Backbone | Training Dataset | Test Dataset | mIoU | config | evaluation checkpoint | +| ------ | --------- | ----------------------- | ------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| PSPNet | R-50-D8 | Cityscapes Training set | Dark Zurich | 10.91 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338.log.json) | +| PSPNet | R-50-D8 | Cityscapes Training set | Nighttime Driving | 23.02 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338.log.json) | +| PSPNet | R-50-D8 | Cityscapes Training set | Cityscapes Validation set | 77.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338.log.json) | +| PSPNet | R-101-D8 | Cityscapes Training set | Dark Zurich | 10.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751-467e7cf4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751.log.json) | +| PSPNet | R-101-D8 | Cityscapes Training set | Nighttime Driving | 20.25 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751-467e7cf4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751.log.json) | +| PSPNet | R-101-D8 | Cityscapes Training set | Cityscapes Validation set | 78.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751-467e7cf4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751.log.json) | +| PSPNet | R-101b-D8 | Cityscapes Training set | Dark Zurich | 15.54 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes_20201226_170012-3a4d38ab.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes-20201226_170012.log.json) | +| PSPNet | R-101b-D8 | Cityscapes Training set | Nighttime Driving | 22.25 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes_20201226_170012-3a4d38ab.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes-20201226_170012.log.json) | +| PSPNet | R-101b-D8 | Cityscapes Training set | Cityscapes Validation set | 79.69 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes_20201226_170012-3a4d38ab.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes-20201226_170012.log.json) | + +### COCO-Stuff 10k + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-50-D8 | 512x512 | 20000 | 9.6 | 20.5 | V100 | 35.69 | 36.62 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-20k_coco-stuff10k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_20k_coco-stuff10k/pspnet_r50-d8_512x512_4x4_20k_coco-stuff10k_20210820_203258-b88df27f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_20k_coco-stuff10k/pspnet_r50-d8_512x512_4x4_20k_coco-stuff10k_20210820_203258.log.json) | +| PSPNet | R-101-D8 | 512x512 | 20000 | 13.2 | 11.1 | V100 | 37.26 | 38.52 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-20k_coco-stuff10k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_20k_coco-stuff10k/pspnet_r101-d8_512x512_4x4_20k_coco-stuff10k_20210820_232135-76aae482.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_20k_coco-stuff10k/pspnet_r101-d8_512x512_4x4_20k_coco-stuff10k_20210820_232135.log.json) | +| PSPNet | R-50-D8 | 512x512 | 40000 | - | - | V100 | 36.33 | 37.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-40k_coco-stuff10k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_40k_coco-stuff10k/pspnet_r50-d8_512x512_4x4_40k_coco-stuff10k_20210821_030857-92e2902b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_40k_coco-stuff10k/pspnet_r50-d8_512x512_4x4_40k_coco-stuff10k_20210821_030857.log.json) | +| PSPNet | R-101-D8 | 512x512 | 40000 | - | - | V100 | 37.76 | 38.86 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-40k_coco-stuff10k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_40k_coco-stuff10k/pspnet_r101-d8_512x512_4x4_40k_coco-stuff10k_20210821_014022-831aec95.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_40k_coco-stuff10k/pspnet_r101-d8_512x512_4x4_40k_coco-stuff10k_20210821_014022.log.json) | + +### COCO-Stuff 164k + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-50-D8 | 512x512 | 80000 | 9.6 | 20.5 | V100 | 38.80 | 39.19 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-80k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_80k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_80k_coco-stuff164k_20210707_152034-0e41b2db.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_80k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_80k_coco-stuff164k_20210707_152034.log.json) | +| PSPNet | R-101-D8 | 512x512 | 80000 | 13.2 | 11.1 | V100 | 40.34 | 40.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-80k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_80k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_80k_coco-stuff164k_20210707_152034-7eb41789.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_80k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_80k_coco-stuff164k_20210707_152034.log.json) | +| PSPNet | R-50-D8 | 512x512 | 160000 | - | - | V100 | 39.64 | 39.97 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_160k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_160k_coco-stuff164k_20210707_152004-51276a57.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_160k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_160k_coco-stuff164k_20210707_152004.log.json) | +| PSPNet | R-101-D8 | 512x512 | 160000 | - | - | V100 | 41.28 | 41.66 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-160k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_160k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_160k_coco-stuff164k_20210707_152004-4af9621b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_160k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_160k_coco-stuff164k_20210707_152004.log.json) | +| PSPNet | R-50-D8 | 512x512 | 320000 | - | - | V100 | 40.53 | 40.75 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-320k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_320k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_320k_coco-stuff164k_20210707_152004-be9610cc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_320k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_320k_coco-stuff164k_20210707_152004.log.json) | +| PSPNet | R-101-D8 | 512x512 | 320000 | - | - | V100 | 41.95 | 42.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-320k_coco-stuff164k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_320k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_320k_coco-stuff164k_20210707_152004-72220c60.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_320k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_320k_coco-stuff164k_20210707_152004.log.json) | + +### LoveDA + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| PSPNet | R-18-D8 | 512x512 | 80000 | 1.45 | 26.87 | V100 | 48.62 | 47.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r18-d8_4xb4-80k_loveda-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_512x512_80k_loveda/pspnet_r18-d8_512x512_80k_loveda_20211105_052100-b97697f1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_512x512_80k_loveda/pspnet_r18-d8_512x512_80k_loveda_20211105_052100.log.json) | +| PSPNet | R-50-D8 | 512x512 | 80000 | 6.14 | 6.60 | V100 | 50.46 | 50.19 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-80k_loveda-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_80k_loveda/pspnet_r50-d8_512x512_80k_loveda_20211104_155728-88610f9f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_80k_loveda/pspnet_r50-d8_512x512_80k_loveda_20211104_155728.log.json) | +| PSPNet | R-101-D8 | 512x512 | 80000 | 9.61 | 4.58 | V100 | 51.86 | 51.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-80k_loveda-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_80k_loveda/pspnet_r101-d8_512x512_80k_loveda_20211104_153212-1c06c6a8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_80k_loveda/pspnet_r101-d8_512x512_80k_loveda_20211104_153212.log.json) | + +### Potsdam + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-18-D8 | 512x512 | 80000 | 1.50 | 85.12 | V100 | 77.09 | 78.30 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r18-d8_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_512x512_80k_potsdam/pspnet_r18-d8_4x4_512x512_80k_potsdam_20211220_125612-7cd046e1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_512x512_80k_potsdam/pspnet_r18-d8_4x4_512x512_80k_potsdam_20211220_125612.log.json) | +| PSPNet | R-50-D8 | 512x512 | 80000 | 6.14 | 30.21 | V100 | 78.12 | 78.98 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_512x512_80k_potsdam/pspnet_r50-d8_4x4_512x512_80k_potsdam_20211219_043541-2dd5fe67.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_512x512_80k_potsdam/pspnet_r50-d8_4x4_512x512_80k_potsdam_20211219_043541.log.json) | +| PSPNet | R-101-D8 | 512x512 | 80000 | 9.61 | 19.40 | V100 | 78.62 | 79.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-80k_potsdam-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_4x4_512x512_80k_potsdam/pspnet_r101-d8_4x4_512x512_80k_potsdam_20211220_125612-aed036c4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_4x4_512x512_80k_potsdam/pspnet_r101-d8_4x4_512x512_80k_potsdam_20211220_125612.log.json) | + +### Vaihingen + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-18-D8 | 512x512 | 80000 | 1.45 | 85.06 | V100 | 71.46 | 73.36 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r18-d8_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_512x512_80k_vaihingen/pspnet_r18-d8_4x4_512x512_80k_vaihingen_20211228_160355-52a8a6f6.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_512x512_80k_vaihingen/pspnet_r18-d8_4x4_512x512_80k_vaihingen_20211228_160355.log.json) | +| PSPNet | R-50-D8 | 512x512 | 80000 | 6.14 | 30.29 | V100 | 72.36 | 73.75 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_512x512_80k_vaihingen/pspnet_r50-d8_4x4_512x512_80k_vaihingen_20211228_160355-382f8f5b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_512x512_80k_vaihingen/pspnet_r50-d8_4x4_512x512_80k_vaihingen_20211228_160355.log.json) | +| PSPNet | R-101-D8 | 512x512 | 80000 | 9.61 | 19.97 | V100 | 72.61 | 74.18 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r101-d8_4xb4-80k_vaihingen-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_4x4_512x512_80k_vaihingen/pspnet_r101-d8_4x4_512x512_80k_vaihingen_20211231_230806-8eba0a09.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_4x4_512x512_80k_vaihingen/pspnet_r101-d8_4x4_512x512_80k_vaihingen_20211231_230806.log.json) | + +### iSAID + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| PSPNet | R-18-D8 | 896x896 | 80000 | 4.52 | 26.91 | V100 | 60.22 | 61.25 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r18-d8_4xb4-80k_isaid-896x896.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_896x896_80k_isaid/pspnet_r18-d8_4x4_896x896_80k_isaid_20220110_180526-e84c0b6a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_896x896_80k_isaid/pspnet_r18-d8_4x4_896x896_80k_isaid_20220110_180526.log.json) | +| PSPNet | R-50-D8 | 896x896 | 80000 | 16.58 | 8.88 | V100 | 65.36 | 66.48 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet/pspnet_r50-d8_4xb4-80k_isaid-896x896.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_896x896_80k_isaid/pspnet_r50-d8_4x4_896x896_80k_isaid_20220110_180629-1f21dc32.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_896x896_80k_isaid/pspnet_r50-d8_4x4_896x896_80k_isaid_20220110_180629.log.json) | + +Note: + +- `FP16` means Mixed Precision (FP16) is adopted in training. +- `896x896` is the Crop Size of iSAID dataset, which is followed by the implementation of [PointFlow: Flowing Semantics Through Points for Aerial Image Segmentation](https://arxiv.org/pdf/2103.06564.pdf) +- `rsb` is short for 'Resnet strikes back'. +- The `b` in `R-50b` means ResNetV1b, which is a standard ResNet backbone. In MMSegmentation, default backbone is ResNetV1c, which usually performs better in semantic segmentation task. + +## Citation + +```bibtex +@inproceedings{zhao2017pspnet, + title={Pyramid Scene Parsing Network}, + author={Zhao, Hengshuang and Shi, Jianping and Qi, Xiaojuan and Wang, Xiaogang and Jia, Jiaya}, + booktitle={CVPR}, + year={2017} +} +``` + +```bibtex +@article{wightman2021resnet, + title={Resnet strikes back: An improved training procedure in timm}, + author={Wightman, Ross and Touvron, Hugo and J{\'e}gou, Herv{\'e}}, + journal={arXiv preprint arXiv:2110.00476}, + year={2021} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/pspnet/metafile.yaml new file mode 100644 index 0000000..d00b89d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/metafile.yaml @@ -0,0 +1,1303 @@ +Collections: +- Name: PSPNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + - Pascal Context + - Pascal Context 59 + - Dark Zurich and Nighttime Driving + - COCO-Stuff 10k + - COCO-Stuff 164k + - LoveDA + - Potsdam + - Vaihingen + - iSAID + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + README: configs/pspnet/README.md + Frameworks: + - PyTorch +Models: +- Name: pspnet_r50-d8_4xb2-40k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.85 + mIoU(ms+flip): 79.18 + Config: configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb2-40k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.34 + mIoU(ms+flip): 79.74 + Config: configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751-467e7cf4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_40k_cityscapes/pspnet_r101-d8_512x1024_40k_cityscapes_20200604_232751.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb2-40k_cityscapes-769x769 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.26 + mIoU(ms+flip): 79.88 + Config: configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_769x769_40k_cityscapes/pspnet_r50-d8_769x769_40k_cityscapes_20200606_112725-86638686.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_769x769_40k_cityscapes/pspnet_r50-d8_769x769_40k_cityscapes_20200606_112725.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb2-40k_cityscapes-769x769 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.08 + mIoU(ms+flip): 80.28 + Config: configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_769x769_40k_cityscapes/pspnet_r101-d8_769x769_40k_cityscapes_20200606_112753-61c6f5be.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_769x769_40k_cityscapes/pspnet_r101-d8_769x769_40k_cityscapes_20200606_112753.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r18-d8_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.87 + mIoU(ms+flip): 76.04 + Config: configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_512x1024_80k_cityscapes/pspnet_r18-d8_512x1024_80k_cityscapes_20201225_021458-09ffa746.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_512x1024_80k_cityscapes/pspnet_r18-d8_512x1024_80k_cityscapes-20201225_021458.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.55 + mIoU(ms+flip): 79.79 + Config: configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_80k_cityscapes/pspnet_r50-d8_512x1024_80k_cityscapes_20200606_112131-2376f12b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_80k_cityscapes/pspnet_r50-d8_512x1024_80k_cityscapes_20200606_112131.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8-rsb_4xb2-adamw-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.47 + mIoU(ms+flip): 79.45 + Config: configs/pspnet/pspnet_r50-d8-rsb_4xb2-adamw-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_80k_cityscapes/pspnet_r50-d8_rsb-pretrain_512x1024_adamw_80k_cityscapes_20220315_123238-588c30be.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x1024_80k_cityscapes/pspnet_r50-d8_rsb-pretrain_512x1024_adamw_80k_cityscapes_20220315_123238.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.76 + mIoU(ms+flip): 81.01 + Config: configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_80k_cityscapes/pspnet_r101-d8_512x1024_80k_cityscapes_20200606_112211-e1e1100f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x1024_80k_cityscapes/pspnet_r101-d8_512x1024_80k_cityscapes_20200606_112211.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb2-amp-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.46 + Config: configs/pspnet/pspnet_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSPNet + - (FP16) + Training Resources: 4x V100 GPUS + Memory (GB): 5.34 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_fp16_512x1024_80k_cityscapes/pspnet_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230919-a0875e5c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_fp16_512x1024_80k_cityscapes/pspnet_r101-d8_fp16_512x1024_80k_cityscapes_20200717_230919.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r18-d8_4xb2-80k_cityscapes-769x769 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.9 + mIoU(ms+flip): 77.86 + Config: configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_769x769_80k_cityscapes/pspnet_r18-d8_769x769_80k_cityscapes_20201225_021458-3deefc62.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_769x769_80k_cityscapes/pspnet_r18-d8_769x769_80k_cityscapes-20201225_021458.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb2-80k_cityscapes-769x769 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.59 + mIoU(ms+flip): 80.69 + Config: configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_769x769_80k_cityscapes/pspnet_r50-d8_769x769_80k_cityscapes_20200606_210121-5ccf03dd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_769x769_80k_cityscapes/pspnet_r50-d8_769x769_80k_cityscapes_20200606_210121.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb2-80k_cityscapes-769x769 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.77 + mIoU(ms+flip): 81.06 + Config: configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.oz1z1penmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_769x769_80k_cityscapes/pspnet_r101-d8_769x769_80k_cityscapes_20200606_225055-dba412fa.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_769x769_80k_cityscapes/pspnet_r101-d8_769x769_80k_cityscapes_20200606_225055.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r18b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.23 + mIoU(ms+flip): 75.79 + Config: configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18b-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18b-d8_512x1024_80k_cityscapes/pspnet_r18b-d8_512x1024_80k_cityscapes_20201226_063116-26928a60.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18b-d8_512x1024_80k_cityscapes/pspnet_r18b-d8_512x1024_80k_cityscapes-20201226_063116.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.22 + mIoU(ms+flip): 79.46 + Config: configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d8_512x1024_80k_cityscapes/pspnet_r50b-d8_512x1024_80k_cityscapes_20201225_094315-6344287a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d8_512x1024_80k_cityscapes/pspnet_r50b-d8_512x1024_80k_cityscapes-20201225_094315.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.69 + mIoU(ms+flip): 80.79 + Config: configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes_20201226_170012-3a4d38ab.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_512x1024_80k_cityscapes/pspnet_r101b-d8_512x1024_80k_cityscapes-20201226_170012.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r18b-d8_4xb2-80k_cityscapes-769x769 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.92 + mIoU(ms+flip): 76.9 + Config: configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-18b-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.7 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18b-d8_769x769_80k_cityscapes/pspnet_r18b-d8_769x769_80k_cityscapes_20201226_080942-bf98d186.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18b-d8_769x769_80k_cityscapes/pspnet_r18b-d8_769x769_80k_cityscapes-20201226_080942.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50b-d8_4xb2-80k_cityscapes-769x769 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.5 + mIoU(ms+flip): 79.96 + Config: configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d8_769x769_80k_cityscapes/pspnet_r50b-d8_769x769_80k_cityscapes_20201225_094316-4c643cf6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d8_769x769_80k_cityscapes/pspnet_r50b-d8_769x769_80k_cityscapes-20201225_094316.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101b-d8_4xb2-80k_cityscapes-769x769 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.87 + mIoU(ms+flip): 80.04 + Config: configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101b-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 10.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_769x769_80k_cityscapes/pspnet_r101b-d8_769x769_80k_cityscapes_20201226_171823-f0e7c293.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101b-d8_769x769_80k_cityscapes/pspnet_r101b-d8_769x769_80k_cityscapes-20201226_171823.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.88 + mIoU(ms+flip): 76.85 + Config: configs/pspnet/pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50-D32 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 3.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d32_512x1024_80k_cityscapes/pspnet_r50-d32_512x1024_80k_cityscapes_20220316_224840-9092b254.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d32_512x1024_80k_cityscapes/pspnet_r50-d32_512x1024_80k_cityscapes_20220316_224840.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d32_rsb_4xb2-adamw-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.09 + mIoU(ms+flip): 77.18 + Config: configs/pspnet/pspnet_r50-d32_rsb_4xb2-adamw-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D32 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 3.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d32_rsb-pretrain_512x1024_adamw_80k_cityscapes/pspnet_r50-d32_rsb-pretrain_512x1024_adamw_80k_cityscapes_20220316_141229-dd9c9610.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d32_rsb-pretrain_512x1024_adamw_80k_cityscapes/pspnet_r50-d32_rsb-pretrain_512x1024_adamw_80k_cityscapes_20220316_141229.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 72.61 + mIoU(ms+flip): 75.51 + Config: configs/pspnet/pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50b-D32 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 2.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d32_512x1024_80k_cityscapes/pspnet_r50b-d32_512x1024_80k_cityscapes_20220311_152152-23bcaf8c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50b-d32_512x1024_80k_cityscapes/pspnet_r50b-d32_512x1024_80k_cityscapes_20220311_152152.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-80k_ade20k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.13 + mIoU(ms+flip): 41.94 + Config: configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_80k_ade20k/pspnet_r50-d8_512x512_80k_ade20k_20200615_014128-15a8b914.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_80k_ade20k/pspnet_r50-d8_512x512_80k_ade20k_20200615_014128.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-80k_ade20k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.57 + mIoU(ms+flip): 44.35 + Config: configs/pspnet/pspnet_r101-d8_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 12.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_80k_ade20k/pspnet_r101-d8_512x512_80k_ade20k_20200614_031423-b6e782f0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_80k_ade20k/pspnet_r101-d8_512x512_80k_ade20k_20200614_031423.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-160k_ade20k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.48 + mIoU(ms+flip): 43.44 + Config: configs/pspnet/pspnet_r50-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_160k_ade20k/pspnet_r50-d8_512x512_160k_ade20k_20200615_184358-1890b0bd.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_160k_ade20k/pspnet_r50-d8_512x512_160k_ade20k_20200615_184358.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-160k_ade20k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.39 + mIoU(ms+flip): 45.35 + Config: configs/pspnet/pspnet_r101-d8_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_160k_ade20k/pspnet_r101-d8_512x512_160k_ade20k_20200615_100650-967c316f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_160k_ade20k/pspnet_r101-d8_512x512_160k_ade20k_20200615_100650.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-20k_voc12aug-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 76.78 + mIoU(ms+flip): 77.61 + Config: configs/pspnet/pspnet_r50-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_20k_voc12aug/pspnet_r50-d8_512x512_20k_voc12aug_20200617_101958-ed5dfbd9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_20k_voc12aug/pspnet_r50-d8_512x512_20k_voc12aug_20200617_101958.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-20k_voc12aug-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 78.47 + mIoU(ms+flip): 79.25 + Config: configs/pspnet/pspnet_r101-d8_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_20k_voc12aug/pspnet_r101-d8_512x512_20k_voc12aug_20200617_102003-4aef3c9a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_20k_voc12aug/pspnet_r101-d8_512x512_20k_voc12aug_20200617_102003.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-40k_voc12aug-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.29 + mIoU(ms+flip): 78.48 + Config: configs/pspnet/pspnet_r50-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_40k_voc12aug/pspnet_r50-d8_512x512_40k_voc12aug_20200613_161222-ae9c1b8c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_40k_voc12aug/pspnet_r50-d8_512x512_40k_voc12aug_20200613_161222.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-40k_voc12aug-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 78.52 + mIoU(ms+flip): 79.57 + Config: configs/pspnet/pspnet_r101-d8_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_40k_voc12aug/pspnet_r101-d8_512x512_40k_voc12aug_20200613_161222-bc933b18.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_40k_voc12aug/pspnet_r101-d8_512x512_40k_voc12aug_20200613_161222.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-40k_pascal-context-480x480 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 46.6 + mIoU(ms+flip): 47.78 + Config: configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_40k_pascal_context/pspnet_r101-d8_480x480_40k_pascal_context_20200911_211210-bf0f5d7c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_40k_pascal_context/pspnet_r101-d8_480x480_40k_pascal_context-20200911_211210.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-80k_pascal-context-480x480 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Pascal Context + Metrics: + mIoU: 46.03 + mIoU(ms+flip): 47.15 + Config: configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-480x480.py + Metadata: + Training Data: Pascal Context + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_80k_pascal_context/pspnet_r101-d8_480x480_80k_pascal_context_20200911_190530-c86d6233.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_80k_pascal_context/pspnet_r101-d8_480x480_80k_pascal_context-20200911_190530.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-40k_pascal-context-59-480x480 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 52.02 + mIoU(ms+flip): 53.54 + Config: configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_40k_pascal_context_59/pspnet_r101-d8_480x480_40k_pascal_context_59_20210416_114524-86d44cd4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_40k_pascal_context_59/pspnet_r101-d8_480x480_40k_pascal_context_59-20210416_114524.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-80k_pascal-context-59-480x480 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Pascal Context 59 + Metrics: + mIoU: 52.47 + mIoU(ms+flip): 53.99 + Config: configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-59-480x480.py + Metadata: + Training Data: Pascal Context 59 + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_80k_pascal_context_59/pspnet_r101-d8_480x480_80k_pascal_context_59_20210416_114418-fa6caaa2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_480x480_80k_pascal_context_59/pspnet_r101-d8_480x480_80k_pascal_context_59-20210416_114418.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-20k_coco-stuff10k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 10k + Metrics: + mIoU: 35.69 + mIoU(ms+flip): 36.62 + Config: configs/pspnet/pspnet_r50-d8_4xb4-20k_coco-stuff10k-512x512.py + Metadata: + Training Data: COCO-Stuff 10k + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_20k_coco-stuff10k/pspnet_r50-d8_512x512_4x4_20k_coco-stuff10k_20210820_203258-b88df27f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_20k_coco-stuff10k/pspnet_r50-d8_512x512_4x4_20k_coco-stuff10k_20210820_203258.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-20k_coco-stuff10k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 10k + Metrics: + mIoU: 37.26 + mIoU(ms+flip): 38.52 + Config: configs/pspnet/pspnet_r101-d8_4xb4-20k_coco-stuff10k-512x512.py + Metadata: + Training Data: COCO-Stuff 10k + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 13.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_20k_coco-stuff10k/pspnet_r101-d8_512x512_4x4_20k_coco-stuff10k_20210820_232135-76aae482.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_20k_coco-stuff10k/pspnet_r101-d8_512x512_4x4_20k_coco-stuff10k_20210820_232135.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-40k_coco-stuff10k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 10k + Metrics: + mIoU: 36.33 + mIoU(ms+flip): 37.24 + Config: configs/pspnet/pspnet_r50-d8_4xb4-40k_coco-stuff10k-512x512.py + Metadata: + Training Data: COCO-Stuff 10k + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_40k_coco-stuff10k/pspnet_r50-d8_512x512_4x4_40k_coco-stuff10k_20210821_030857-92e2902b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_40k_coco-stuff10k/pspnet_r50-d8_512x512_4x4_40k_coco-stuff10k_20210821_030857.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-40k_coco-stuff10k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 10k + Metrics: + mIoU: 37.76 + mIoU(ms+flip): 38.86 + Config: configs/pspnet/pspnet_r101-d8_4xb4-40k_coco-stuff10k-512x512.py + Metadata: + Training Data: COCO-Stuff 10k + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_40k_coco-stuff10k/pspnet_r101-d8_512x512_4x4_40k_coco-stuff10k_20210821_014022-831aec95.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_40k_coco-stuff10k/pspnet_r101-d8_512x512_4x4_40k_coco-stuff10k_20210821_014022.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-80k_coco-stuff164k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 38.8 + mIoU(ms+flip): 39.19 + Config: configs/pspnet/pspnet_r50-d8_4xb4-80k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_80k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_80k_coco-stuff164k_20210707_152034-0e41b2db.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_80k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_80k_coco-stuff164k_20210707_152034.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-80k_coco-stuff164k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 40.34 + mIoU(ms+flip): 40.79 + Config: configs/pspnet/pspnet_r101-d8_4xb4-80k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 13.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_80k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_80k_coco-stuff164k_20210707_152034-7eb41789.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_80k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_80k_coco-stuff164k_20210707_152034.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-160k_coco-stuff164k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 39.64 + mIoU(ms+flip): 39.97 + Config: configs/pspnet/pspnet_r50-d8_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_160k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_160k_coco-stuff164k_20210707_152004-51276a57.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_160k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_160k_coco-stuff164k_20210707_152004.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-160k_coco-stuff164k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 41.28 + mIoU(ms+flip): 41.66 + Config: configs/pspnet/pspnet_r101-d8_4xb4-160k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_160k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_160k_coco-stuff164k_20210707_152004-4af9621b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_160k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_160k_coco-stuff164k_20210707_152004.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-320k_coco-stuff164k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 40.53 + mIoU(ms+flip): 40.75 + Config: configs/pspnet/pspnet_r50-d8_4xb4-320k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_320k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_320k_coco-stuff164k_20210707_152004-be9610cc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_4x4_320k_coco-stuff164k/pspnet_r50-d8_512x512_4x4_320k_coco-stuff164k_20210707_152004.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-320k_coco-stuff164k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 41.95 + mIoU(ms+flip): 42.42 + Config: configs/pspnet/pspnet_r101-d8_4xb4-320k_coco-stuff164k-512x512.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_320k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_320k_coco-stuff164k_20210707_152004-72220c60.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_4x4_320k_coco-stuff164k/pspnet_r101-d8_512x512_4x4_320k_coco-stuff164k_20210707_152004.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r18-d8_4xb4-80k_loveda-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 48.62 + mIoU(ms+flip): 47.57 + Config: configs/pspnet/pspnet_r18-d8_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - R-18-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.45 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_512x512_80k_loveda/pspnet_r18-d8_512x512_80k_loveda_20211105_052100-b97697f1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_512x512_80k_loveda/pspnet_r18-d8_512x512_80k_loveda_20211105_052100.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-80k_loveda-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 50.46 + mIoU(ms+flip): 50.19 + Config: configs/pspnet/pspnet_r50-d8_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.14 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_80k_loveda/pspnet_r50-d8_512x512_80k_loveda_20211104_155728-88610f9f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_512x512_80k_loveda/pspnet_r50-d8_512x512_80k_loveda_20211104_155728.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-80k_loveda-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: LoveDA + Metrics: + mIoU: 51.86 + mIoU(ms+flip): 51.34 + Config: configs/pspnet/pspnet_r101-d8_4xb4-80k_loveda-512x512.py + Metadata: + Training Data: LoveDA + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.61 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_80k_loveda/pspnet_r101-d8_512x512_80k_loveda_20211104_153212-1c06c6a8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_512x512_80k_loveda/pspnet_r101-d8_512x512_80k_loveda_20211104_153212.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r18-d8_4xb4-80k_potsdam-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 77.09 + mIoU(ms+flip): 78.3 + Config: configs/pspnet/pspnet_r18-d8_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - R-18-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_512x512_80k_potsdam/pspnet_r18-d8_4x4_512x512_80k_potsdam_20211220_125612-7cd046e1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_512x512_80k_potsdam/pspnet_r18-d8_4x4_512x512_80k_potsdam_20211220_125612.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-80k_potsdam-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 78.12 + mIoU(ms+flip): 78.98 + Config: configs/pspnet/pspnet_r50-d8_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.14 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_512x512_80k_potsdam/pspnet_r50-d8_4x4_512x512_80k_potsdam_20211219_043541-2dd5fe67.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_512x512_80k_potsdam/pspnet_r50-d8_4x4_512x512_80k_potsdam_20211219_043541.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-80k_potsdam-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Potsdam + Metrics: + mIoU: 78.62 + mIoU(ms+flip): 79.47 + Config: configs/pspnet/pspnet_r101-d8_4xb4-80k_potsdam-512x512.py + Metadata: + Training Data: Potsdam + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.61 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_4x4_512x512_80k_potsdam/pspnet_r101-d8_4x4_512x512_80k_potsdam_20211220_125612-aed036c4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_4x4_512x512_80k_potsdam/pspnet_r101-d8_4x4_512x512_80k_potsdam_20211220_125612.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r18-d8_4xb4-80k_vaihingen-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 71.46 + mIoU(ms+flip): 73.36 + Config: configs/pspnet/pspnet_r18-d8_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - R-18-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.45 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_512x512_80k_vaihingen/pspnet_r18-d8_4x4_512x512_80k_vaihingen_20211228_160355-52a8a6f6.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_512x512_80k_vaihingen/pspnet_r18-d8_4x4_512x512_80k_vaihingen_20211228_160355.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-80k_vaihingen-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 72.36 + mIoU(ms+flip): 73.75 + Config: configs/pspnet/pspnet_r50-d8_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.14 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_512x512_80k_vaihingen/pspnet_r50-d8_4x4_512x512_80k_vaihingen_20211228_160355-382f8f5b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_512x512_80k_vaihingen/pspnet_r50-d8_4x4_512x512_80k_vaihingen_20211228_160355.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r101-d8_4xb4-80k_vaihingen-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Vaihingen + Metrics: + mIoU: 72.61 + mIoU(ms+flip): 74.18 + Config: configs/pspnet/pspnet_r101-d8_4xb4-80k_vaihingen-512x512.py + Metadata: + Training Data: Vaihingen + Batch Size: 16 + Architecture: + - R-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.61 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_4x4_512x512_80k_vaihingen/pspnet_r101-d8_4x4_512x512_80k_vaihingen_20211231_230806-8eba0a09.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r101-d8_4x4_512x512_80k_vaihingen/pspnet_r101-d8_4x4_512x512_80k_vaihingen_20211231_230806.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r18-d8_4xb4-80k_isaid-896x896 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: iSAID + Metrics: + mIoU: 60.22 + mIoU(ms+flip): 61.25 + Config: configs/pspnet/pspnet_r18-d8_4xb4-80k_isaid-896x896.py + Metadata: + Training Data: iSAID + Batch Size: 16 + Architecture: + - R-18-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 4.52 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_896x896_80k_isaid/pspnet_r18-d8_4x4_896x896_80k_isaid_20220110_180526-e84c0b6a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r18-d8_4x4_896x896_80k_isaid/pspnet_r18-d8_4x4_896x896_80k_isaid_20220110_180526.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch +- Name: pspnet_r50-d8_4xb4-80k_isaid-896x896 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: iSAID + Metrics: + mIoU: 65.36 + mIoU(ms+flip): 66.48 + Config: configs/pspnet/pspnet_r50-d8_4xb4-80k_isaid-896x896.py + Metadata: + Training Data: iSAID + Batch Size: 16 + Architecture: + - R-50-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 16.58 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_896x896_80k_isaid/pspnet_r50-d8_4x4_896x896_80k_isaid_20220110_180629-1f21dc32.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/pspnet/pspnet_r50-d8_4x4_896x896_80k_isaid/pspnet_r50-d8_4x4_896x896_80k_isaid_20220110_180629.log.json + Paper: + Title: Pyramid Scene Parsing Network + URL: https://arxiv.org/abs/1612.01105 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/psp_head.py#L63 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..ff75a6a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_autolaparo.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [123.62464353460942, 85.34836259209033, 82.31539425671558], 'std': [47.172211618459315, 47.08256715323592, 48.135121265163605], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [123.62464353460942, 85.34836259209033, 82.31539425671558], 'std': [47.172211618459315, 47.08256715323592, 48.135121265163605], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 512, 'channels': 128, 'num_classes': 10, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 10, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..ee17692 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_cholecseg8k.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 512, 'channels': 128, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..b065152 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_dresden.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [103.172638338208, 61.44762740851152, 51.407770213021976], 'std': [75.77031253622098, 54.63616729031377, 49.45572239497569], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [103.172638338208, 61.44762740851152, 51.407770213021976], 'std': [75.77031253622098, 54.63616729031377, 49.45572239497569], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 512, 'channels': 128, 'num_classes': 11, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 11, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..3c87b05 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_endovis_2017.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 512, 'channels': 128, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..bc084e9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,20 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_endovis_2018.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +data_preprocessor = {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet18_v1c.pth', 'backbone': {'depth': 18, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 512, 'channels': 128, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 256, 'channels': 64, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r18_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..5a6b6cc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,22 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_autolaparo.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +train_dataloader = dict(batch_size=8) # TODO + +data_preprocessor = {'size': (512, 512), 'mean': [123.62464353460942, 85.34836259209033, 82.31539425671558], 'std': [47.172211618459315, 47.08256715323592, 48.135121265163605], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [123.62464353460942, 85.34836259209033, 82.31539425671558], 'std': [47.172211618459315, 47.08256715323592, 48.135121265163605], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 2048, 'channels': 512, 'num_classes': 10, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 10, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..66a8d7e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,22 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_cholecseg8k.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +train_dataloader = dict(batch_size=8) # TODO + +data_preprocessor = {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [85.65740418979115, 53.99282220050495, 46.074045888534535], 'std': [72.24589167201978, 56.76979155397199, 49.056637115061775], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 2048, 'channels': 512, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 13, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..d86f173 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,22 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_dresden.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +train_dataloader = dict(batch_size=4) # TODO + +data_preprocessor = {'size': (512, 512), 'mean': [103.172638338208, 61.44762740851152, 51.407770213021976], 'std': [75.77031253622098, 54.63616729031377, 49.45572239497569], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [103.172638338208, 61.44762740851152, 51.407770213021976], 'std': [75.77031253622098, 54.63616729031377, 49.45572239497569], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 2048, 'channels': 512, 'num_classes': 11, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 11, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..996162b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,22 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_endovis_2017.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +train_dataloader = dict(batch_size=8) # TODO + +data_preprocessor = {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 2048, 'channels': 512, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..e09fce6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,22 @@ +_base_ = ['../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/publicdataset_endovis_2018.py', '../_base_/default_runtime.py', '../_base_/schedules/schedule_300e_val1_check10.py'] + +norm_cfg = {'type': 'BN'} + +crop_size = (512, 512) + +train_dataloader = dict(batch_size=8) # TODO + +data_preprocessor = {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False} + +model = {'pretrained': './My_Local_Model/open_mmlab/resnet50_v1c.pth', 'backbone': {'depth': 50, 'type': 'ResNetV1c'}, 'data_preprocessor': {'size': (512, 512), 'mean': [122.21429912990676, 77.0821859677977, 87.03836664626716], 'std': [50.53335800365262, 42.895340354037465, 47.739426483390446], 'bgr_to_rgb': False}, 'decode_head': {'in_channels': 2048, 'channels': 512, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}, 'auxiliary_head': {'in_channels': 1024, 'channels': 256, 'num_classes': 8, 'loss_decode': {'type': 'DiceLoss', 'use_sigmoid': False, 'loss_weight': 1.0}, 'norm_cfg': {'type': 'BN'}}} + +test_cfg = {'crop_size': (512, 512)} + +optim_wrapper = {'type': 'OptimWrapper', '_delete_': True, 'optimizer': {'type': 'AdamW', 'lr': 0.0001, 'weight_decay': 0.0005}, 'clip_grad': {'max_norm': 1, 'norm_type': 2}} + +param_scheduler = [{'type': 'LinearLR', 'start_factor': 1e-06, 'by_epoch': True, 'begin': 0, 'end': 10}, {'type': 'PolyLR', 'power': 0.9, 'begin': 10, 'end': 300, 'eta_min': 1e-05, 'by_epoch': True}] + +vis_backends = [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}] + +visualizer = {'name': 'visualizer', 'type': 'SegLocalVisualizer', 'vis_backends': [{'type': 'LocalVisBackend'}, {'type': 'TensorboardVisBackend'}, {'type': 'WandbVisBackend', 'init_kwargs': {'project': 'Seg_MMSeg_Test', 'name': 'my_pspnet_r50_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512'}}]} + diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..f33d653 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py new file mode 100644 index 0000000..5babaa8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py' # noqa +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py new file mode 100644 index 0000000..a9480c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py' # noqa +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..e05cff6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..6704cdd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..3733e69 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..52f86b5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb2-amp-80k_cityscapes-512x1024.py @@ -0,0 +1,6 @@ +_base_ = './pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py' +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005), + loss_scale=512.) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..2231049 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..f5390f8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-160k_coco-stuff164k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-20k_coco-stuff10k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-20k_coco-stuff10k-512x512.py new file mode 100644 index 0000000..84a986c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-20k_coco-stuff10k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-20k_coco-stuff10k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..71897dd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-320k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-320k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..ebaea36 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-320k_coco-stuff164k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-320k_coco-stuff164k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_coco-stuff10k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_coco-stuff10k-512x512.py new file mode 100644 index 0000000..2a55f53 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_coco-stuff10k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-40k_coco-stuff10k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..205d00b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-40k_pascal-context-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..0d7c176 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-40k_pascal-context-59-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..0599f31 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..f955603 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..4a34f97 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_coco-stuff164k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_coco-stuff164k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..7076877 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_loveda-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_loveda-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..0ac40dc --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_pascal-context-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..307188c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_pascal-context-59-480x480.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..31ed2f2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_potsdam-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..ac33ed7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101-d8_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_vaihingen-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..d2c0f69 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,4 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py new file mode 100644 index 0000000..b181744 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py @@ -0,0 +1,4 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py' # noqa +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py new file mode 100644 index 0000000..6a8994b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py @@ -0,0 +1,4 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py' # noqa +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..891bfd5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r101b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,4 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet101', + backbone=dict(type='ResNet', depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..a4b342e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..0e7f3e9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,9 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_isaid-896x896.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_isaid-896x896.py new file mode 100644 index 0000000..efce7a0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_isaid-896x896.py @@ -0,0 +1,9 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_isaid-896x896.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..80e2d20 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_loveda-512x512.py @@ -0,0 +1,9 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_loveda-512x512.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..1ef0585 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,9 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_potsdam-512x512.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..51e66d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18-d8_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,9 @@ +_base_ = './pspnet_r50-d8_4xb4-80k_vaihingen-512x512.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..2e356c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='torchvision://resnet18', + backbone=dict(type='ResNet', depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..831354d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r18b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,9 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict( + pretrained='torchvision://resnet18', + backbone=dict(type='ResNet', depth=18), + decode_head=dict( + in_channels=512, + channels=128, + ), + auxiliary_head=dict(in_channels=256, channels=64)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d32_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d32_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..5700b5b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d32_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(dilations=(1, 1, 2, 4), strides=(1, 2, 2, 2))) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d32_rsb_4xb2-adamw-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d32_rsb_4xb2-adamw-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..1390329 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d32_rsb_4xb2-adamw-80k_cityscapes-512x1024.py @@ -0,0 +1,35 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb256-rsb-a1-600e_in1k_20211228-20e21305.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='ResNet', + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint), + dilations=(1, 1, 2, 4), + strides=(1, 2, 2, 2))) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0005, weight_decay=0.05), + clip_grad=dict(max_norm=1, norm_type=2)) +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=1000, + end=80000, + by_epoch=False, + milestones=[60000, 72000], + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8-rsb_4xb2-adamw-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8-rsb_4xb2-adamw-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..b83a0b4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8-rsb_4xb2-adamw-80k_cityscapes-512x1024.py @@ -0,0 +1,33 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb256-rsb-a1-600e_in1k_20211228-20e21305.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='ResNet', + init_cfg=dict( + type='Pretrained', prefix='backbone.', checkpoint=checkpoint))) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0005, weight_decay=0.05), + clip_grad=dict(max_norm=1, norm_type=2)) +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, + end=1000), + dict( + type='MultiStepLR', + begin=1000, + end=80000, + by_epoch=False, + milestones=[60000, 72000], + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..a9dcb52 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py new file mode 100644 index 0000000..1bf4a13 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_dark-zurich-1920x1080.py @@ -0,0 +1,24 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1920, 1080), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +test_dataloader = dict( + dataset=dict( + type='DarkZurichDataset', + data_root='data/dark_zurich/', + data_prefix=dict( + img_path='rgb_anon/val/night/GOPR0356', + seg_map_path='gt/val/night/GOPR0356'), + pipeline=test_pipeline)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py new file mode 100644 index 0000000..b912589 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024_night-driving-1920x1080.py @@ -0,0 +1,25 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] + +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1920, 1080), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +test_dataloader = dict( + dataset=dict( + type='NightDrivingDataset', + data_root='data/NighttimeDrivingTest/', + data_prefix=dict( + img_path='leftImg8bit/test/night', + seg_map_path='gtCoarse_daytime_trainvaltest/test/night'), + pipeline=test_pipeline)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..6baa31b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..6ea27de --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py new file mode 100644 index 0000000..200679f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_dark-zurich-1920x1080.py @@ -0,0 +1,25 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] + +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1920, 1080), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +test_dataloader = dict( + dataset=dict( + type='DarkZurichDataset', + data_root='data/dark_zurich/', + data_prefix=dict( + img_path='rgb_anon/val/night/GOPR0356', + seg_map_path='gt/val/night/GOPR0356'), + pipeline=test_pipeline)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py new file mode 100644 index 0000000..5173813 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-512x1024_night-driving-1920x1080.py @@ -0,0 +1,25 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] + +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(1920, 1080), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +test_dataloader = dict( + dataset=dict( + type='NightDrivingDataset', + data_root='data/NighttimeDrivingTest/', + data_prefix=dict( + img_path='leftImg8bit/test/night', + seg_map_path='gtCoarse_daytime_trainvaltest/test/night'), + pipeline=test_pipeline)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..d43d30a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..3d9164f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-160k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-160k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..6185c2e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-160k_coco-stuff164k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-20k_coco-stuff10k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-20k_coco-stuff10k-512x512.py new file mode 100644 index 0000000..8c1ba2d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-20k_coco-stuff10k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/coco-stuff10k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..0f60819 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-320k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-320k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..2a9ce4c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-320k_coco-stuff164k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_320k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_coco-stuff10k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_coco-stuff10k-512x512.py new file mode 100644 index 0000000..fae57b0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_coco-stuff10k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/coco-stuff10k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_pascal-context-480x480.py new file mode 100644 index 0000000..08a2144 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_pascal-context-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/pascal_context.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + auxiliary_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_pascal-context-59-480x480.py new file mode 100644 index 0000000..b654495 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_pascal-context-59-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + auxiliary_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..c4a4611 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..bb12aed --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_coco-stuff164k-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_coco-stuff164k-512x512.py new file mode 100644 index 0000000..954a653 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_coco-stuff164k-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/coco-stuff164k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=171), + auxiliary_head=dict(num_classes=171)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_isaid-896x896.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_isaid-896x896.py new file mode 100644 index 0000000..63165b6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_isaid-896x896.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/isaid.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (896, 896) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=16), + auxiliary_head=dict(num_classes=16)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_loveda-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_loveda-512x512.py new file mode 100644 index 0000000..920729d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_loveda-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/loveda.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=7), + auxiliary_head=dict(num_classes=7)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_pascal-context-480x480.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_pascal-context-480x480.py new file mode 100644 index 0000000..a7d8247 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_pascal-context-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/pascal_context.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=60), + auxiliary_head=dict(num_classes=60), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_pascal-context-59-480x480.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_pascal-context-59-480x480.py new file mode 100644 index 0000000..b7abc1b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_pascal-context-59-480x480.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (480, 480) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=59), + auxiliary_head=dict(num_classes=59), + test_cfg=dict(mode='slide', crop_size=(480, 480), stride=(320, 320))) +optimizer = dict(type='SGD', lr=0.004, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_potsdam-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_potsdam-512x512.py new file mode 100644 index 0000000..afb3977 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_potsdam-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/potsdam.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=6), + auxiliary_head=dict(num_classes=6)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_vaihingen-512x512.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_vaihingen-512x512.py new file mode 100644 index 0000000..35322d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50-d8_4xb4-80k_vaihingen-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/vaihingen.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=6), + auxiliary_head=dict(num_classes=6)) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..64e5509 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d32_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='torchvision://resnet50', + backbone=dict(type='ResNet', dilations=(1, 1, 2, 4), strides=(1, 2, 2, 2))) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..7dd64b3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..3875c09 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './pspnet_r50-d8_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='torchvision://resnet50', backbone=dict(type='ResNet')) diff --git a/Seg_All_In_One_MMSeg/configs/resnest/README.md b/Seg_All_In_One_MMSeg/configs/resnest/README.md new file mode 100644 index 0000000..304791a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/README.md @@ -0,0 +1,54 @@ +# ResNeSt + +> [ResNeSt: Split-Attention Networks](https://arxiv.org/abs/2004.08955) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +It is well known that featuremap attention and multi-path representation are important for visual recognition. In this paper, we present a modularized architecture, which applies the channel-wise attention on different network branches to leverage their success in capturing cross-feature interactions and learning diverse representations. Our design results in a simple and unified computation block, which can be parameterized using only a few variables. Our model, named ResNeSt, outperforms EfficientNet in accuracy and latency trade-off on image classification. In addition, ResNeSt has achieved superior transfer learning results on several public benchmarks serving as the backbone, and has been adopted by the winning entries of COCO-LVIS challenge. The source code for complete system and pretrained models are publicly available. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | S-101-D8 | 512x1024 | 80000 | 11.4 | 2.39 | V100 | 77.56 | 78.98 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest/resnest_s101-d8_fcn_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/fcn_s101-d8_512x1024_80k_cityscapes/fcn_s101-d8_512x1024_80k_cityscapes_20200807_140631-f8d155b3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/fcn_s101-d8_512x1024_80k_cityscapes/fcn_s101-d8_512x1024_80k_cityscapes-20200807_140631.log.json) | +| PSPNet | S-101-D8 | 512x1024 | 80000 | 11.8 | 2.52 | V100 | 78.57 | 79.19 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest/resnest_s101-d8_pspnet_4xb2-80k_cityscapes512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/pspnet_s101-d8_512x1024_80k_cityscapes/pspnet_s101-d8_512x1024_80k_cityscapes_20200807_140631-c75f3b99.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/pspnet_s101-d8_512x1024_80k_cityscapes/pspnet_s101-d8_512x1024_80k_cityscapes-20200807_140631.log.json) | +| DeepLabV3 | S-101-D8 | 512x1024 | 80000 | 11.9 | 1.88 | V100 | 79.67 | 80.51 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest/resnest_s101-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3_s101-d8_512x1024_80k_cityscapes/deeplabv3_s101-d8_512x1024_80k_cityscapes_20200807_144429-b73c4270.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3_s101-d8_512x1024_80k_cityscapes/deeplabv3_s101-d8_512x1024_80k_cityscapes-20200807_144429.log.json) | +| DeepLabV3+ | S-101-D8 | 512x1024 | 80000 | 13.2 | 2.36 | V100 | 79.62 | 80.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3plus_s101-d8_512x1024_80k_cityscapes/deeplabv3plus_s101-d8_512x1024_80k_cityscapes_20200807_144429-1239eb43.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3plus_s101-d8_512x1024_80k_cityscapes/deeplabv3plus_s101-d8_512x1024_80k_cityscapes-20200807_144429.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | S-101-D8 | 512x512 | 160000 | 14.2 | 12.86 | V100 | 45.62 | 46.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest/resnest_s101-d8_fcn_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/fcn_s101-d8_512x512_160k_ade20k/fcn_s101-d8_512x512_160k_ade20k_20200807_145416-d3160329.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/fcn_s101-d8_512x512_160k_ade20k/fcn_s101-d8_512x512_160k_ade20k-20200807_145416.log.json) | +| PSPNet | S-101-D8 | 512x512 | 160000 | 14.2 | 13.02 | V100 | 45.44 | 46.28 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest/resnest_s101-d8_pspnet_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/pspnet_s101-d8_512x512_160k_ade20k/pspnet_s101-d8_512x512_160k_ade20k_20200807_145416-a6daa92a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/pspnet_s101-d8_512x512_160k_ade20k/pspnet_s101-d8_512x512_160k_ade20k-20200807_145416.log.json) | +| DeepLabV3 | S-101-D8 | 512x512 | 160000 | 14.6 | 9.28 | V100 | 45.71 | 46.59 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest/resnest_s101-d8_deeplabv3_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3_s101-d8_512x512_160k_ade20k/deeplabv3_s101-d8_512x512_160k_ade20k_20200807_144503-17ecabe5.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3_s101-d8_512x512_160k_ade20k/deeplabv3_s101-d8_512x512_160k_ade20k-20200807_144503.log.json) | +| DeepLabV3+ | S-101-D8 | 512x512 | 160000 | 16.2 | 11.96 | V100 | 46.47 | 47.27 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3plus_s101-d8_512x512_160k_ade20k/deeplabv3plus_s101-d8_512x512_160k_ade20k_20200807_144503-27b26226.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3plus_s101-d8_512x512_160k_ade20k/deeplabv3plus_s101-d8_512x512_160k_ade20k-20200807_144503.log.json) | + +## Citation + +```bibtex +@article{zhang2020resnest, +title={ResNeSt: Split-Attention Networks}, +author={Zhang, Hang and Wu, Chongruo and Zhang, Zhongyue and Zhu, Yi and Zhang, Zhi and Lin, Haibin and Sun, Yue and He, Tong and Muller, Jonas and Manmatha, R. and Li, Mu and Smola, Alexander}, +journal={arXiv preprint arXiv:2004.08955}, +year={2020} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/resnest/metafile.yaml b/Seg_All_In_One_MMSeg/configs/resnest/metafile.yaml new file mode 100644 index 0000000..0b8d41e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/metafile.yaml @@ -0,0 +1,193 @@ +Models: +- Name: resnest_s101-d8_fcn_4xb2-80k_cityscapes-512x1024 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.56 + mIoU(ms+flip): 78.98 + Config: configs/resnest/resnest_s101-d8_fcn_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - S-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 11.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/fcn_s101-d8_512x1024_80k_cityscapes/fcn_s101-d8_512x1024_80k_cityscapes_20200807_140631-f8d155b3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/fcn_s101-d8_512x1024_80k_cityscapes/fcn_s101-d8_512x1024_80k_cityscapes-20200807_140631.log.json + Paper: + Title: 'ResNeSt: Split-Attention Networks' + URL: https://arxiv.org/abs/2004.08955 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/resnest.py#L271 + Framework: PyTorch +- Name: resnest_s101-d8_pspnet_4xb2-80k_cityscapes512x1024 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.57 + mIoU(ms+flip): 79.19 + Config: configs/resnest/resnest_s101-d8_pspnet_4xb2-80k_cityscapes512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - S-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 11.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/pspnet_s101-d8_512x1024_80k_cityscapes/pspnet_s101-d8_512x1024_80k_cityscapes_20200807_140631-c75f3b99.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/pspnet_s101-d8_512x1024_80k_cityscapes/pspnet_s101-d8_512x1024_80k_cityscapes-20200807_140631.log.json + Paper: + Title: 'ResNeSt: Split-Attention Networks' + URL: https://arxiv.org/abs/2004.08955 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/resnest.py#L271 + Framework: PyTorch +- Name: resnest_s101-d8_deeplabv3_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.67 + mIoU(ms+flip): 80.51 + Config: configs/resnest/resnest_s101-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - S-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 11.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3_s101-d8_512x1024_80k_cityscapes/deeplabv3_s101-d8_512x1024_80k_cityscapes_20200807_144429-b73c4270.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3_s101-d8_512x1024_80k_cityscapes/deeplabv3_s101-d8_512x1024_80k_cityscapes-20200807_144429.log.json + Paper: + Title: 'ResNeSt: Split-Attention Networks' + URL: https://arxiv.org/abs/2004.08955 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/resnest.py#L271 + Framework: PyTorch +- Name: resnest_s101-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.62 + mIoU(ms+flip): 80.27 + Config: configs/resnest/resnest_s101-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - S-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 13.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3plus_s101-d8_512x1024_80k_cityscapes/deeplabv3plus_s101-d8_512x1024_80k_cityscapes_20200807_144429-1239eb43.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3plus_s101-d8_512x1024_80k_cityscapes/deeplabv3plus_s101-d8_512x1024_80k_cityscapes-20200807_144429.log.json + Paper: + Title: 'ResNeSt: Split-Attention Networks' + URL: https://arxiv.org/abs/2004.08955 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/resnest.py#L271 + Framework: PyTorch +- Name: resnest_s101-d8_fcn_4xb4-160k_ade20k-512x512 + In Collection: FCN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.62 + mIoU(ms+flip): 46.16 + Config: configs/resnest/resnest_s101-d8_fcn_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - S-101-D8 + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 14.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/fcn_s101-d8_512x512_160k_ade20k/fcn_s101-d8_512x512_160k_ade20k_20200807_145416-d3160329.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/fcn_s101-d8_512x512_160k_ade20k/fcn_s101-d8_512x512_160k_ade20k-20200807_145416.log.json + Paper: + Title: 'ResNeSt: Split-Attention Networks' + URL: https://arxiv.org/abs/2004.08955 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/resnest.py#L271 + Framework: PyTorch +- Name: resnest_s101-d8_pspnet_4xb4-160k_ade20k-512x512 + In Collection: PSPNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.44 + mIoU(ms+flip): 46.28 + Config: configs/resnest/resnest_s101-d8_pspnet_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - S-101-D8 + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 14.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/pspnet_s101-d8_512x512_160k_ade20k/pspnet_s101-d8_512x512_160k_ade20k_20200807_145416-a6daa92a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/pspnet_s101-d8_512x512_160k_ade20k/pspnet_s101-d8_512x512_160k_ade20k-20200807_145416.log.json + Paper: + Title: 'ResNeSt: Split-Attention Networks' + URL: https://arxiv.org/abs/2004.08955 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/resnest.py#L271 + Framework: PyTorch +- Name: resnest_s101-d8_deeplabv3_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3 + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.71 + mIoU(ms+flip): 46.59 + Config: configs/resnest/resnest_s101-d8_deeplabv3_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - S-101-D8 + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 14.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3_s101-d8_512x512_160k_ade20k/deeplabv3_s101-d8_512x512_160k_ade20k_20200807_144503-17ecabe5.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3_s101-d8_512x512_160k_ade20k/deeplabv3_s101-d8_512x512_160k_ade20k-20200807_144503.log.json + Paper: + Title: 'ResNeSt: Split-Attention Networks' + URL: https://arxiv.org/abs/2004.08955 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/resnest.py#L271 + Framework: PyTorch +- Name: resnest_s101-d8_deeplabv3plus_4xb4-160k_ade20k-512x512 + In Collection: DeepLabV3+ + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.47 + mIoU(ms+flip): 47.27 + Config: configs/resnest/resnest_s101-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - S-101-D8 + - DeepLabV3+ + Training Resources: 4x V100 GPUS + Memory (GB): 16.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3plus_s101-d8_512x512_160k_ade20k/deeplabv3plus_s101-d8_512x512_160k_ade20k_20200807_144503-27b26226.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/resnest/deeplabv3plus_s101-d8_512x512_160k_ade20k/deeplabv3plus_s101-d8_512x512_160k_ade20k-20200807_144503.log.json + Paper: + Title: 'ResNeSt: Split-Attention Networks' + URL: https://arxiv.org/abs/2004.08955 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/resnest.py#L271 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..7ece894 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = '../deeplabv3/deeplabv3_r101-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnest101', + backbone=dict( + type='ResNeSt', + stem_channels=128, + radix=2, + reduction_factor=4, + avg_down_stride=True)) diff --git a/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..c285230 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = '../deeplabv3/deeplabv3_r101-d8_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://resnest101', + backbone=dict( + type='ResNeSt', + stem_channels=128, + radix=2, + reduction_factor=4, + avg_down_stride=True)) diff --git a/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..5c43a95 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = '../deeplabv3plus/deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024.py' # noqa +model = dict( + pretrained='open-mmlab://resnest101', + backbone=dict( + type='ResNeSt', + stem_channels=128, + radix=2, + reduction_factor=4, + avg_down_stride=True)) diff --git a/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..ce39d37 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_deeplabv3plus_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = '../deeplabv3plus/deeplabv3plus_r101-d8_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://resnest101', + backbone=dict( + type='ResNeSt', + stem_channels=128, + radix=2, + reduction_factor=4, + avg_down_stride=True)) diff --git a/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_fcn_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_fcn_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..fc333e4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_fcn_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,9 @@ +_base_ = '../fcn/fcn_r101-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnest101', + backbone=dict( + type='ResNeSt', + stem_channels=128, + radix=2, + reduction_factor=4, + avg_down_stride=True)) diff --git a/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_fcn_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_fcn_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..af12733 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_fcn_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = '../fcn/fcn_r101-d8_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://resnest101', + backbone=dict( + type='ResNeSt', + stem_channels=128, + radix=2, + reduction_factor=4, + avg_down_stride=True)) diff --git a/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_pspnet_4xb2-80k_cityscapes512x1024.py b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_pspnet_4xb2-80k_cityscapes512x1024.py new file mode 100644 index 0000000..3aab524 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_pspnet_4xb2-80k_cityscapes512x1024.py @@ -0,0 +1,9 @@ +_base_ = '../pspnet/pspnet_r101-d8_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnest101', + backbone=dict( + type='ResNeSt', + stem_channels=128, + radix=2, + reduction_factor=4, + avg_down_stride=True)) diff --git a/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_pspnet_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_pspnet_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..66e6639 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/resnest/resnest_s101-d8_pspnet_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = '../pspnet/pspnet_r101-d8_4xb4-160k_ade20k-512x512.py' +model = dict( + pretrained='open-mmlab://resnest101', + backbone=dict( + type='ResNeSt', + stem_channels=128, + radix=2, + reduction_factor=4, + avg_down_stride=True)) diff --git a/Seg_All_In_One_MMSeg/configs/san/README.md b/Seg_All_In_One_MMSeg/configs/san/README.md new file mode 100644 index 0000000..d9940eb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/san/README.md @@ -0,0 +1,47 @@ +# SAN + +> [Side Adapter Network for Open-Vocabulary Semantic Segmentation](https://arxiv.org/abs/2302.12242) + +## Introduction + + + +Official Repo + +## Abstract + + + +This paper presents a new framework for open-vocabulary semantic segmentation with the pre-trained vision-language model, named Side Adapter Network (SAN). Our approach models the semantic segmentation task as a region recognition problem. A side network is attached to a frozen CLIP model with two branches: one for predicting mask proposals, and the other for predicting attention bias which is applied in the CLIP model to recognize the class of masks. This decoupled design has the benefit CLIP in recognizing the class of mask proposals. Since the attached side network can reuse CLIP features, it can be very light. In addition, the entire network can be trained end-to-end, allowing the side network to be adapted to the frozen CLIP model, which makes the predicted mask proposals CLIP-aware. Our approach is fast, accurate, and only adds a few additional trainable parameters. We evaluate our approach on multiple semantic segmentation benchmarks. Our method significantly outperforms other counterparts, with up to 18 times fewer trainable parameters and 19 times faster inference speed. We hope our approach will serve as a solid baseline and help ease future research in open-vocabulary semantic segmentation. + + + +
+ +
+ +## Results and models + +### COCO-Stuff164k + +| Method | Backbone | Pretrained | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | ------------ | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| SAN | ViT-B_16 | CLIP_ViT-B16 | 640x640 | 60000 | 12.61 | - | V100 | 41.93 | 41.77 | https://github.com/open-mmlab/mmsegmentation/blob/main/configs/san/san-vit-b16_coco-stuff164k-640x640.py | [model](https://download.openmmlab.com/mmsegmentation/v0.5/san/san-vit-b16_20230906-fd0a7684.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/san/san-vit-b16_20230906.log) | +| SAN | ViT-L_14 | CLIP_ViT-L14 | 640x640 | 60000 | 22.84 | - | V100 | 45.78 | 43.99 | https://github.com/open-mmlab/mmsegmentation/blob/main/configs/san/san-vit-l14_coco-stuff164k-640x640.py | [model](https://download.openmmlab.com/mmsegmentation/v0.5/san/san-vit-l14_20230907-a11e098f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/san/san-vit-l14_20230907.log) | + +## Notes + +git push +The pretrained weights in config files are converted from open_clip models using tools/model_converters/clip2mmseg.py. + +## Citation + +```bibtex +@inproceedings{xu2023side, + title={Side adapter network for open-vocabulary semantic segmentation}, + author={Xu, Mengde and Zhang, Zheng and Wei, Fangyun and Hu, Han and Bai, Xiang}, + booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition}, + pages={2945--2954}, + year={2023} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/san/metafile.yaml b/Seg_All_In_One_MMSeg/configs/san/metafile.yaml new file mode 100644 index 0000000..117d088 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/san/metafile.yaml @@ -0,0 +1,61 @@ +Collections: +- Name: SAN + License: Apache License 2.0 + Metadata: + Training Data: + - COCO-Stuff 164k + Paper: + Title: 'Side Adapter Network for Open-Vocabulary Semantic Segmentation' + URL: https://arxiv.org/abs/2302.12242 + README: configs/san/README.md + Frameworks: + - PyTorch +Models: +- Name: san-vit-b16_coco-stuff164k-640x640 + In Collection: SAN + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 41.93 + mIoU(ms+flip): 41.77 + Config: configs/san/san-vit-b16_coco-stuff164k-640x640.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - SAN + - ViT + Training Resources: 8x V100 GPUS + Memory (GB): 12.61 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/san/san-vit-b16_20230906-fd0a7684.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/san/san-vit-b16_20230906.log + Paper: + Title: 'Side Adapter Network for Open-Vocabulary Semantic Segmentation' + URL: https://arxiv.org/abs/2302.12242 + Code: https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/mmseg/models/decode_heads/san_head.py#L470 + Framework: PyTorch +- Name: san-vit-l14_coco-stuff164k-640x640 + In Collection: SAN + Results: + Task: Semantic Segmentation + Dataset: COCO-Stuff 164k + Metrics: + mIoU: 45.78 + mIoU(ms+flip): 43.99 + Config: configs/san/san-vit-l14_coco-stuff164k-640x640.py + Metadata: + Training Data: COCO-Stuff 164k + Batch Size: 16 + Architecture: + - SAN + - ViT + Training Resources: 8x V100 GPUS + Memory (GB): 12.61 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/san/san-vit-l14_20230907-a11e098f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/san/san-vit-l14_20230907.log + Paper: + Title: 'Side Adapter Network for Open-Vocabulary Semantic Segmentation' + URL: https://arxiv.org/abs/2302.12242 + Code: https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/mmseg/models/decode_heads/san_head.py#L470 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_coco-stuff164k-640x640.py b/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_coco-stuff164k-640x640.py new file mode 100644 index 0000000..4059248 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_coco-stuff164k-640x640.py @@ -0,0 +1,82 @@ +_base_ = [ + '../_base_/models/san_vit-b16.py', '../_base_/datasets/coco-stuff164k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (640, 640) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomChoiceResize', + scales=[int(640 * x * 0.1) for x in range(5, 16)], + resize_type='ResizeShortestEdge', + max_size=2560), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=1.0), + dict(type='PhotoMetricDistortion'), + dict(type='RandomFlip', prob=0.5), + dict(type='PackSegInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='ResizeShortestEdge', scale=crop_size, max_size=2560), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] + +# By default, models are trained on 4 GPUs with 8 images per GPU +train_dataloader = dict(batch_size=8, dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(batch_size=1, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/san/clip_vit-base-patch16-224_3rdparty-d08f8887.pth' # noqa +data_preprocessor = dict( + mean=[122.7709, 116.7460, 104.0937], + std=[68.5005, 66.6322, 70.3232], + size_divisor=640, + test_cfg=dict(size_divisor=32)) +model = dict( + pretrained=pretrained, + text_encoder=dict(dataset_name='coco-stuff164k'), + decode_head=dict(num_classes=171)) + +# training schedule for 60k +train_cfg = dict( + type='IterBasedTrainLoop', + max_iters=60000, + val_interval=500, + val_begin=55000) +default_hooks = dict( + checkpoint=dict( + type='CheckpointHook', + by_epoch=False, + interval=10000, + save_best='mIoU')) + +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.0001), + paramwise_cfg=dict( + custom_keys={ + 'img_encoder': dict(lr_mult=0.1, decay_mult=1.0), + 'pos_embed': dict(decay_mult=0.), + 'cls_token': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + }), + loss_scale='dynamic', + clip_grad=dict(max_norm=0.01, norm_type=2)) + +param_scheduler = [ + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=0, + end=60000, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_pascal_context-640x640.py b/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_pascal_context-640x640.py new file mode 100644 index 0000000..b164fe4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_pascal_context-640x640.py @@ -0,0 +1,56 @@ +_base_ = [ + '../_base_/models/san_vit-b16.py', + '../_base_/datasets/pascal_context_59.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (640, 640) + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='ResizeShortestEdge', scale=crop_size, max_size=2560), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +data_preprocessor = dict( + mean=[122.7709, 116.7460, 104.0937], + std=[68.5005, 66.6322, 70.3232], + size_divisor=640, + test_cfg=dict(size_divisor=32)) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='pretrain/vit_base_patch16_224.pth', + text_encoder=dict(dataset_name='pascal_context'), + decode_head=dict(num_classes=59)) + +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'pos_embed': dict(decay_mult=0.), + 'cls_token': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_voc12aug-640x640.py b/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_voc12aug-640x640.py new file mode 100644 index 0000000..62e9b26 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/san/san-vit-b16_voc12aug-640x640.py @@ -0,0 +1,65 @@ +_base_ = [ + '../_base_/models/san_vit-b16.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (640, 640) + +metainfo = dict( + classes=('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', + 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', + 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'), + palette=[[128, 0, 0], [0, 128, 0], [128, 128, 0], [0, 0, 128], + [128, 0, 128], [0, 128, 128], [128, 128, 128], [64, 0, 0], + [192, 0, 0], [64, 128, 0], [192, 128, 0], [64, 0, 128], + [192, 0, 128], [64, 128, 128], [192, 128, 128], [0, 64, 0], + [128, 64, 0], [0, 192, 0], [128, 192, 0], [0, 64, 128]]) +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='ResizeShortestEdge', scale=crop_size, max_size=2560), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict( + batch_size=1, dataset=dict(metainfo=metainfo, pipeline=test_pipeline)) +test_dataloader = val_dataloader + +data_preprocessor = dict( + mean=[122.7709, 116.7460, 104.0937], + std=[68.5005, 66.6322, 70.3232], + size_divisor=640, + test_cfg=dict(size_divisor=32)) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='pretrain/vit_base_patch16_224.pth', + text_encoder=dict(dataset_name='voc'), + decode_head=dict(num_classes=20)) + +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'pos_embed': dict(decay_mult=0.), + 'cls_token': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_coco-stuff164k-640x640.py b/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_coco-stuff164k-640x640.py new file mode 100644 index 0000000..c34328d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_coco-stuff164k-640x640.py @@ -0,0 +1,36 @@ +_base_ = ['./san-vit-b16_coco-stuff164k-640x640.py'] + +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/san/clip_vit-large-patch14-336_3rdparty-0b5df9cb.pth' # noqa +model = dict( + type='MultimodalEncoderDecoder', + pretrained=pretrained, + encoder_resolution=0.7, + image_encoder=dict( + type='VisionTransformer', + img_size=(336, 336), + patch_size=14, + patch_pad=0, + embed_dims=1024, + num_layers=18, + num_heads=16, + out_indices=(5, 11, 17), + ), + text_encoder=dict( + type='CLIPTextEncoder', + embed_dims=768, + num_layers=12, + num_heads=12, + output_dims=768, + ), + decode_head=dict( + type='SideAdapterCLIPHead', + san_cfg=dict(clip_channels=1024, cfg_decoder=dict(num_heads=16)), + maskgen_cfg=dict( + num_layers=6, + embed_dims=1024, + num_heads=16, + out_dims=768, + ))) + +# By default, models are trained on 8 GPUs with 4 images per GPU +train_dataloader = dict(batch_size=4) diff --git a/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_pascal_context-640x640.py b/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_pascal_context-640x640.py new file mode 100644 index 0000000..a9545fa --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_pascal_context-640x640.py @@ -0,0 +1,32 @@ +_base_ = ['./san-vit-b16_pascal_context-640x640.py'] + +model = dict( + type='MultimodalEncoderDecoder', + pretrained='pretrain/jx_vit_base_p16_224-80ecf9dd.pth', + encoder_resolution=0.7, + image_encoder=dict( + type='VisionTransformer', + img_size=(336, 336), + patch_size=14, + patch_pad=0, + embed_dims=1024, + num_layers=18, + num_heads=16, + out_indices=(5, 11, 17), + ), + text_encoder=dict( + type='CLIPTextEncoder', + embed_dims=768, + num_layers=12, + num_heads=12, + output_dims=768, + ), + decode_head=dict( + type='SideAdapterCLIPHead', + san_cfg=dict(clip_channels=1024, cfg_decoder=dict(num_heads=16)), + maskgen_cfg=dict( + num_layers=6, + embed_dims=1024, + num_heads=16, + out_dims=768, + ))) diff --git a/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_voc12aug-640x640.py b/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_voc12aug-640x640.py new file mode 100644 index 0000000..2f37715 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/san/san-vit-l14_voc12aug-640x640.py @@ -0,0 +1,32 @@ +_base_ = ['./san-vit-b16_voc12aug-640x640.py'] + +model = dict( + type='MultimodalEncoderDecoder', + pretrained='pretrain/jx_vit_base_p16_224-80ecf9dd.pth', + encoder_resolution=0.7, + image_encoder=dict( + type='VisionTransformer', + img_size=(336, 336), + patch_size=14, + patch_pad=0, + embed_dims=1024, + num_layers=18, + num_heads=16, + out_indices=(5, 11, 17), + ), + text_encoder=dict( + type='CLIPTextEncoder', + embed_dims=768, + num_layers=12, + num_heads=12, + output_dims=768, + ), + decode_head=dict( + type='SideAdapterCLIPHead', + san_cfg=dict(clip_channels=1024, cfg_decoder=dict(num_heads=16)), + maskgen_cfg=dict( + num_layers=6, + embed_dims=1024, + num_heads=16, + out_dims=768, + ))) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/README.md b/Seg_All_In_One_MMSeg/configs/segformer/README.md new file mode 100644 index 0000000..f8999b0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/README.md @@ -0,0 +1,101 @@ +# SegFormer + +> [SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers](https://arxiv.org/abs/2105.15203) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +We present SegFormer, a simple, efficient yet powerful semantic segmentation framework which unifies Transformers with lightweight multilayer perception (MLP) decoders. SegFormer has two appealing features: 1) SegFormer comprises a novel hierarchically structured Transformer encoder which outputs multiscale features. It does not need positional encoding, thereby avoiding the interpolation of positional codes which leads to decreased performance when the testing resolution differs from training. 2) SegFormer avoids complex decoders. The proposed MLP decoder aggregates information from different layers, and thus combining both local attention and global attention to render powerful representations. We show that this simple and lightweight design is the key to efficient segmentation on Transformers. We scale our approach up to obtain a series of models from SegFormer-B0 to SegFormer-B5, reaching significantly better performance and efficiency than previous counterparts. For example, SegFormer-B4 achieves 50.3% mIoU on ADE20K with 64M parameters, being 5x smaller and 2.2% better than the previous best method. Our best model, SegFormer-B5, achieves 84.0% mIoU on Cityscapes validation set and shows excellent zero-shot robustness on Cityscapes-C. Code will be released at: [this http URL](https://github.com/NVlabs/SegFormer). + + + +
+ +
+ +## Usage + +To use other repositories' pre-trained models, it is necessary to convert keys. + +We provide a script [`mit2mmseg.py`](../../tools/model_converters/mit2mmseg.py) in the tools directory to convert the key of models from [the official repo](https://github.com/NVlabs/SegFormer) to MMSegmentation style. + +```shell +python tools/model_converters/mit2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------: | -------------- | -------- | ----: | ------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Segformer | MIT-B0 | 512x512 | 160000 | 2.1 | 51.32 | 1080 Ti | 37.41 | 38.34 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b0_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b0_512x512_160k_ade20k/segformer_mit-b0_512x512_160k_ade20k_20210726_101530-8ffa8fda.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b0_512x512_160k_ade20k/segformer_mit-b0_512x512_160k_ade20k_20210726_101530.log.json) | +| Segformer | MIT-B1 | 512x512 | 160000 | 2.6 | 47.66 | TITAN Xp | 40.97 | 42.54 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b1_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b1_512x512_160k_ade20k/segformer_mit-b1_512x512_160k_ade20k_20210726_112106-d70e859d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b1_512x512_160k_ade20k/segformer_mit-b1_512x512_160k_ade20k_20210726_112106.log.json) | +| Segformer | MIT-B2 | 512x512 | 160000 | 3.6 | 30.88 | TITAN Xp | 45.58 | 47.03 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b2_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b2_512x512_160k_ade20k/segformer_mit-b2_512x512_160k_ade20k_20210726_112103-cbd414ac.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b2_512x512_160k_ade20k/segformer_mit-b2_512x512_160k_ade20k_20210726_112103.log.json) | +| Segformer | MIT-B3 | 512x512 | 160000 | 4.8 | 22.11 | V100 | 47.82 | 48.81 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b3_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b3_512x512_160k_ade20k/segformer_mit-b3_512x512_160k_ade20k_20210726_081410-962b98d2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b3_512x512_160k_ade20k/segformer_mit-b3_512x512_160k_ade20k_20210726_081410.log.json) | +| Segformer | MIT-B4 | 512x512 | 160000 | 6.1 | 15.45 | V100 | 48.46 | 49.76 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b4_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b4_512x512_160k_ade20k/segformer_mit-b4_512x512_160k_ade20k_20210728_183055-7f509d7d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b4_512x512_160k_ade20k/segformer_mit-b4_512x512_160k_ade20k_20210728_183055.log.json) | +| Segformer | MIT-B5 | 512x512 | 160000 | 7.2 | 11.89 | V100 | 49.13 | 50.22 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_512x512_160k_ade20k/segformer_mit-b5_512x512_160k_ade20k_20210726_145235-94cedf59.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_512x512_160k_ade20k/segformer_mit-b5_512x512_160k_ade20k_20210726_145235.log.json) | +| Segformer | MIT-B5 | 640x640 | 160000 | 11.5 | 11.30 | V100 | 49.62 | 50.36 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-640x640.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_640x640_160k_ade20k/segformer_mit-b5_640x640_160k_ade20k_20210801_121243-41d2845b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_640x640_160k_ade20k/segformer_mit-b5_640x640_160k_ade20k_20210801_121243.log.json) | + +Evaluation with AlignedResize: + +| Method | Backbone | Crop Size | Lr schd | mIoU | mIoU(ms+flip) | +| --------- | -------- | --------- | ------: | ----: | ------------- | +| Segformer | MIT-B0 | 512x512 | 160000 | 38.1 | 38.57 | +| Segformer | MIT-B1 | 512x512 | 160000 | 41.64 | 42.76 | +| Segformer | MIT-B2 | 512x512 | 160000 | 46.53 | 47.49 | +| Segformer | MIT-B3 | 512x512 | 160000 | 48.46 | 49.14 | +| Segformer | MIT-B4 | 512x512 | 160000 | 49.34 | 50.29 | +| Segformer | MIT-B5 | 512x512 | 160000 | 50.08 | 50.72 | +| Segformer | MIT-B5 | 640x640 | 160000 | 50.58 | 50.8 | + +We replace `AlignedResize` in original implementatiuon to `Resize + ResizeToMultiple`. If you want to test by +using `AlignedResize`, you can change the dataset pipeline like this: + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # resize image to multiple of 32, improve SegFormer by 0.5-1.0 mIoU. + dict(type='ResizeToMultiple', size_divisor=32), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +``` + +### Cityscapes + +The lower fps result is caused by the sliding window inference scheme (window size:1024x1024). + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| --------- | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Segformer | MIT-B0 | 1024x1024 | 160000 | 3.64 | 4.74 | V100 | 76.54 | 78.22 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b0_8x1_1024x1024_160k_cityscapes/segformer_mit-b0_8x1_1024x1024_160k_cityscapes_20211208_101857-e7f88502.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b0_8x1_1024x1024_160k_cityscapes/segformer_mit-b0_8x1_1024x1024_160k_cityscapes_20211208_101857.log.json) | +| Segformer | MIT-B1 | 1024x1024 | 160000 | 4.49 | 4.3 | V100 | 78.56 | 79.73 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b1_8xb1-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b1_8x1_1024x1024_160k_cityscapes/segformer_mit-b1_8x1_1024x1024_160k_cityscapes_20211208_064213-655c7b3f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b1_8x1_1024x1024_160k_cityscapes/segformer_mit-b1_8x1_1024x1024_160k_cityscapes_20211208_064213.log.json) | +| Segformer | MIT-B2 | 1024x1024 | 160000 | 7.42 | 3.36 | V100 | 81.08 | 82.18 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b2_8xb1-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b2_8x1_1024x1024_160k_cityscapes/segformer_mit-b2_8x1_1024x1024_160k_cityscapes_20211207_134205-6096669a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b2_8x1_1024x1024_160k_cityscapes/segformer_mit-b2_8x1_1024x1024_160k_cityscapes_20211207_134205.log.json) | +| Segformer | MIT-B3 | 1024x1024 | 160000 | 10.86 | 2.53 | V100 | 81.94 | 83.14 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b3_8xb1-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b3_8x1_1024x1024_160k_cityscapes/segformer_mit-b3_8x1_1024x1024_160k_cityscapes_20211206_224823-a8f8a177.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b3_8x1_1024x1024_160k_cityscapes/segformer_mit-b3_8x1_1024x1024_160k_cityscapes_20211206_224823.log.json) | +| Segformer | MIT-B4 | 1024x1024 | 160000 | 15.07 | 1.88 | V100 | 81.89 | 83.38 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b4_8xb1-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b4_8x1_1024x1024_160k_cityscapes/segformer_mit-b4_8x1_1024x1024_160k_cityscapes_20211207_080709-07f6c333.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b4_8x1_1024x1024_160k_cityscapes/segformer_mit-b4_8x1_1024x1024_160k_cityscapes_20211207_080709.log.json) | +| Segformer | MIT-B5 | 1024x1024 | 160000 | 18.00 | 1.39 | V100 | 82.25 | 83.48 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer/segformer_mit-b5_8xb1-160k_cityscapes-1024x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_8x1_1024x1024_160k_cityscapes/segformer_mit-b5_8x1_1024x1024_160k_cityscapes_20211206_072934-87a052ec.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_8x1_1024x1024_160k_cityscapes/segformer_mit-b5_8x1_1024x1024_160k_cityscapes_20211206_072934.log.json) | + +## Citation + +```bibtex +@article{xie2021segformer, + title={SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers}, + author={Xie, Enze and Wang, Wenhai and Yu, Zhiding and Anandkumar, Anima and Alvarez, Jose M and Luo, Ping}, + journal={arXiv preprint arXiv:2105.15203}, + year={2021} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/segformer/metafile.yaml b/Seg_All_In_One_MMSeg/configs/segformer/metafile.yaml new file mode 100644 index 0000000..7fb38d7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/metafile.yaml @@ -0,0 +1,340 @@ +Collections: +- Name: Segformer + License: Apache License 2.0 + Metadata: + Training Data: + - ADE20K + - Cityscapes + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + README: configs/segformer/README.md + Frameworks: + - PyTorch +Models: +- Name: segformer_mit-b0_8xb2-160k_ade20k-512x512 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 37.41 + mIoU(ms+flip): 38.34 + Config: configs/segformer/segformer_mit-b0_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MIT-B0 + - Segformer + Training Resources: 8x 1080 Ti GPUS + Memory (GB): 2.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b0_512x512_160k_ade20k/segformer_mit-b0_512x512_160k_ade20k_20210726_101530-8ffa8fda.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b0_512x512_160k_ade20k/segformer_mit-b0_512x512_160k_ade20k_20210726_101530.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b1_8xb2-160k_ade20k-512x512 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 40.97 + mIoU(ms+flip): 42.54 + Config: configs/segformer/segformer_mit-b1_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MIT-B1 + - Segformer + Training Resources: 8x TITAN Xp GPUS + Memory (GB): 2.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b1_512x512_160k_ade20k/segformer_mit-b1_512x512_160k_ade20k_20210726_112106-d70e859d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b1_512x512_160k_ade20k/segformer_mit-b1_512x512_160k_ade20k_20210726_112106.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b2_8xb2-160k_ade20k-512x512 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.58 + mIoU(ms+flip): 47.03 + Config: configs/segformer/segformer_mit-b2_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MIT-B2 + - Segformer + Training Resources: 8x TITAN Xp GPUS + Memory (GB): 3.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b2_512x512_160k_ade20k/segformer_mit-b2_512x512_160k_ade20k_20210726_112103-cbd414ac.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b2_512x512_160k_ade20k/segformer_mit-b2_512x512_160k_ade20k_20210726_112103.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b3_8xb2-160k_ade20k-512x512 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.82 + mIoU(ms+flip): 48.81 + Config: configs/segformer/segformer_mit-b3_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MIT-B3 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 4.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b3_512x512_160k_ade20k/segformer_mit-b3_512x512_160k_ade20k_20210726_081410-962b98d2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b3_512x512_160k_ade20k/segformer_mit-b3_512x512_160k_ade20k_20210726_081410.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b4_8xb2-160k_ade20k-512x512 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.46 + mIoU(ms+flip): 49.76 + Config: configs/segformer/segformer_mit-b4_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MIT-B4 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 6.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b4_512x512_160k_ade20k/segformer_mit-b4_512x512_160k_ade20k_20210728_183055-7f509d7d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b4_512x512_160k_ade20k/segformer_mit-b4_512x512_160k_ade20k_20210728_183055.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b5_8xb2-160k_ade20k-512x512 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 49.13 + mIoU(ms+flip): 50.22 + Config: configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MIT-B5 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 7.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_512x512_160k_ade20k/segformer_mit-b5_512x512_160k_ade20k_20210726_145235-94cedf59.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_512x512_160k_ade20k/segformer_mit-b5_512x512_160k_ade20k_20210726_145235.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b5_8xb2-160k_ade20k-640x640 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 49.62 + mIoU(ms+flip): 50.36 + Config: configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-640x640.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MIT-B5 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 11.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_640x640_160k_ade20k/segformer_mit-b5_640x640_160k_ade20k_20210801_121243-41d2845b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_640x640_160k_ade20k/segformer_mit-b5_640x640_160k_ade20k_20210801_121243.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b0_8xb1-160k_cityscapes-1024x1024 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.54 + mIoU(ms+flip): 78.22 + Config: configs/segformer/segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - MIT-B0 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 3.64 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b0_8x1_1024x1024_160k_cityscapes/segformer_mit-b0_8x1_1024x1024_160k_cityscapes_20211208_101857-e7f88502.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b0_8x1_1024x1024_160k_cityscapes/segformer_mit-b0_8x1_1024x1024_160k_cityscapes_20211208_101857.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b1_8xb1-160k_cityscapes-1024x1024 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.56 + mIoU(ms+flip): 79.73 + Config: configs/segformer/segformer_mit-b1_8xb1-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - MIT-B1 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 4.49 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b1_8x1_1024x1024_160k_cityscapes/segformer_mit-b1_8x1_1024x1024_160k_cityscapes_20211208_064213-655c7b3f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b1_8x1_1024x1024_160k_cityscapes/segformer_mit-b1_8x1_1024x1024_160k_cityscapes_20211208_064213.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b2_8xb1-160k_cityscapes-1024x1024 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 81.08 + mIoU(ms+flip): 82.18 + Config: configs/segformer/segformer_mit-b2_8xb1-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - MIT-B2 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 7.42 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b2_8x1_1024x1024_160k_cityscapes/segformer_mit-b2_8x1_1024x1024_160k_cityscapes_20211207_134205-6096669a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b2_8x1_1024x1024_160k_cityscapes/segformer_mit-b2_8x1_1024x1024_160k_cityscapes_20211207_134205.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b3_8xb1-160k_cityscapes-1024x1024 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 81.94 + mIoU(ms+flip): 83.14 + Config: configs/segformer/segformer_mit-b3_8xb1-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - MIT-B3 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 10.86 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b3_8x1_1024x1024_160k_cityscapes/segformer_mit-b3_8x1_1024x1024_160k_cityscapes_20211206_224823-a8f8a177.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b3_8x1_1024x1024_160k_cityscapes/segformer_mit-b3_8x1_1024x1024_160k_cityscapes_20211206_224823.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b4_8xb1-160k_cityscapes-1024x1024 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 81.89 + mIoU(ms+flip): 83.38 + Config: configs/segformer/segformer_mit-b4_8xb1-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - MIT-B4 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 15.07 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b4_8x1_1024x1024_160k_cityscapes/segformer_mit-b4_8x1_1024x1024_160k_cityscapes_20211207_080709-07f6c333.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b4_8x1_1024x1024_160k_cityscapes/segformer_mit-b4_8x1_1024x1024_160k_cityscapes_20211207_080709.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch +- Name: segformer_mit-b5_8xb1-160k_cityscapes-1024x1024 + In Collection: Segformer + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 82.25 + mIoU(ms+flip): 83.48 + Config: configs/segformer/segformer_mit-b5_8xb1-160k_cityscapes-1024x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - MIT-B5 + - Segformer + Training Resources: 8x V100 GPUS + Memory (GB): 18.0 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_8x1_1024x1024_160k_cityscapes/segformer_mit-b5_8x1_1024x1024_160k_cityscapes_20211206_072934-87a052ec.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segformer/segformer_mit-b5_8x1_1024x1024_160k_cityscapes/segformer_mit-b5_8x1_1024x1024_160k_cityscapes_20211206_072934.log.json + Paper: + Title: 'SegFormer: Simple and Efficient Design for Semantic Segmentation with + Transformers' + URL: https://arxiv.org/abs/2105.15203 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/mit.py#L246 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..1280047 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py @@ -0,0 +1,41 @@ +_base_ = [ + '../_base_/models/segformer_mit-b0.py', + '../_base_/datasets/cityscapes_1024x1024.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (1024, 1024) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b0_20220624-7e0fe6dd.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(init_cfg=dict(type='Pretrained', checkpoint=checkpoint)), + test_cfg=dict(mode='slide', crop_size=(1024, 1024), stride=(768, 768))) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'pos_block': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.), + 'head': dict(lr_mult=10.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] + +train_dataloader = dict(batch_size=1, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b0_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b0_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..4a9476d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b0_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,39 @@ +_base_ = [ + '../_base_/models/segformer_mit-b0.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b0_20220624-7e0fe6dd.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(init_cfg=dict(type='Pretrained', checkpoint=checkpoint)), + decode_head=dict(num_classes=150)) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'pos_block': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.), + 'head': dict(lr_mult=10.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b1_8xb1-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b1_8xb1-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..85c126e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b1_8xb1-160k_cityscapes-1024x1024.py @@ -0,0 +1,9 @@ +_base_ = ['./segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b1_20220624-02e5a6a1.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b1_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b1_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..1ff21b8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b1_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = ['./segformer_mit-b0_8xb2-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b1_20220624-02e5a6a1.pth' # noqa + +# model settings +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_heads=[1, 2, 5, 8], + num_layers=[2, 2, 2, 2]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b2_8xb1-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b2_8xb1-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..c802f27 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b2_8xb1-160k_cityscapes-1024x1024.py @@ -0,0 +1,10 @@ +_base_ = ['./segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b2_20220624-66e8bf70.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_layers=[3, 4, 6, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b2_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b2_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..0f4c1af --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b2_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = ['./segformer_mit-b0_8xb2-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b2_20220624-66e8bf70.pth' # noqa + +# model settings +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_heads=[1, 2, 5, 8], + num_layers=[3, 4, 6, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b3_8xb1-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b3_8xb1-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..9b41ad0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b3_8xb1-160k_cityscapes-1024x1024.py @@ -0,0 +1,10 @@ +_base_ = ['./segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b3_20220624-13b1141c.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_layers=[3, 4, 18, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b3_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b3_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..a2cc13d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b3_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = ['./segformer_mit-b0_8xb2-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b3_20220624-13b1141c.pth' # noqa + +# model settings +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_heads=[1, 2, 5, 8], + num_layers=[3, 4, 18, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b4_8xb1-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b4_8xb1-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..5fb1608 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b4_8xb1-160k_cityscapes-1024x1024.py @@ -0,0 +1,10 @@ +_base_ = ['./segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b4_20220624-d588d980.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_layers=[3, 8, 27, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b4_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b4_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..5f39c30 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b4_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = ['./segformer_mit-b0_8xb2-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b4_20220624-d588d980.pth' # noqa + +# model settings +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_heads=[1, 2, 5, 8], + num_layers=[3, 8, 27, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb1-160k_cityscapes-1024x1024.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb1-160k_cityscapes-1024x1024.py new file mode 100644 index 0000000..18c3c16 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb1-160k_cityscapes-1024x1024.py @@ -0,0 +1,10 @@ +_base_ = ['./segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b5_20220624-658746d9.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_layers=[3, 6, 40, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..1e9a209 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = ['./segformer_mit-b0_8xb2-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b5_20220624-658746d9.pth' # noqa + +# model settings +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_heads=[1, 2, 5, 8], + num_layers=[3, 6, 40, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-640x640.py b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-640x640.py new file mode 100644 index 0000000..a32eb7c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segformer/segformer_mit-b5_8xb2-160k_ade20k-640x640.py @@ -0,0 +1,41 @@ +_base_ = ['./segformer_mit-b0_8xb2-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segformer/mit_b5_20220624-658746d9.pth' # noqa + +# dataset settings +crop_size = (640, 640) +data_preprocessor = dict(size=crop_size) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2048, 640), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 640), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +val_dataloader = dict(batch_size=1, dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader + +# model settings +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=64, + num_heads=[1, 2, 5, 8], + num_layers=[3, 6, 40, 3]), + decode_head=dict(in_channels=[64, 128, 320, 512])) diff --git a/Seg_All_In_One_MMSeg/configs/segmenter/README.md b/Seg_All_In_One_MMSeg/configs/segmenter/README.md new file mode 100644 index 0000000..103b125 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segmenter/README.md @@ -0,0 +1,76 @@ +# Segmenter + +> [Segmenter: Transformer for Semantic Segmentation](https://arxiv.org/abs/2105.05633) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Image segmentation is often ambiguous at the level of individual image patches and requires contextual information to reach label consensus. In this paper we introduce Segmenter, a transformer model for semantic segmentation. In contrast to convolution-based methods, our approach allows to model global context already at the first layer and throughout the network. We build on the recent Vision Transformer (ViT) and extend it to semantic segmentation. To do so, we rely on the output embeddings corresponding to image patches and obtain class labels from these embeddings with a point-wise linear decoder or a mask transformer decoder. We leverage models pre-trained for image classification and show that we can fine-tune them on moderate sized datasets available for semantic segmentation. The linear decoder allows to obtain excellent results already, but the performance can be further improved by a mask transformer generating class masks. We conduct an extensive ablation study to show the impact of the different parameters, in particular the performance is better for large models and small patch sizes. Segmenter attains excellent results for semantic segmentation. It outperforms the state of the art on both ADE20K and Pascal Context datasets and is competitive on Cityscapes. + + + +
+ +
+ +## Usage + +We have provided pretrained models converted from [ViT-AugReg](https://github.com/rwightman/pytorch-image-models/blob/f55c22bebf9d8afc449d317a723231ef72e0d662/timm/models/vision_transformer.py#L54-L106). + +If you want to convert keys on your own to use the pre-trained ViT model from [Segmenter](https://github.com/rstrudel/segmenter), we also provide a script [`vitjax2mmseg.py`](../../tools/model_converters/vitjax2mmseg.py) in the tools directory to convert the key of models from [ViT-AugReg](https://github.com/rwightman/pytorch-image-models/blob/f55c22bebf9d8afc449d317a723231ef72e0d662/timm/models/vision_transformer.py#L54-L106) to MMSegmentation style. + +```shell +python tools/model_converters/vitjax2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +E.g. + +```shell +python tools/model_converters/vitjax2mmseg.py \ +Ti_16-i21k-300ep-lr_0.001-aug_none-wd_0.03-do_0.0-sd_0.0--imagenet2012-steps_20k-lr_0.03-res_384.npz \ +pretrain/vit_tiny_p16_384.pth +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +In our default setting, pretrained models and their corresponding [ViT-AugReg](https://github.com/rwightman/pytorch-image-models/blob/f55c22bebf9d8afc449d317a723231ef72e0d662/timm/models/vision_transformer.py#L54-L106) models could be defined below: + +| pretrained models | original models | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| vit_tiny_p16_384.pth | [vit_tiny_patch16_384](https://storage.googleapis.com/vit_models/augreg/Ti_16-i21k-300ep-lr_0.001-aug_none-wd_0.03-do_0.0-sd_0.0--imagenet2012-steps_20k-lr_0.03-res_384.npz) | +| vit_small_p16_384.pth | [vit_small_patch16_384](https://storage.googleapis.com/vit_models/augreg/S_16-i21k-300ep-lr_0.001-aug_light1-wd_0.03-do_0.0-sd_0.0--imagenet2012-steps_20k-lr_0.03-res_384.npz) | +| vit_base_p16_384.pth | [vit_base_patch16_384](https://storage.googleapis.com/vit_models/augreg/B_16-i21k-300ep-lr_0.001-aug_medium1-wd_0.1-do_0.0-sd_0.0--imagenet2012-steps_20k-lr_0.01-res_384.npz) | +| vit_large_p16_384.pth | [vit_large_patch16_384](https://storage.googleapis.com/vit_models/augreg/L_16-i21k-300ep-lr_0.001-aug_medium1-wd_0.1-do_0.1-sd_0.1--imagenet2012-steps_20k-lr_0.01-res_384.npz) | + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------------- | -------- | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Segmenter Mask | ViT-T_16 | 512x512 | 160000 | 1.21 | 27.98 | V100 | 39.99 | 40.83 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segmenter/segmenter_vit-t_mask_8xb1-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-t_mask_8x1_512x512_160k_ade20k/segmenter_vit-t_mask_8x1_512x512_160k_ade20k_20220105_151706-ffcf7509.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-t_mask_8x1_512x512_160k_ade20k/segmenter_vit-t_mask_8x1_512x512_160k_ade20k_20220105_151706.log.json) | +| Segmenter Linear | ViT-S_16 | 512x512 | 160000 | 1.78 | 28.07 | V100 | 45.75 | 46.82 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segmenter/segmenter_vit-s_fcn_8xb1-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-s_linear_8x1_512x512_160k_ade20k/segmenter_vit-s_linear_8x1_512x512_160k_ade20k_20220105_151713-39658c46.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-s_linear_8x1_512x512_160k_ade20k/segmenter_vit-s_linear_8x1_512x512_160k_ade20k_20220105_151713.log.json) | +| Segmenter Mask | ViT-S_16 | 512x512 | 160000 | 2.03 | 24.80 | V100 | 46.19 | 47.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segmenter/segmenter_vit-s_mask_8xb1-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-s_mask_8x1_512x512_160k_ade20k/segmenter_vit-s_mask_8x1_512x512_160k_ade20k_20220105_151706-511bb103.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-s_mask_8x1_512x512_160k_ade20k/segmenter_vit-s_mask_8x1_512x512_160k_ade20k_20220105_151706.log.json) | +| Segmenter Mask | ViT-B_16 | 512x512 | 160000 | 4.20 | 13.20 | V100 | 49.60 | 51.07 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segmenter/segmenter_vit-b_mask_8xb1-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-b_mask_8x1_512x512_160k_ade20k/segmenter_vit-b_mask_8x1_512x512_160k_ade20k_20220105_151706-bc533b08.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-b_mask_8x1_512x512_160k_ade20k/segmenter_vit-b_mask_8x1_512x512_160k_ade20k_20220105_151706.log.json) | +| Segmenter Mask | ViT-L_16 | 640x640 | 160000 | 16.56 | 2.62 | V100 | 52.16 | 53.65 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segmenter/segmenter_vit-l_mask_8xb1-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-l_mask_8x1_512x512_160k_ade20k/segmenter_vit-l_mask_8x1_512x512_160k_ade20k_20220105_162750-7ef345be.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-l_mask_8x1_512x512_160k_ade20k/segmenter_vit-l_mask_8x1_512x512_160k_ade20k_20220105_162750.log.json) | + +## Citation + +```bibtex +@inproceedings{strudel2021segmenter, + title={Segmenter: Transformer for semantic segmentation}, + author={Strudel, Robin and Garcia, Ricardo and Laptev, Ivan and Schmid, Cordelia}, + booktitle={Proceedings of the IEEE/CVF International Conference on Computer Vision}, + pages={7262--7272}, + year={2021} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/segmenter/metafile.yaml b/Seg_All_In_One_MMSeg/configs/segmenter/metafile.yaml new file mode 100644 index 0000000..ff2aa44 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segmenter/metafile.yaml @@ -0,0 +1,138 @@ +Collections: +- Name: Segmenter + License: Apache License 2.0 + Metadata: + Training Data: + - ADE20K + Paper: + Title: 'Segmenter: Transformer for Semantic Segmentation' + URL: https://arxiv.org/abs/2105.05633 + README: configs/segmenter/README.md + Frameworks: + - PyTorch +Models: +- Name: segmenter_vit-t_mask_8xb1-160k_ade20k-512x512 + In Collection: Segmenter + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 39.99 + mIoU(ms+flip): 40.83 + Config: configs/segmenter/segmenter_vit-t_mask_8xb1-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 8 + Architecture: + - ViT-T_16 + - Segmenter + - Mask + Training Resources: 8x V100 GPUS + Memory (GB): 1.21 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-t_mask_8x1_512x512_160k_ade20k/segmenter_vit-t_mask_8x1_512x512_160k_ade20k_20220105_151706-ffcf7509.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-t_mask_8x1_512x512_160k_ade20k/segmenter_vit-t_mask_8x1_512x512_160k_ade20k_20220105_151706.log.json + Paper: + Title: 'Segmenter: Transformer for Semantic Segmentation' + URL: https://arxiv.org/abs/2105.05633 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.21.0/mmseg/models/decode_heads/segmenter_mask_head.py#L15 + Framework: PyTorch +- Name: segmenter_vit-s_fcn_8xb1-160k_ade20k-512x512 + In Collection: Segmenter + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.75 + mIoU(ms+flip): 46.82 + Config: configs/segmenter/segmenter_vit-s_fcn_8xb1-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 8 + Architecture: + - ViT-S_16 + - Segmenter + - Linear + Training Resources: 8x V100 GPUS + Memory (GB): 1.78 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-s_linear_8x1_512x512_160k_ade20k/segmenter_vit-s_linear_8x1_512x512_160k_ade20k_20220105_151713-39658c46.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-s_linear_8x1_512x512_160k_ade20k/segmenter_vit-s_linear_8x1_512x512_160k_ade20k_20220105_151713.log.json + Paper: + Title: 'Segmenter: Transformer for Semantic Segmentation' + URL: https://arxiv.org/abs/2105.05633 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.21.0/mmseg/models/decode_heads/segmenter_mask_head.py#L15 + Framework: PyTorch +- Name: segmenter_vit-s_mask_8xb1-160k_ade20k-512x512 + In Collection: Segmenter + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.19 + mIoU(ms+flip): 47.85 + Config: configs/segmenter/segmenter_vit-s_mask_8xb1-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 8 + Architecture: + - ViT-S_16 + - Segmenter + - Mask + Training Resources: 8x V100 GPUS + Memory (GB): 2.03 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-s_mask_8x1_512x512_160k_ade20k/segmenter_vit-s_mask_8x1_512x512_160k_ade20k_20220105_151706-511bb103.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-s_mask_8x1_512x512_160k_ade20k/segmenter_vit-s_mask_8x1_512x512_160k_ade20k_20220105_151706.log.json + Paper: + Title: 'Segmenter: Transformer for Semantic Segmentation' + URL: https://arxiv.org/abs/2105.05633 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.21.0/mmseg/models/decode_heads/segmenter_mask_head.py#L15 + Framework: PyTorch +- Name: segmenter_vit-b_mask_8xb1-160k_ade20k-512x512 + In Collection: Segmenter + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 49.6 + mIoU(ms+flip): 51.07 + Config: configs/segmenter/segmenter_vit-b_mask_8xb1-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 8 + Architecture: + - ViT-B_16 + - Segmenter + - Mask + Training Resources: 8x V100 GPUS + Memory (GB): 4.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-b_mask_8x1_512x512_160k_ade20k/segmenter_vit-b_mask_8x1_512x512_160k_ade20k_20220105_151706-bc533b08.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-b_mask_8x1_512x512_160k_ade20k/segmenter_vit-b_mask_8x1_512x512_160k_ade20k_20220105_151706.log.json + Paper: + Title: 'Segmenter: Transformer for Semantic Segmentation' + URL: https://arxiv.org/abs/2105.05633 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.21.0/mmseg/models/decode_heads/segmenter_mask_head.py#L15 + Framework: PyTorch +- Name: segmenter_vit-l_mask_8xb1-160k_ade20k-512x512 + In Collection: Segmenter + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 52.16 + mIoU(ms+flip): 53.65 + Config: configs/segmenter/segmenter_vit-l_mask_8xb1-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 8 + Architecture: + - ViT-L_16 + - Segmenter + - Mask + Training Resources: 8x V100 GPUS + Memory (GB): 16.56 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-l_mask_8x1_512x512_160k_ade20k/segmenter_vit-l_mask_8x1_512x512_160k_ade20k_20220105_162750-7ef345be.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segmenter/segmenter_vit-l_mask_8x1_512x512_160k_ade20k/segmenter_vit-l_mask_8x1_512x512_160k_ade20k_20220105_162750.log.json + Paper: + Title: 'Segmenter: Transformer for Semantic Segmentation' + URL: https://arxiv.org/abs/2105.05633 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.21.0/mmseg/models/decode_heads/segmenter_mask_head.py#L15 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-b_mask_8xb1-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-b_mask_8xb1-160k_ade20k-512x512.py new file mode 100644 index 0000000..a4bae50 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-b_mask_8xb1-160k_ade20k-512x512.py @@ -0,0 +1,14 @@ +_base_ = [ + '../_base_/models/segmenter_vit-b16_mask.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +optimizer = dict(lr=0.001, weight_decay=0.0) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict( + # num_gpus: 8 -> batch_size: 8 + batch_size=1) +val_dataloader = dict(batch_size=1) diff --git a/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-l_mask_8xb1-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-l_mask_8xb1-160k_ade20k-512x512.py new file mode 100644 index 0000000..302acde --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-l_mask_8xb1-160k_ade20k-512x512.py @@ -0,0 +1,32 @@ +_base_ = [ + '../_base_/models/segmenter_vit-b16_mask.py', + '../_base_/datasets/ade20k_640x640.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (640, 640) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segmenter/vit_large_p16_384_20220308-d4efb41d.pth' # noqa + +model = dict( + data_preprocessor=data_preprocessor, + pretrained=checkpoint, + backbone=dict( + type='VisionTransformer', + img_size=(640, 640), + embed_dims=1024, + num_layers=24, + num_heads=16), + decode_head=dict( + type='SegmenterMaskTransformerHead', + in_channels=1024, + channels=1024, + num_heads=16, + embed_dims=1024), + test_cfg=dict(mode='slide', crop_size=(640, 640), stride=(608, 608))) + +optimizer = dict(lr=0.001, weight_decay=0.0) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict( + # num_gpus: 8 -> batch_size: 8 + batch_size=1) +val_dataloader = dict(batch_size=1) diff --git a/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-s_fcn_8xb1-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-s_fcn_8xb1-160k_ade20k-512x512.py new file mode 100644 index 0000000..dc1e4c8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-s_fcn_8xb1-160k_ade20k-512x512.py @@ -0,0 +1,14 @@ +_base_ = './segmenter_vit-s_mask_8xb1-160k_ade20k-512x512.py' + +model = dict( + decode_head=dict( + _delete_=True, + type='FCNHead', + in_channels=384, + channels=384, + num_convs=0, + dropout_ratio=0.0, + concat_input=False, + num_classes=150, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))) diff --git a/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-s_mask_8xb1-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-s_mask_8xb1-160k_ade20k-512x512.py new file mode 100644 index 0000000..b19fd41 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-s_mask_8xb1-160k_ade20k-512x512.py @@ -0,0 +1,36 @@ +_base_ = [ + '../_base_/models/segmenter_vit-b16_mask.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segmenter/vit_small_p16_384_20220308-410f6037.pth' # noqa + +backbone_norm_cfg = dict(type='LN', eps=1e-6, requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + pretrained=checkpoint, + backbone=dict( + img_size=(512, 512), + embed_dims=384, + num_heads=6, + ), + decode_head=dict( + type='SegmenterMaskTransformerHead', + in_channels=384, + channels=384, + num_classes=150, + num_layers=2, + num_heads=6, + embed_dims=384, + dropout_ratio=0.0, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))) + +optimizer = dict(lr=0.001, weight_decay=0.0) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict( + # num_gpus: 8 -> batch_size: 8 + batch_size=1) +val_dataloader = dict(batch_size=1) diff --git a/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-t_mask_8xb1-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-t_mask_8xb1-160k_ade20k-512x512.py new file mode 100644 index 0000000..221a9f9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segmenter/segmenter_vit-t_mask_8xb1-160k_ade20k-512x512.py @@ -0,0 +1,26 @@ +_base_ = [ + '../_base_/models/segmenter_vit-b16_mask.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segmenter/vit_tiny_p16_384_20220308-cce8c795.pth' # noqa + +model = dict( + data_preprocessor=data_preprocessor, + pretrained=checkpoint, + backbone=dict(embed_dims=192, num_heads=3), + decode_head=dict( + type='SegmenterMaskTransformerHead', + in_channels=192, + channels=192, + num_heads=3, + embed_dims=192)) + +optimizer = dict(lr=0.001, weight_decay=0.0) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer) +train_dataloader = dict( + # num_gpus: 8 -> batch_size: 8 + batch_size=1) +val_dataloader = dict(batch_size=1) diff --git a/Seg_All_In_One_MMSeg/configs/segnext/README.md b/Seg_All_In_One_MMSeg/configs/segnext/README.md new file mode 100644 index 0000000..d7434a0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segnext/README.md @@ -0,0 +1,63 @@ +# SegNeXt + +> [SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation](https://arxiv.org/abs/2209.08575) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +We present SegNeXt, a simple convolutional network architecture for semantic segmentation. Recent transformer-based models have dominated the field of semantic segmentation due to the efficiency of self-attention in encoding spatial information. In this paper, we show that convolutional attention is a more efficient and effective way to encode contextual information than the self-attention mechanism in transformers. By re-examining the characteristics owned by successful segmentation models, we discover several key components leading to the performance improvement of segmentation models. This motivates us to design a novel convolutional attention network that uses cheap convolutional operations. Without bells and whistles, our SegNeXt significantly improves the performance of previous state-of-the-art methods on popular benchmarks, including ADE20K, Cityscapes, COCO-Stuff, Pascal VOC, Pascal Context, and iSAID. Notably, SegNeXt outperforms EfficientNet-L2 w/ NAS-FPN and achieves 90.6% mIoU on the Pascal VOC 2012 test leaderboard using only 1/10 parameters of it. On average, SegNeXt achieves about 2.0% mIoU improvements compared to the state-of-the-art methods on the ADE20K datasets with the same or fewer computations. Code is available at [this https URL](https://github.com/uyzhang/JSeg) (Jittor) and [this https URL](https://github.com/Visual-Attention-Network/SegNeXt) (Pytorch). + + + +
+ +
+ +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | -------- | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| SegNeXt | MSCAN-T | 512x512 | 160000 | 17.88 | 52.38 | A100 | 41.50 | 42.59 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segnext/segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-t_1x16_512x512_adamw_160k_ade20k/segnext_mscan-t_1x16_512x512_adamw_160k_ade20k_20230210_140244-05bd8466.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-t_1x16_512x512_adamw_160k_ade20k/segnext_mscan-t_1x16_512x512_adamw_160k_ade20k_20230210_140244.log.json) | +| SegNeXt | MSCAN-S | 512x512 | 160000 | 21.47 | 42.27 | A100 | 44.16 | 45.81 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segnext/segnext_mscan-s_1xb16-adamw-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-s_1x16_512x512_adamw_160k_ade20k/segnext_mscan-s_1x16_512x512_adamw_160k_ade20k_20230214_113014-43013668.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-s_1x16_512x512_adamw_160k_ade20k/segnext_mscan-s_1x16_512x512_adamw_160k_ade20k_20230214_113014.log.json) | +| SegNeXt | MSCAN-B | 512x512 | 160000 | 31.03 | 35.15 | A100 | 48.03 | 49.68 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segnext/segnext_mscan-b_1xb16-adamw-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-b_1x16_512x512_adamw_160k_ade20k/segnext_mscan-b_1x16_512x512_adamw_160k_ade20k_20230209_172053-b6f6c70c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-b_1x16_512x512_adamw_160k_ade20k/segnext_mscan-b_1x16_512x512_adamw_160k_ade20k_20230209_172053.log.json) | +| SegNeXt | MSCAN-L | 512x512 | 160000 | 43.32 | 22.91 | A100 | 50.99 | 52.10 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segnext/segnext_mscan-l_1xb16-adamw-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-l_1x16_512x512_adamw_160k_ade20k/segnext_mscan-l_1x16_512x512_adamw_160k_ade20k_20230209_172055-19b14b63.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-l_1x16_512x512_adamw_160k_ade20k/segnext_mscan-l_1x16_512x512_adamw_160k_ade20k_20230209_172055.log.json) | + +Note: + +- When we integrated SegNeXt into MMSegmentation, we modified some layers' names to make them more precise and concise without changing the model architecture. Therefore, the keys of pre-trained weights are different from the [original weights](https://cloud.tsinghua.edu.cn/d/c15b25a6745946618462/), but don't worry about these changes. we have converted them and uploaded the checkpoints, you might find URL of pre-trained checkpoints in config files and can use them directly for training. + +- The total batch size is 16. We trained for SegNeXt with a single GPU as the performance degrades significantly when using`SyncBN` (mainly in `OverlapPatchEmbed` modules of `MSCAN`) of PyTorch 1.9. + +- There will be subtle differences when model testing as Non-negative Matrix Factorization (NMF) in `LightHamHead` will be initialized randomly. To control this randomness, please set the random seed when model testing. You can modify [`./tools/test.py`](https://github.com/open-mmlab/mmsegmentation/blob/main/tools/test.py) like: + +```python +def main(): + from mmengine.runner import seg_random_seed + random_seed = xxx # set random seed recorded in training log + set_random_seed(random_seed, deterministic=False) + ... +``` + +- This model performance is sensitive to the seed values used, please refer to the log file for the specific settings of the seed. If you choose a different seed, the results might differ from the table results. Take SegNeXt Large for example, its results range from 49.60 to 51.0. + +## Citation + +```bibtex +@article{guo2022segnext, + title={SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation}, + author={Guo, Meng-Hao and Lu, Cheng-Ze and Hou, Qibin and Liu, Zhengning and Cheng, Ming-Ming and Hu, Shi-Min}, + journal={arXiv preprint arXiv:2209.08575}, + year={2022} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/segnext/metafile.yaml b/Seg_All_In_One_MMSeg/configs/segnext/metafile.yaml new file mode 100644 index 0000000..3c8ff5b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segnext/metafile.yaml @@ -0,0 +1,109 @@ +Collections: +- Name: SegNeXt + License: Apache License 2.0 + Metadata: + Training Data: + - ADE20K + Paper: + Title: 'SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation' + URL: https://arxiv.org/abs/2209.08575 + README: configs/segnext/README.md + Frameworks: + - PyTorch +Models: +- Name: segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512 + In Collection: SegNeXt + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 41.5 + mIoU(ms+flip): 42.59 + Config: configs/segnext/segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MSCAN-T + - SegNeXt + Training Resources: 1x A100 GPUS + Memory (GB): 17.88 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-t_1x16_512x512_adamw_160k_ade20k/segnext_mscan-t_1x16_512x512_adamw_160k_ade20k_20230210_140244-05bd8466.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-t_1x16_512x512_adamw_160k_ade20k/segnext_mscan-t_1x16_512x512_adamw_160k_ade20k_20230210_140244.log.json + Paper: + Title: 'SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation' + URL: https://arxiv.org/abs/2209.08575 + Code: https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/backbones/mscan.py#L328 + Framework: PyTorch +- Name: segnext_mscan-s_1xb16-adamw-160k_ade20k-512x512 + In Collection: SegNeXt + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.16 + mIoU(ms+flip): 45.81 + Config: configs/segnext/segnext_mscan-s_1xb16-adamw-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MSCAN-S + - SegNeXt + Training Resources: 1x A100 GPUS + Memory (GB): 21.47 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-s_1x16_512x512_adamw_160k_ade20k/segnext_mscan-s_1x16_512x512_adamw_160k_ade20k_20230214_113014-43013668.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-s_1x16_512x512_adamw_160k_ade20k/segnext_mscan-s_1x16_512x512_adamw_160k_ade20k_20230214_113014.log.json + Paper: + Title: 'SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation' + URL: https://arxiv.org/abs/2209.08575 + Code: https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/backbones/mscan.py#L328 + Framework: PyTorch +- Name: segnext_mscan-b_1xb16-adamw-160k_ade20k-512x512 + In Collection: SegNeXt + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.03 + mIoU(ms+flip): 49.68 + Config: configs/segnext/segnext_mscan-b_1xb16-adamw-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MSCAN-B + - SegNeXt + Training Resources: 1x A100 GPUS + Memory (GB): 31.03 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-b_1x16_512x512_adamw_160k_ade20k/segnext_mscan-b_1x16_512x512_adamw_160k_ade20k_20230209_172053-b6f6c70c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-b_1x16_512x512_adamw_160k_ade20k/segnext_mscan-b_1x16_512x512_adamw_160k_ade20k_20230209_172053.log.json + Paper: + Title: 'SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation' + URL: https://arxiv.org/abs/2209.08575 + Code: https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/backbones/mscan.py#L328 + Framework: PyTorch +- Name: segnext_mscan-l_1xb16-adamw-160k_ade20k-512x512 + In Collection: SegNeXt + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 50.99 + mIoU(ms+flip): 52.1 + Config: configs/segnext/segnext_mscan-l_1xb16-adamw-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - MSCAN-L + - SegNeXt + Training Resources: 1x A100 GPUS + Memory (GB): 43.32 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-l_1x16_512x512_adamw_160k_ade20k/segnext_mscan-l_1x16_512x512_adamw_160k_ade20k_20230209_172055-19b14b63.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/segnext/segnext_mscan-l_1x16_512x512_adamw_160k_ade20k/segnext_mscan-l_1x16_512x512_adamw_160k_ade20k_20230209_172055.log.json + Paper: + Title: 'SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation' + URL: https://arxiv.org/abs/2209.08575 + Code: https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/backbones/mscan.py#L328 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-b_1xb16-adamw-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-b_1xb16-adamw-160k_ade20k-512x512.py new file mode 100644 index 0000000..000f448 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-b_1xb16-adamw-160k_ade20k-512x512.py @@ -0,0 +1,28 @@ +_base_ = './segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py' + +# model settings +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segnext/mscan_b_20230227-3ab7d230.pth' # noqa +ham_norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + type='EncoderDecoder', + backbone=dict( + embed_dims=[64, 128, 320, 512], + depths=[3, 3, 12, 3], + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + drop_path_rate=0.1, + norm_cfg=dict(type='BN', requires_grad=True)), + decode_head=dict( + type='LightHamHead', + in_channels=[128, 320, 512], + in_index=[1, 2, 3], + channels=512, + ham_channels=512, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=ham_norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-l_1xb16-adamw-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-l_1xb16-adamw-160k_ade20k-512x512.py new file mode 100644 index 0000000..212d0a8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-l_1xb16-adamw-160k_ade20k-512x512.py @@ -0,0 +1,27 @@ +_base_ = './segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py' +# model settings +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segnext/mscan_l_20230227-cef260d4.pth' # noqa +ham_norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + type='EncoderDecoder', + backbone=dict( + embed_dims=[64, 128, 320, 512], + depths=[3, 5, 27, 3], + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + drop_path_rate=0.3, + norm_cfg=dict(type='BN', requires_grad=True)), + decode_head=dict( + type='LightHamHead', + in_channels=[128, 320, 512], + in_index=[1, 2, 3], + channels=1024, + ham_channels=1024, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=ham_norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-s_1xb16-adamw-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-s_1xb16-adamw-160k_ade20k-512x512.py new file mode 100644 index 0000000..9a90779 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-s_1xb16-adamw-160k_ade20k-512x512.py @@ -0,0 +1,27 @@ +_base_ = './segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py' +# model settings +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segnext/mscan_s_20230227-f33ccdf2.pth' # noqa +ham_norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +model = dict( + type='EncoderDecoder', + backbone=dict( + embed_dims=[64, 128, 320, 512], + depths=[2, 2, 4, 2], + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + norm_cfg=dict(type='BN', requires_grad=True)), + decode_head=dict( + type='LightHamHead', + in_channels=[128, 320, 512], + in_index=[1, 2, 3], + channels=256, + ham_channels=256, + ham_kwargs=dict(MD_R=16), + dropout_ratio=0.1, + num_classes=150, + norm_cfg=ham_norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py new file mode 100644 index 0000000..c8d6da8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/segnext/segnext_mscan-t_1xb16-adamw-160k_ade20k-512x512.py @@ -0,0 +1,84 @@ +_base_ = [ + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py', + '../_base_/datasets/ade20k.py' +] +# model settings +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/segnext/mscan_t_20230227-119e8c9f.pth' # noqa +ham_norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) +crop_size = (512, 512) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size=(512, 512), + test_cfg=dict(size_divisor=32)) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='MSCAN', + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + embed_dims=[32, 64, 160, 256], + mlp_ratios=[8, 8, 4, 4], + drop_rate=0.0, + drop_path_rate=0.1, + depths=[3, 3, 5, 2], + attention_kernel_sizes=[5, [1, 7], [1, 11], [1, 21]], + attention_kernel_paddings=[2, [0, 3], [0, 5], [0, 10]], + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='BN', requires_grad=True)), + decode_head=dict( + type='LightHamHead', + in_channels=[64, 160, 256], + in_index=[1, 2, 3], + channels=256, + ham_channels=256, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=ham_norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + ham_kwargs=dict( + MD_S=1, + MD_R=16, + train_steps=6, + eval_steps=7, + inv_t=100, + rand_init=True)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +# dataset settings +train_dataloader = dict(batch_size=16) + +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'pos_block': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.), + 'head': dict(lr_mult=10.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/sem_fpn/README.md b/Seg_All_In_One_MMSeg/configs/sem_fpn/README.md new file mode 100644 index 0000000..697cf50 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/sem_fpn/README.md @@ -0,0 +1,51 @@ +# Semantic FPN + +> [Panoptic Feature Pyramid Networks](https://arxiv.org/abs/1901.02446) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +The recently introduced panoptic segmentation task has renewed our community's interest in unifying the tasks of instance segmentation (for thing classes) and semantic segmentation (for stuff classes). However, current state-of-the-art methods for this joint task use separate and dissimilar networks for instance and semantic segmentation, without performing any shared computation. In this work, we aim to unify these methods at the architectural level, designing a single network for both tasks. Our approach is to endow Mask R-CNN, a popular instance segmentation method, with a semantic segmentation branch using a shared Feature Pyramid Network (FPN) backbone. Surprisingly, this simple baseline not only remains effective for instance segmentation, but also yields a lightweight, top-performing method for semantic segmentation. In this work, we perform a detailed study of this minimally extended version of Mask R-CNN with FPN, which we refer to as Panoptic FPN, and show it is a robust and accurate baseline for both tasks. Given its effectiveness and conceptual simplicity, we hope our method can serve as a strong baseline and aid future research in panoptic segmentation. + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FPN | R-50 | 512x1024 | 80000 | 2.8 | 13.54 | V100 | 74.52 | 76.08 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/sem_fpn/fpn_r50_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r50_512x1024_80k_cityscapes/fpn_r50_512x1024_80k_cityscapes_20200717_021437-94018a0d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r50_512x1024_80k_cityscapes/fpn_r50_512x1024_80k_cityscapes-20200717_021437.log.json) | +| FPN | R-101 | 512x1024 | 80000 | 3.9 | 10.29 | V100 | 75.80 | 77.40 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/sem_fpn/fpn_r101_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r101_512x1024_80k_cityscapes/fpn_r101_512x1024_80k_cityscapes_20200717_012416-c5800d4c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r101_512x1024_80k_cityscapes/fpn_r101_512x1024_80k_cityscapes-20200717_012416.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------: | -------------- | ------ | ----: | ------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| FPN | R-50 | 512x512 | 160000 | 4.9 | 55.77 | V100 | 37.49 | 39.09 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/sem_fpn/fpn_r50_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r50_512x512_160k_ade20k/fpn_r50_512x512_160k_ade20k_20200718_131734-5b5a6ab9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r50_512x512_160k_ade20k/fpn_r50_512x512_160k_ade20k-20200718_131734.log.json) | +| FPN | R-101 | 512x512 | 160000 | 5.9 | 40.58 | V100 | 39.35 | 40.72 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/sem_fpn/fpn_r101_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r101_512x512_160k_ade20k/fpn_r101_512x512_160k_ade20k_20200718_131734-306b5004.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r101_512x512_160k_ade20k/fpn_r101_512x512_160k_ade20k-20200718_131734.log.json) | + +## Citation + +```bibtex +@inproceedings{kirillov2019panoptic, + title={Panoptic feature pyramid networks}, + author={Kirillov, Alexander and Girshick, Ross and He, Kaiming and Doll{\'a}r, Piotr}, + booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition}, + pages={6399--6408}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r101_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r101_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..1e9bcfb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r101_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './fpn_r50_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r101_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r101_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..adad1a4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r101_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,5 @@ +_base_ = './fpn_r50_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r50_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r50_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..bf71d38 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r50_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/fpn_r50.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r50_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r50_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..4e4bc57 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/sem_fpn/fpn_r50_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = [ + '../_base_/models/fpn_r50.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, decode_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/sem_fpn/metafile.yaml b/Seg_All_In_One_MMSeg/configs/sem_fpn/metafile.yaml new file mode 100644 index 0000000..e734897 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/sem_fpn/metafile.yaml @@ -0,0 +1,110 @@ +Collections: +- Name: FPN + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + Paper: + Title: Panoptic Feature Pyramid Networks + URL: https://arxiv.org/abs/1901.02446 + README: configs/sem_fpn/README.md + Frameworks: + - PyTorch +Models: +- Name: fpn_r50_4xb2-80k_cityscapes-512x1024 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.52 + mIoU(ms+flip): 76.08 + Config: configs/sem_fpn/fpn_r50_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50 + - FPN + Training Resources: 4x V100 GPUS + Memory (GB): 2.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r50_512x1024_80k_cityscapes/fpn_r50_512x1024_80k_cityscapes_20200717_021437-94018a0d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r50_512x1024_80k_cityscapes/fpn_r50_512x1024_80k_cityscapes-20200717_021437.log.json + Paper: + Title: Panoptic Feature Pyramid Networks + URL: https://arxiv.org/abs/1901.02446 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fpn_head.py#L12 + Framework: PyTorch +- Name: fpn_r101_4xb2-80k_cityscapes-512x1024 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 75.8 + mIoU(ms+flip): 77.4 + Config: configs/sem_fpn/fpn_r101_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101 + - FPN + Training Resources: 4x V100 GPUS + Memory (GB): 3.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r101_512x1024_80k_cityscapes/fpn_r101_512x1024_80k_cityscapes_20200717_012416-c5800d4c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r101_512x1024_80k_cityscapes/fpn_r101_512x1024_80k_cityscapes-20200717_012416.log.json + Paper: + Title: Panoptic Feature Pyramid Networks + URL: https://arxiv.org/abs/1901.02446 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fpn_head.py#L12 + Framework: PyTorch +- Name: fpn_r50_4xb4-160k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 37.49 + mIoU(ms+flip): 39.09 + Config: configs/sem_fpn/fpn_r50_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50 + - FPN + Training Resources: 4x V100 GPUS + Memory (GB): 4.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r50_512x512_160k_ade20k/fpn_r50_512x512_160k_ade20k_20200718_131734-5b5a6ab9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r50_512x512_160k_ade20k/fpn_r50_512x512_160k_ade20k-20200718_131734.log.json + Paper: + Title: Panoptic Feature Pyramid Networks + URL: https://arxiv.org/abs/1901.02446 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fpn_head.py#L12 + Framework: PyTorch +- Name: fpn_r101_4xb4-160k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 39.35 + mIoU(ms+flip): 40.72 + Config: configs/sem_fpn/fpn_r101_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101 + - FPN + Training Resources: 4x V100 GPUS + Memory (GB): 5.9 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r101_512x512_160k_ade20k/fpn_r101_512x512_160k_ade20k_20200718_131734-306b5004.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/sem_fpn/fpn_r101_512x512_160k_ade20k/fpn_r101_512x512_160k_ade20k-20200718_131734.log.json + Paper: + Title: Panoptic Feature Pyramid Networks + URL: https://arxiv.org/abs/1901.02446 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/fpn_head.py#L12 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/setr/README.md b/Seg_All_In_One_MMSeg/configs/setr/README.md new file mode 100644 index 0000000..15be6ec --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/README.md @@ -0,0 +1,74 @@ +# SETR + +> [Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective with Transformers](https://arxiv.org/abs/2012.15840) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Most recent semantic segmentation methods adopt a fully-convolutional network (FCN) with an encoder-decoder architecture. The encoder progressively reduces the spatial resolution and learns more abstract/semantic visual concepts with larger receptive fields. Since context modeling is critical for segmentation, the latest efforts have been focused on increasing the receptive field, through either dilated/atrous convolutions or inserting attention modules. However, the encoder-decoder based FCN architecture remains unchanged. In this paper, we aim to provide an alternative perspective by treating semantic segmentation as a sequence-to-sequence prediction task. Specifically, we deploy a pure transformer (ie, without convolution and resolution reduction) to encode an image as a sequence of patches. With the global context modeled in every layer of the transformer, this encoder can be combined with a simple decoder to provide a powerful segmentation model, termed SEgmentation TRansformer (SETR). Extensive experiments show that SETR achieves new state of the art on ADE20K (50.28% mIoU), Pascal Context (55.83% mIoU) and competitive results on Cityscapes. Particularly, we achieve the first position in the highly competitive ADE20K test server leaderboard on the day of submission. + + + +
+ +
+ +```None +This head has two version head. +``` + +## Usage + +You can download the pretrain from [here](https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_large_p16_384-b3be5167.pth). Then you can convert its keys with the script `vit2mmseg.py` in the tools directory. + +```shell +python tools/model_converters/vit2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +E.g. + +```shell +python tools/model_converters/vit2mmseg.py \ +jx_vit_large_p16_384-b3be5167.pth pretrain/vit_large_p16.pth +``` + +This script convert the model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Batch Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ---------- | ------- | -------- | -------------- | ------ | ----- | ------------: | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| SETR Naive | ViT-L | 512x512 | 16 | 160000 | 18.40 | 4.72 | V100 | 48.28 | 49.56 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr/setr_vit-l_naive_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_naive_512x512_160k_b16_ade20k/setr_naive_512x512_160k_b16_ade20k_20210619_191258-061f24f5.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_naive_512x512_160k_b16_ade20k/setr_naive_512x512_160k_b16_ade20k_20210619_191258.log.json) | +| SETR PUP | ViT-L | 512x512 | 16 | 160000 | 19.54 | 4.50 | V100 | 48.24 | 49.99 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr/setr_vit-l_pup_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_pup_512x512_160k_b16_ade20k/setr_pup_512x512_160k_b16_ade20k_20210619_191343-7e0ce826.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_pup_512x512_160k_b16_ade20k/setr_pup_512x512_160k_b16_ade20k_20210619_191343.log.json) | +| SETR MLA | ViT-L | 512x512 | 8 | 160000 | 10.96 | - | V100 | 47.34 | 49.05 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr/setr_vit-l-mla_8xb1-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_512x512_160k_b8_ade20k/setr_mla_512x512_160k_b8_ade20k_20210619_191118-c6d21df0.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_512x512_160k_b8_ade20k/setr_mla_512x512_160k_b8_ade20k_20210619_191118.log.json) | +| SETR MLA | ViT-L | 512x512 | 16 | 160000 | 17.30 | 5.25 | V100 | 47.39 | 49.37 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr/setr_vit-l_mla_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_512x512_160k_b16_ade20k/setr_mla_512x512_160k_b16_ade20k_20210619_191057-f9741de7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_512x512_160k_b16_ade20k/setr_mla_512x512_160k_b16_ade20k_20210619_191057.log.json) | + +### Cityscapes + +| Method | Backbone | Crop Size | Batch Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | -------- | --------- | ---------- | ------- | -------- | -------------- | ------ | ----- | ------------: | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| SETR Naive | ViT-L | 768x768 | 8 | 80000 | 24.06 | 0.39 | V100 | 78.10 | 80.22 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr/setr_vit-l_naive_8xb1-80k_cityscapes-768x768.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_naive_vit-large_8x1_768x768_80k_cityscapes/setr_naive_vit-large_8x1_768x768_80k_cityscapes_20211123_000505-20728e80.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_naive_vit-large_8x1_768x768_80k_cityscapes/setr_naive_vit-large_8x1_768x768_80k_cityscapes_20211123_000505.log.json) | +| SETR PUP | ViT-L | 768x768 | 8 | 80000 | 27.96 | 0.37 | V100 | 79.21 | 81.02 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr/setr_vit-l_pup_8xb1-80k_cityscapes-768x768.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_pup_vit-large_8x1_768x768_80k_cityscapes/setr_pup_vit-large_8x1_768x768_80k_cityscapes_20211122_155115-f6f37b8f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_pup_vit-large_8x1_768x768_80k_cityscapes/setr_pup_vit-large_8x1_768x768_80k_cityscapes_20211122_155115.log.json) | +| SETR MLA | ViT-L | 768x768 | 8 | 80000 | 24.10 | 0.41 | V100 | 77.00 | 79.59 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr/setr_vit-l_mla_8xb1-80k_cityscapes-768x768.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_vit-large_8x1_768x768_80k_cityscapes/setr_mla_vit-large_8x1_768x768_80k_cityscapes_20211119_101003-7f8dccbe.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_vit-large_8x1_768x768_80k_cityscapes/setr_mla_vit-large_8x1_768x768_80k_cityscapes_20211119_101003.log.json) | + +## Citation + +```bibtex +@article{zheng2020rethinking, + title={Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective with Transformers}, + author={Zheng, Sixiao and Lu, Jiachen and Zhao, Hengshuang and Zhu, Xiatian and Luo, Zekun and Wang, Yabiao and Fu, Yanwei and Feng, Jianfeng and Xiang, Tao and Torr, Philip HS and others}, + journal={arXiv preprint arXiv:2012.15840}, + year={2020} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/setr/metafile.yaml b/Seg_All_In_One_MMSeg/configs/setr/metafile.yaml new file mode 100644 index 0000000..8e6bc08 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/metafile.yaml @@ -0,0 +1,197 @@ +Collections: +- Name: SETR + License: Apache License 2.0 + Metadata: + Training Data: + - ADE20K + - Cityscapes + Paper: + Title: Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective + with Transformers + URL: https://arxiv.org/abs/2012.15840 + README: configs/setr/README.md + Frameworks: + - PyTorch +Models: +- Name: setr_vit-l_naive_8xb2-160k_ade20k-512x512 + In Collection: SETR + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.28 + mIoU(ms+flip): 49.56 + Config: configs/setr/setr_vit-l_naive_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ViT-L + - SETR + - Naive + Training Resources: 8x V100 GPUS + Memory (GB): 18.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_naive_512x512_160k_b16_ade20k/setr_naive_512x512_160k_b16_ade20k_20210619_191258-061f24f5.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_naive_512x512_160k_b16_ade20k/setr_naive_512x512_160k_b16_ade20k_20210619_191258.log.json + Paper: + Title: Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective + with Transformers + URL: https://arxiv.org/abs/2012.15840 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/setr_up_head.py#L11 + Framework: PyTorch +- Name: setr_vit-l_pup_8xb2-160k_ade20k-512x512 + In Collection: SETR + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.24 + mIoU(ms+flip): 49.99 + Config: configs/setr/setr_vit-l_pup_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ViT-L + - SETR + - PUP + Training Resources: 8x V100 GPUS + Memory (GB): 19.54 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_pup_512x512_160k_b16_ade20k/setr_pup_512x512_160k_b16_ade20k_20210619_191343-7e0ce826.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_pup_512x512_160k_b16_ade20k/setr_pup_512x512_160k_b16_ade20k_20210619_191343.log.json + Paper: + Title: Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective + with Transformers + URL: https://arxiv.org/abs/2012.15840 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/setr_up_head.py#L11 + Framework: PyTorch +- Name: setr_vit-l-mla_8xb1-160k_ade20k-512x512 + In Collection: SETR + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.34 + mIoU(ms+flip): 49.05 + Config: configs/setr/setr_vit-l-mla_8xb1-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 8 + Architecture: + - ViT-L + - SETR + - MLA + Training Resources: 8x V100 GPUS + Memory (GB): 10.96 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_512x512_160k_b8_ade20k/setr_mla_512x512_160k_b8_ade20k_20210619_191118-c6d21df0.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_512x512_160k_b8_ade20k/setr_mla_512x512_160k_b8_ade20k_20210619_191118.log.json + Paper: + Title: Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective + with Transformers + URL: https://arxiv.org/abs/2012.15840 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/setr_up_head.py#L11 + Framework: PyTorch +- Name: setr_vit-l_mla_8xb2-160k_ade20k-512x512 + In Collection: SETR + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.39 + mIoU(ms+flip): 49.37 + Config: configs/setr/setr_vit-l_mla_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ViT-L + - SETR + - MLA + Training Resources: 8x V100 GPUS + Memory (GB): 17.3 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_512x512_160k_b16_ade20k/setr_mla_512x512_160k_b16_ade20k_20210619_191057-f9741de7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_512x512_160k_b16_ade20k/setr_mla_512x512_160k_b16_ade20k_20210619_191057.log.json + Paper: + Title: Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective + with Transformers + URL: https://arxiv.org/abs/2012.15840 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/setr_up_head.py#L11 + Framework: PyTorch +- Name: setr_vit-l_naive_8xb1-80k_cityscapes-768x768 + In Collection: SETR + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.1 + mIoU(ms+flip): 80.22 + Config: configs/setr/setr_vit-l_naive_8xb1-80k_cityscapes-768x768.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - ViT-L + - SETR + - Naive + Training Resources: 8x V100 GPUS + Memory (GB): 24.06 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_naive_vit-large_8x1_768x768_80k_cityscapes/setr_naive_vit-large_8x1_768x768_80k_cityscapes_20211123_000505-20728e80.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_naive_vit-large_8x1_768x768_80k_cityscapes/setr_naive_vit-large_8x1_768x768_80k_cityscapes_20211123_000505.log.json + Paper: + Title: Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective + with Transformers + URL: https://arxiv.org/abs/2012.15840 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/setr_up_head.py#L11 + Framework: PyTorch +- Name: setr_vit-l_pup_8xb1-80k_cityscapes-768x768 + In Collection: SETR + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.21 + mIoU(ms+flip): 81.02 + Config: configs/setr/setr_vit-l_pup_8xb1-80k_cityscapes-768x768.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - ViT-L + - SETR + - PUP + Training Resources: 8x V100 GPUS + Memory (GB): 27.96 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_pup_vit-large_8x1_768x768_80k_cityscapes/setr_pup_vit-large_8x1_768x768_80k_cityscapes_20211122_155115-f6f37b8f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_pup_vit-large_8x1_768x768_80k_cityscapes/setr_pup_vit-large_8x1_768x768_80k_cityscapes_20211122_155115.log.json + Paper: + Title: Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective + with Transformers + URL: https://arxiv.org/abs/2012.15840 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/setr_up_head.py#L11 + Framework: PyTorch +- Name: setr_vit-l_mla_8xb1-80k_cityscapes-768x768 + In Collection: SETR + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.0 + mIoU(ms+flip): 79.59 + Config: configs/setr/setr_vit-l_mla_8xb1-80k_cityscapes-768x768.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - ViT-L + - SETR + - MLA + Training Resources: 8x V100 GPUS + Memory (GB): 24.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_vit-large_8x1_768x768_80k_cityscapes/setr_mla_vit-large_8x1_768x768_80k_cityscapes_20211119_101003-7f8dccbe.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/setr/setr_mla_vit-large_8x1_768x768_80k_cityscapes/setr_mla_vit-large_8x1_768x768_80k_cityscapes_20211119_101003.log.json + Paper: + Title: Rethinking Semantic Segmentation from a Sequence-to-Sequence Perspective + with Transformers + URL: https://arxiv.org/abs/2012.15840 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/setr_up_head.py#L11 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l-mla_8xb1-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l-mla_8xb1-160k_ade20k-512x512.py new file mode 100644 index 0000000..1c6e284 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l-mla_8xb1-160k_ade20k-512x512.py @@ -0,0 +1,90 @@ +_base_ = [ + '../_base_/models/setr_mla.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + img_size=(512, 512), + drop_rate=0., + init_cfg=dict( + type='Pretrained', checkpoint='pretrain/vit_large_p16.pth')), + decode_head=dict(num_classes=150), + auxiliary_head=[ + dict( + type='FCNHead', + in_channels=256, + channels=256, + in_index=0, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=0, + kernel_size=1, + concat_input=False, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='FCNHead', + in_channels=256, + channels=256, + in_index=1, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=0, + kernel_size=1, + concat_input=False, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='FCNHead', + in_channels=256, + channels=256, + in_index=2, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=0, + kernel_size=1, + concat_input=False, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='FCNHead', + in_channels=256, + channels=256, + in_index=3, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=0, + kernel_size=1, + concat_input=False, + num_classes=150, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + ], + test_cfg=dict(mode='slide', crop_size=(512, 512), stride=(341, 341)), +) + +optimizer = dict(lr=0.001, weight_decay=0.0) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + paramwise_cfg=dict(custom_keys={'head': dict(lr_mult=10.)})) +# num_gpus: 8 -> batch_size: 8 +train_dataloader = dict(batch_size=1) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_mla_8xb1-80k_cityscapes-768x768.py b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_mla_8xb1-80k_cityscapes-768x768.py new file mode 100644 index 0000000..026557f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_mla_8xb1-80k_cityscapes-768x768.py @@ -0,0 +1,23 @@ +_base_ = [ + '../_base_/models/setr_mla.py', '../_base_/datasets/cityscapes_768x768.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (768, 768) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + drop_rate=0, + init_cfg=dict( + type='Pretrained', checkpoint='pretrain/vit_large_p16.pth')), + test_cfg=dict(mode='slide', crop_size=(768, 768), stride=(512, 512))) + +optimizer = dict(lr=0.002, weight_decay=0.0) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + paramwise_cfg=dict(custom_keys={'head': dict(lr_mult=10.)})) +train_dataloader = dict(batch_size=1) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_mla_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_mla_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..4d3fb7d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_mla_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,6 @@ +_base_ = ['./setr_vit-l-mla_8xb1-160k_ade20k-512x512.py'] + +# num_gpus: 8 -> batch_size: 16 +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_naive_8xb1-80k_cityscapes-768x768.py b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_naive_8xb1-80k_cityscapes-768x768.py new file mode 100644 index 0000000..db49317 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_naive_8xb1-80k_cityscapes-768x768.py @@ -0,0 +1,24 @@ +_base_ = [ + '../_base_/models/setr_naive.py', + '../_base_/datasets/cityscapes_768x768.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (768, 768) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + drop_rate=0., + init_cfg=dict( + type='Pretrained', checkpoint='pretrain/vit_large_p16.pth')), + test_cfg=dict(mode='slide', crop_size=(768, 768), stride=(512, 512))) + +optimizer = dict(weight_decay=0.0) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + paramwise_cfg=dict(custom_keys={'head': dict(lr_mult=10.)})) +train_dataloader = dict(batch_size=1) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_naive_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_naive_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..109996c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_naive_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,72 @@ +_base_ = [ + '../_base_/models/setr_naive.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + img_size=(512, 512), + drop_rate=0., + init_cfg=dict( + type='Pretrained', checkpoint='pretrain/vit_large_p16.pth')), + decode_head=dict(num_classes=150), + auxiliary_head=[ + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=0, + num_classes=150, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=2, + kernel_size=1, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=1, + num_classes=150, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=2, + kernel_size=1, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=2, + num_classes=150, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=2, + kernel_size=1, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)) + ], + test_cfg=dict(mode='slide', crop_size=(512, 512), stride=(341, 341)), +) + +optimizer = dict(lr=0.01, weight_decay=0.0) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + paramwise_cfg=dict(custom_keys={'head': dict(lr_mult=10.)})) +# num_gpus: 8 -> batch_size: 16 +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_pup_8xb1-80k_cityscapes-768x768.py b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_pup_8xb1-80k_cityscapes-768x768.py new file mode 100644 index 0000000..999ab18 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_pup_8xb1-80k_cityscapes-768x768.py @@ -0,0 +1,70 @@ +_base_ = [ + '../_base_/models/setr_pup.py', '../_base_/datasets/cityscapes_768x768.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (768, 768) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +crop_size = (768, 768) +model = dict( + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + drop_rate=0., + init_cfg=dict( + type='Pretrained', checkpoint='pretrain/vit_large_p16.pth')), + auxiliary_head=[ + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=0, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=2, + up_scale=4, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=1, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=2, + up_scale=4, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=2, + num_classes=19, + dropout_ratio=0, + norm_cfg=norm_cfg, + num_convs=2, + up_scale=4, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)) + ], + test_cfg=dict(mode='slide', crop_size=crop_size, stride=(512, 512))) + +optimizer = dict(weight_decay=0.0) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + paramwise_cfg=dict(custom_keys={'head': dict(lr_mult=10.)})) + +train_dataloader = dict(batch_size=1) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_pup_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_pup_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..e9bfb22 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/setr/setr_vit-l_pup_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,72 @@ +_base_ = [ + '../_base_/models/setr_pup.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + img_size=(512, 512), + drop_rate=0., + init_cfg=dict( + type='Pretrained', checkpoint='pretrain/vit_large_p16.pth')), + decode_head=dict(num_classes=150), + auxiliary_head=[ + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=0, + num_classes=150, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=2, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=1, + num_classes=150, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=2, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + dict( + type='SETRUPHead', + in_channels=1024, + channels=256, + in_index=2, + num_classes=150, + dropout_ratio=0, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU'), + num_convs=2, + kernel_size=3, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + ], + test_cfg=dict(mode='slide', crop_size=(512, 512), stride=(341, 341)), +) + +optimizer = dict(lr=0.001, weight_decay=0.0) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + paramwise_cfg=dict(custom_keys={'head': dict(lr_mult=10.)})) +# num_gpus: 8 -> batch_size: 16 +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/stdc/README.md b/Seg_All_In_One_MMSeg/configs/stdc/README.md new file mode 100644 index 0000000..3e8bf60 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/README.md @@ -0,0 +1,73 @@ +# STDC + +> [Rethinking BiSeNet For Real-time Semantic Segmentation](https://arxiv.org/abs/2104.13188) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +BiSeNet has been proved to be a popular two-stream network for real-time segmentation. However, its principle of adding an extra path to encode spatial information is time-consuming, and the backbones borrowed from pretrained tasks, e.g., image classification, may be inefficient for image segmentation due to the deficiency of task-specific design. To handle these problems, we propose a novel and efficient structure named Short-Term Dense Concatenate network (STDC network) by removing structure redundancy. Specifically, we gradually reduce the dimension of feature maps and use the aggregation of them for image representation, which forms the basic module of STDC network. In the decoder, we propose a Detail Aggregation module by integrating the learning of spatial information into low-level layers in single-stream manner. Finally, the low-level features and deep features are fused to predict the final segmentation results. Extensive experiments on Cityscapes and CamVid dataset demonstrate the effectiveness of our method by achieving promising trade-off between segmentation accuracy and inference speed. On Cityscapes, we achieve 71.9% mIoU on the test set with a speed of 250.4 FPS on NVIDIA GTX 1080Ti, which is 45.2% faster than the latest methods, and achieve 76.8% mIoU with 97.0 FPS while inferring on higher resolution images. + + + +
+ +
+ +## Usage + +We have provided [ImageNet Pretrained STDCNet Weights](https://drive.google.com/drive/folders/1wROFwRt8qWHD4jSo8Zu1gp1d6oYJ3ns1) models converted from [official repo](https://github.com/MichaelFan01/STDC-Seg). + +If you want to convert keys on your own to use official repositories' pre-trained models, we also provide a script [`stdc2mmseg.py`](../../tools/model_converters/stdc2mmseg.py) in the tools directory to convert the key of models from [the official repo](https://github.com/MichaelFan01/STDC-Seg) to MMSegmentation style. + +```shell +python tools/model_converters/stdc2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} ${STDC_TYPE} +``` + +E.g. + +```shell +python tools/model_converters/stdc2mmseg.py ./STDCNet813M_73.91.tar ./pretrained/stdc1.pth STDC1 + +python tools/model_converters/stdc2mmseg.py ./STDCNet1446_76.47.tar ./pretrained/stdc2.pth STDC2 +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| STDC | STDC1 (No Pretrain) | 512x1024 | 80000 | 7.15 | 23.06 | V100 | 71.82 | 73.89 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/stdc/stdc1_4xb12-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc1_512x1024_80k_cityscapes/stdc1_512x1024_80k_cityscapes_20220224_073048-74e6920a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc1_512x1024_80k_cityscapes/stdc1_512x1024_80k_cityscapes_20220224_073048.log.json) | +| STDC | STDC1 | 512x1024 | 80000 | - | - | V100 | 74.94 | 76.97 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/stdc/stdc1_in1k-pre_4xb12-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc1_in1k-pre_512x1024_80k_cityscapes/stdc1_in1k-pre_512x1024_80k_cityscapes_20220224_141648-3d4c2981.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc1_in1k-pre_512x1024_80k_cityscapes/stdc1_in1k-pre_512x1024_80k_cityscapes_20220224_141648.log.json) | +| STDC | STDC2 (No Pretrain) | 512x1024 | 80000 | 8.27 | 23.71 | V100 | 73.15 | 76.13 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/stdc/stdc2_4xb12-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc2_512x1024_80k_cityscapes/stdc2_512x1024_80k_cityscapes_20220222_132015-fb1e3a1a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc2_512x1024_80k_cityscapes/stdc2_512x1024_80k_cityscapes_20220222_132015.log.json) | +| STDC | STDC2 | 512x1024 | 80000 | - | - | V100 | 76.67 | 78.67 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/stdc/stdc2_in1k-pre_4xb12-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc2_in1k-pre_512x1024_80k_cityscapes/stdc2_in1k-pre_512x1024_80k_cityscapes_20220224_073048-1f8f0f6c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc2_in1k-pre_512x1024_80k_cityscapes/stdc2_in1k-pre_512x1024_80k_cityscapes_20220224_073048.log.json) | + +Note: + +- For STDC on Cityscapes dataset, default setting is 4 GPUs with 12 samples per GPU in training. +- `No Pretrain` means the model is trained from scratch. +- The FPS is for reference only. The environment is also different from paper setting, whose input size is `512x1024` and `768x1536`, i.e., 50% and 75% of our input size, respectively and using TensorRT. +- The parameter `fusion_kernel` in `STDCHead` is not learnable. In official repo, `find_unused_parameters=True` is set [here](https://github.com/MichaelFan01/STDC-Seg/blob/59ff37fbd693b99972c76fcefe97caa14aeb619f/train.py#L220). You may check it by printing model parameters of original repo on your own. + +## Citation + +```bibtex +@inproceedings{fan2021rethinking, + title={Rethinking BiSeNet For Real-time Semantic Segmentation}, + author={Fan, Mingyuan and Lai, Shenqi and Huang, Junshi and Wei, Xiaoming and Chai, Zhenhua and Luo, Junfeng and Wei, Xiaolin}, + booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition}, + pages={9716--9725}, + year={2021} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/stdc/metafile.yaml b/Seg_All_In_One_MMSeg/configs/stdc/metafile.yaml new file mode 100644 index 0000000..93cb14f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/metafile.yaml @@ -0,0 +1,107 @@ +Collections: +- Name: STDC + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + Paper: + Title: Rethinking BiSeNet For Real-time Semantic Segmentation + URL: https://arxiv.org/abs/2104.13188 + README: configs/stdc/README.md + Frameworks: + - PyTorch +Models: +- Name: stdc1_4xb12-80k_cityscapes-512x1024 + In Collection: STDC + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 71.82 + mIoU(ms+flip): 73.89 + Config: configs/stdc/stdc1_4xb12-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 48 + Architecture: + - STDC1 + - STDC + Training Resources: 4x V100 GPUS + Memory (GB): 7.15 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc1_512x1024_80k_cityscapes/stdc1_512x1024_80k_cityscapes_20220224_073048-74e6920a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc1_512x1024_80k_cityscapes/stdc1_512x1024_80k_cityscapes_20220224_073048.log.json + Paper: + Title: Rethinking BiSeNet For Real-time Semantic Segmentation + URL: https://arxiv.org/abs/2104.13188 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/stdc.py#L394 + Framework: PyTorch +- Name: stdc1_in1k-pre_4xb12-80k_cityscapes-512x1024 + In Collection: STDC + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 74.94 + mIoU(ms+flip): 76.97 + Config: configs/stdc/stdc1_in1k-pre_4xb12-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 48 + Architecture: + - STDC1 + - STDC + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc1_in1k-pre_512x1024_80k_cityscapes/stdc1_in1k-pre_512x1024_80k_cityscapes_20220224_141648-3d4c2981.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc1_in1k-pre_512x1024_80k_cityscapes/stdc1_in1k-pre_512x1024_80k_cityscapes_20220224_141648.log.json + Paper: + Title: Rethinking BiSeNet For Real-time Semantic Segmentation + URL: https://arxiv.org/abs/2104.13188 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/stdc.py#L394 + Framework: PyTorch +- Name: stdc2_4xb12-80k_cityscapes-512x1024 + In Collection: STDC + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 73.15 + mIoU(ms+flip): 76.13 + Config: configs/stdc/stdc2_4xb12-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 48 + Architecture: + - STDC2 + - STDC + Training Resources: 4x V100 GPUS + Memory (GB): 8.27 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc2_512x1024_80k_cityscapes/stdc2_512x1024_80k_cityscapes_20220222_132015-fb1e3a1a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc2_512x1024_80k_cityscapes/stdc2_512x1024_80k_cityscapes_20220222_132015.log.json + Paper: + Title: Rethinking BiSeNet For Real-time Semantic Segmentation + URL: https://arxiv.org/abs/2104.13188 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/stdc.py#L394 + Framework: PyTorch +- Name: stdc2_in1k-pre_4xb12-80k_cityscapes-512x1024 + In Collection: STDC + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 76.67 + mIoU(ms+flip): 78.67 + Config: configs/stdc/stdc2_in1k-pre_4xb12-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 48 + Architecture: + - STDC2 + - STDC + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc2_in1k-pre_512x1024_80k_cityscapes/stdc2_in1k-pre_512x1024_80k_cityscapes_20220224_073048-1f8f0f6c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/stdc/stdc2_in1k-pre_512x1024_80k_cityscapes/stdc2_in1k-pre_512x1024_80k_cityscapes_20220224_073048.log.json + Paper: + Title: Rethinking BiSeNet For Real-time Semantic Segmentation + URL: https://arxiv.org/abs/2104.13188 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/stdc.py#L394 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..9885ca7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet1', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc1.pth', + ), + ), + decode_head=dict( + num_classes=10, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..c32d595 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet1', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc1.pth', + ), + ), + decode_head=dict( + num_classes=13, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..71268fb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet1', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc1.pth', + ), + ), + decode_head=dict( + num_classes=11, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..aa342f2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet1', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc1.pth', + ), + ), + decode_head=dict( + num_classes=8, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..fc74ad2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V1_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet1', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc1.pth', + ), + ), + decode_head=dict( + num_classes=8, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py new file mode 100644 index 0000000..642f3d9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_autolaparo-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_autolaparo.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 123.62464353460942, + 85.34836259209033, + 82.31539425671558, + ], + std=[ + 47.172211618459315, + 47.08256715323592, + 48.135121265163605, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet2', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc2.pth', + ), + ), + decode_head=dict( + num_classes=10, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py new file mode 100644 index 0000000..6138a06 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_cholecseg8k-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_cholecseg8k.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 85.65740418979115, + 53.99282220050495, + 46.074045888534535, + ], + std=[ + 72.24589167201978, + 56.76979155397199, + 49.056637115061775, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet2', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc2.pth', + ), + ), + decode_head=dict( + num_classes=13, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py new file mode 100644 index 0000000..8b59b4d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_dresden-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_dresden.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 103.172638338208, + 61.44762740851152, + 51.407770213021976, + ], + std=[ + 75.77031253622098, + 54.63616729031377, + 49.45572239497569, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet2', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc2.pth', + ), + ), + decode_head=dict( + num_classes=11, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py new file mode 100644 index 0000000..7c83c6b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_endovis_2017-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_endovis_2017.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet2', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc2.pth', + ), + ), + decode_head=dict( + num_classes=8, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py new file mode 100644 index 0000000..f7d8c34 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/my_stdc_V2_Pre_g1-300e_val1_check10_publicdataset_endovis_2018-512x512.py @@ -0,0 +1,94 @@ +_base_ = [ + '../_base_/models/stdc.py', + '../_base_/datasets/publicdataset_endovis_2018.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_300e_val1_check10.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (512, 512) + +data_preprocessor = dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(512, 512), + mean=[ + 122.21429912990676, + 77.0821859677977, + 87.03836664626716, + ], + std=[ + 50.53335800365262, + 42.895340354037465, + 47.739426483390446, + ], + bgr_to_rgb=False, + ), + backbone=dict( + backbone_cfg=dict( + stdc_type='STDCNet2', + ), + init_cfg=dict( + type='Pretrained', + checkpoint='./My_Local_Model/open_mmlab/stdc2.pth', + ), + ), + decode_head=dict( + num_classes=8, + loss_decode=dict( + type='DiceLoss', + use_sigmoid=False, + loss_weight=1.0, + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=True, + begin=0, + end=10, + ), + dict( + type='PolyLR', + power=0.9, + begin=10, + end=300, + eta_min=1e-05, + by_epoch=True, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/stdc/stdc1_4xb12-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/stdc/stdc1_4xb12-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..20aec3d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/stdc1_4xb12-80k_cityscapes-512x1024.py @@ -0,0 +1,21 @@ +_base_ = [ + '../_base_/models/stdc.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=80000, + by_epoch=False, + ) +] +train_dataloader = dict(batch_size=12, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/stdc/stdc1_in1k-pre_4xb12-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/stdc/stdc1_in1k-pre_4xb12-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..15e807f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/stdc1_in1k-pre_4xb12-80k_cityscapes-512x1024.py @@ -0,0 +1,6 @@ +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/stdc/stdc1_20220308-5368626c.pth' # noqa +_base_ = './stdc1_4xb12-80k_cityscapes-512x1024.py' +model = dict( + backbone=dict( + backbone_cfg=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint)))) diff --git a/Seg_All_In_One_MMSeg/configs/stdc/stdc2_4xb12-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/stdc/stdc2_4xb12-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..5657351 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/stdc2_4xb12-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './stdc1_4xb12-80k_cityscapes-512x1024.py' +model = dict(backbone=dict(backbone_cfg=dict(stdc_type='STDCNet2'))) diff --git a/Seg_All_In_One_MMSeg/configs/stdc/stdc2_in1k-pre_4xb12-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/stdc/stdc2_in1k-pre_4xb12-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..05a202b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/stdc/stdc2_in1k-pre_4xb12-80k_cityscapes-512x1024.py @@ -0,0 +1,6 @@ +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/stdc/stdc2_20220308-7dbd9127.pth' # noqa +_base_ = './stdc2_4xb12-80k_cityscapes-512x1024.py' +model = dict( + backbone=dict( + backbone_cfg=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint)))) diff --git a/Seg_All_In_One_MMSeg/configs/swin/README.md b/Seg_All_In_One_MMSeg/configs/swin/README.md new file mode 100644 index 0000000..64f0d5f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/README.md @@ -0,0 +1,76 @@ +# Swin Transformer + +> [Swin Transformer: Hierarchical Vision Transformer using Shifted Windows](https://arxiv.org/abs/2103.14030) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +This paper presents a new vision Transformer, called Swin Transformer, that capably serves as a general-purpose backbone for computer vision. Challenges in adapting Transformer from language to vision arise from differences between the two domains, such as large variations in the scale of visual entities and the high resolution of pixels in images compared to words in text. To address these differences, we propose a hierarchical Transformer whose representation is computed with Shifted windows. The shifted windowing scheme brings greater efficiency by limiting self-attention computation to non-overlapping local windows while also allowing for cross-window connection. This hierarchical architecture has the flexibility to model at various scales and has linear computational complexity with respect to image size. These qualities of Swin Transformer make it compatible with a broad range of vision tasks, including image classification (87.3 top-1 accuracy on ImageNet-1K) and dense prediction tasks such as object detection (58.7 box AP and 51.1 mask AP on COCO test-dev) and semantic segmentation (53.5 mIoU on ADE20K val). Its performance surpasses the previous state-of-the-art by a large margin of +2.7 box AP and +2.6 mask AP on COCO, and +3.2 mIoU on ADE20K, demonstrating the potential of Transformer-based models as vision backbones. The hierarchical design and the shifted window approach also prove beneficial for all-MLP architectures. The code and models are publicly available at [this https URL](https://github.com/microsoft/Swin-Transformer). + + + +
+ +
+ +## Usage + +We have provided pretrained models converted from [official repo](https://github.com/microsoft/Swin-Transformer). + +If you want to convert keys on your own to use official repositories' pre-trained models, we also provide a script [`swin2mmseg.py`](../../tools/model_converters/swin2mmseg.py) in the tools directory to convert the key of models from [the official repo](https://github.com/SwinTransformer/Swin-Transformer-Semantic-Segmentation) to MMSegmentation style. + +```shell +python tools/model_converters/swin2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +E.g. + +```shell +python tools/model_converters/swin2mmseg.py https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224.pth pretrain/swin_base_patch4_window7_224.pth +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +In our default setting, pretrained models and their corresponding [original models](https://github.com/microsoft/Swin-Transforme) models could be defined below: + +| pretrained models | original models | +| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| pretrain/swin_tiny_patch4_window7_224.pth | [swin_tiny_patch4_window7_224.pth](https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_tiny_patch4_window7_224.pth) | +| pretrain/swin_small_patch4_window7_224.pth | [swin_small_patch4_window7_224.pth](https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_small_patch4_window7_224.pth) | +| pretrain/swin_base_patch4_window7_224.pth | [swin_base_patch4_window7_224.pth](https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224.pth) | +| pretrain/swin_base_patch4_window7_224_22k.pth | [swin_base_patch4_window7_224_22k.pth](https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224_22k.pth) | +| pretrain/swin_base_patch4_window12_384.pth | [swin_base_patch4_window12_384.pth](https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window12_384.pth) | +| pretrain/swin_base_patch4_window12_384_22k.pth | [swin_base_patch4_window12_384_22k.pth](https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window12_384_22k.pth) | + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | pretrain | pretrain img size | Batch Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | -------- | --------- | ------------ | ----------------- | ---------- | ------- | -------- | -------------- | ------ | ----- | ------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UPerNet | Swin-T | 512x512 | ImageNet-1K | 224x224 | 16 | 160000 | 5.02 | 21.06 | V100 | 44.41 | 45.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/swin/swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210531_112542-e380ad3e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210531_112542.log.json) | +| UPerNet | Swin-S | 512x512 | ImageNet-1K | 224x224 | 16 | 160000 | 6.17 | 14.72 | V100 | 47.72 | 49.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/swin/swin-small-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_small_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_small_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210526_192015-ee2fff1c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_small_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_small_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210526_192015.log.json) | +| UPerNet | Swin-B | 512x512 | ImageNet-1K | 224x224 | 16 | 160000 | 7.61 | 12.65 | V100 | 47.99 | 49.57 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/swin/swin-base-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210526_192340-593b0e13.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210526_192340.log.json) | +| UPerNet | Swin-B | 512x512 | ImageNet-22K | 224x224 | 16 | 160000 | - | - | V100 | 50.13 | 51.9 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/swin/swin-base-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_22K/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_22K_20210526_211650-762e2178.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_22K/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_22K_20210526_211650.log.json) | +| UPerNet | Swin-B | 512x512 | ImageNet-1K | 384x384 | 16 | 160000 | 8.52 | 12.10 | V100 | 48.35 | 49.65 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/swin/swin-base-patch4-window12-in1k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_1K/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_1K_20210531_132020-05b22ea4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_1K/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_1K_20210531_132020.log.json) | +| UPerNet | Swin-B | 512x512 | ImageNet-22K | 384x384 | 16 | 160000 | - | - | V100 | 50.76 | 52.4 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/swin/swin-base-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_22K/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_22K_20210531_125459-429057bf.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_22K/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_22K_20210531_125459.log.json) | + +## Citation + +```bibtex +@article{liu2021Swin, + title={Swin Transformer: Hierarchical Vision Transformer using Shifted Windows}, + author={Liu, Ze and Lin, Yutong and Cao, Yue and Hu, Han and Wei, Yixuan and Zhang, Zheng and Lin, Stephen and Guo, Baining}, + journal={arXiv preprint arXiv:2103.14030}, + year={2021} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/swin/metafile.yaml b/Seg_All_In_One_MMSeg/configs/swin/metafile.yaml new file mode 100644 index 0000000..67a4e07 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/metafile.yaml @@ -0,0 +1,143 @@ +Models: +- Name: swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.41 + mIoU(ms+flip): 45.79 + Config: configs/swin/swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-T + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 5.02 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210531_112542-e380ad3e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210531_112542.log.json + Paper: + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + URL: https://arxiv.org/abs/2103.14030 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/swin.py#L524 + Framework: PyTorch +- Name: swin-small-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.72 + mIoU(ms+flip): 49.24 + Config: configs/swin/swin-small-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-S + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 6.17 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_small_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_small_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210526_192015-ee2fff1c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_small_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_small_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210526_192015.log.json + Paper: + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + URL: https://arxiv.org/abs/2103.14030 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/swin.py#L524 + Framework: PyTorch +- Name: swin-base-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.99 + mIoU(ms+flip): 49.57 + Config: configs/swin/swin-base-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 7.61 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210526_192340-593b0e13.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_1K_20210526_192340.log.json + Paper: + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + URL: https://arxiv.org/abs/2103.14030 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/swin.py#L524 + Framework: PyTorch +- Name: swin-base-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 50.13 + mIoU(ms+flip): 51.9 + Config: configs/swin/swin-base-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-B + - UPerNet + Training Resources: 8x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_22K/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_22K_20210526_211650-762e2178.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_22K/upernet_swin_base_patch4_window7_512x512_160k_ade20k_pretrain_224x224_22K_20210526_211650.log.json + Paper: + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + URL: https://arxiv.org/abs/2103.14030 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/swin.py#L524 + Framework: PyTorch +- Name: swin-base-patch4-window12-in1k-384x384-pre_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.35 + mIoU(ms+flip): 49.65 + Config: configs/swin/swin-base-patch4-window12-in1k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 8.52 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_1K/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_1K_20210531_132020-05b22ea4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_1K/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_1K_20210531_132020.log.json + Paper: + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + URL: https://arxiv.org/abs/2103.14030 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/swin.py#L524 + Framework: PyTorch +- Name: swin-base-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 50.76 + mIoU(ms+flip): 52.4 + Config: configs/swin/swin-base-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Swin-B + - UPerNet + Training Resources: 8x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_22K/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_22K_20210531_125459-429057bf.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/swin/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_22K/upernet_swin_base_patch4_window12_512x512_160k_ade20k_pretrain_384x384_22K_20210531_125459.log.json + Paper: + Title: 'Swin Transformer: Hierarchical Vision Transformer using Shifted Windows' + URL: https://arxiv.org/abs/2103.14030 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/swin.py#L524 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window12-in1k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window12-in1k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..11cea36 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window12-in1k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,14 @@ +_base_ = [ + 'swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py' +] +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window12_384_20220317-55b0104a.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32], + window_size=12), + decode_head=dict(in_channels=[128, 256, 512, 1024], num_classes=150), + auxiliary_head=dict(in_channels=512, num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..5c11716 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,7 @@ +_base_ = [ + './swin-base-patch4-window12-in1k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py' # noqa +] +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window12_384_22k_20220317-e5c09f74.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file))) diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..73bf616 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = [ + './swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py' +] +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window7_224_20220317-e9b98025.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + embed_dims=128, + depths=[2, 2, 18, 2], + num_heads=[4, 8, 16, 32]), + decode_head=dict(in_channels=[128, 256, 512, 1024], num_classes=150), + auxiliary_head=dict(in_channels=512, num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..96148cd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-base-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,7 @@ +_base_ = [ + './swin-base-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py' +] +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window7_224_22k_20220317-4f79f7c0.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file))) diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-large-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/swin/swin-large-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..a0a654e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-large-patch4-window12-in22k-384x384-pre_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + 'swin-large-patch4-window7-in22k-pre_upernet_' + '8xb2-160k_ade20k-512x512.py' +] +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_large_patch4_window12_384_22k_20220412-6580f57d.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + pretrain_img_size=384, + window_size=12)) diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-large-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/swin/swin-large-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..c93cdfe --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-large-patch4-window7-in22k-pre_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,15 @@ +_base_ = [ + 'swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_' + 'ade20k-512x512.py' +] +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_large_patch4_window7_224_22k_20220412-aeecf2aa.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + pretrain_img_size=224, + embed_dims=192, + depths=[2, 2, 18, 2], + num_heads=[6, 12, 24, 48], + window_size=7), + decode_head=dict(in_channels=[192, 384, 768, 1536], num_classes=150), + auxiliary_head=dict(in_channels=768, num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-small-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/swin/swin-small-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..19863df --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-small-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + './swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py' +] +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_small_patch4_window7_224_20220317-7ba6d6dd.pth' # noqa +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + depths=[2, 2, 18, 2]), + decode_head=dict(in_channels=[96, 192, 384, 768], num_classes=150), + auxiliary_head=dict(in_channels=384, num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/swin/swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..f61a276 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-tiny-patch4-window7-in1k-pre_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,52 @@ +_base_ = [ + '../_base_/models/upernet_swin.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +checkpoint_file = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_tiny_patch4_window7_224_20220317-1cdeb081.pth' # noqa +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint_file), + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + use_abs_pos_embed=False, + drop_path_rate=0.3, + patch_norm=True), + decode_head=dict(in_channels=[96, 192, 384, 768], num_classes=150), + auxiliary_head=dict(in_channels=384, num_classes=150)) + +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/swin/swin-tiny-patch4-window7_upernet_1xb8-20k_levir-256x256.py b/Seg_All_In_One_MMSeg/configs/swin/swin-tiny-patch4-window7_upernet_1xb8-20k_levir-256x256.py new file mode 100644 index 0000000..7b90686 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/swin/swin-tiny-patch4-window7_upernet_1xb8-20k_levir-256x256.py @@ -0,0 +1,57 @@ +_base_ = [ + '../_base_/models/upernet_swin.py', '../_base_/datasets/levir_256x256.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_20k.py' +] +crop_size = (256, 256) +norm_cfg = dict(type='BN', requires_grad=True) +data_preprocessor = dict( + size=crop_size, + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53, 123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375, 58.395, 57.12, 57.375], + bgr_to_rgb=False) + +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + in_channels=6, + embed_dims=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + use_abs_pos_embed=False, + drop_path_rate=0.3, + patch_norm=True), + decode_head=dict(in_channels=[96, 192, 384, 768], num_classes=2), + auxiliary_head=dict(in_channels=384, num_classes=2)) + +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=20000, + by_epoch=False, + ) +] + +train_dataloader = dict(batch_size=4) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/twins/README.md b/Seg_All_In_One_MMSeg/configs/twins/README.md new file mode 100644 index 0000000..e4b3735 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/README.md @@ -0,0 +1,76 @@ +# Twins + +> [Twins: Revisiting the Design of Spatial Attention in Vision Transformers](https://arxiv.org/pdf/2104.13840.pdf) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Very recently, a variety of vision transformer architectures for dense prediction tasks have been proposed and they show that the design of spatial attention is critical to their success in these tasks. In this work, we revisit the design of the spatial attention and demonstrate that a carefully-devised yet simple spatial attention mechanism performs favourably against the state-of-the-art schemes. As a result, we propose two vision transformer architectures, namely, Twins-PCPVT and Twins-SVT. Our proposed architectures are highly-efficient and easy to implement, only involving matrix multiplications that are highly optimized in modern deep learning frameworks. More importantly, the proposed architectures achieve excellent performance on a wide range of visual tasks, including image level classification as well as dense detection and segmentation. The simplicity and strong performance suggest that our proposed architectures may serve as stronger backbones for many vision tasks. Our code is released at [this https URL](https://github.com/Meituan-AutoML/Twins). + + + +
+ +
+ +## Usage + +We have provided pretrained models converted from [official repo](https://github.com/Meituan-AutoML/Twins). + +If you want to convert keys on your own to use official repositories' pre-trained models, we also provide a script [`twins2mmseg.py`](../../tools/model_converters/twins2mmseg.py) in the tools directory to convert the key of models from [the official repo](https://github.com/Meituan-AutoML/Twins) to MMSegmentation style. + +```shell +python tools/model_converters/twins2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} ${MODEL_TYPE} +``` + +This script convert `pcpvt` or `svt` pretrained model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +For example, + +```shell +python tools/model_converters/twins2mmseg.py ./alt_gvt_base.pth ./pretrained/alt_gvt_base.pth svt +``` + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | ------------------- | --------- | ------- | -------- | -------------- | ------ | ----- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FPN | Twins-PCPVT-S | 512x512 | 80000 | 6.60 | 27.15 | V100 | 43.26 | 44.11 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-s_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-s_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_204132-41acd132.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-s_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-s_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_204132.log.json) | +| UPerNet | Twins-PCPVT-S | 512x512 | 160000 | 9.67 | 14.24 | V100 | 46.04 | 46.92 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-s_uperhead_8x4_512x512_160k_ade20k/twins_pcpvt-s_uperhead_8x4_512x512_160k_ade20k_20211201_233537-8e99c07a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-s_uperhead_8x4_512x512_160k_ade20k/twins_pcpvt-s_uperhead_8x4_512x512_160k_ade20k_20211201_233537.log.json) | +| FPN | Twins-PCPVT-B | 512x512 | 80000 | 8.41 | 19.67 | V100 | 45.66 | 46.48 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_pcpvt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-b_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-b_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141019-d396db72.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-b_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-b_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141019.log.json) | +| UPerNet | Twins-PCPVT-B (8x2) | 512x512 | 160000 | 6.46 | 12.04 | V100 | 47.91 | 48.64 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_pcpvt-b_uperhead_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-b_uperhead_8x2_512x512_160k_ade20k/twins_pcpvt-b_uperhead_8x2_512x512_160k_ade20k_20211130_141020-02094ea5.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-b_uperhead_8x2_512x512_160k_ade20k/twins_pcpvt-b_uperhead_8x2_512x512_160k_ade20k_20211130_141020.log.json) | +| FPN | Twins-PCPVT-L | 512x512 | 80000 | 10.78 | 14.32 | V100 | 45.94 | 46.70 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_pcpvt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-l_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-l_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_105226-bc6d61dc.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-l_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-l_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_105226.log.json) | +| UPerNet | Twins-PCPVT-L (8x2) | 512x512 | 160000 | 7.82 | 10.70 | V100 | 49.35 | 50.08 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-l_uperhead_8x2_512x512_160k_ade20k/twins_pcpvt-l_uperhead_8x2_512x512_160k_ade20k_20211201_075053-c6095c07.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-l_uperhead_8x2_512x512_160k_ade20k/twins_pcpvt-l_uperhead_8x2_512x512_160k_ade20k_20211201_075053.log.json) | +| FPN | Twins-SVT-S | 512x512 | 80000 | 5.80 | 29.79 | V100 | 44.47 | 45.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-s_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-s_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141006-0a0d3317.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-s_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-s_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141006.log.json) | +| UPerNet | SVT-S (8x2) | 512x512 | 160000 | 4.93 | 15.09 | V100 | 46.08 | 46.96 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_svt-s_uperhead_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-s_uperhead_8x2_512x512_160k_ade20k/twins_svt-s_uperhead_8x2_512x512_160k_ade20k_20211130_141005-e48a2d94.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-s_uperhead_8x2_512x512_160k_ade20k/twins_svt-s_uperhead_8x2_512x512_160k_ade20k_20211130_141005.log.json) | +| FPN | Twins-SVT-B | 512x512 | 80000 | 8.75 | 21.10 | V100 | 46.77 | 47.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_svt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-b_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-b_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_113849-88b2907c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-b_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-b_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_113849.log.json) | +| UPerNet | Twins-SVT-B (8x2) | 512x512 | 160000 | 6.77 | 12.66 | V100 | 48.04 | 48.87 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_svt-b_uperhead_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-b_uperhead_8x2_512x512_160k_ade20k/twins_svt-b_uperhead_8x2_512x512_160k_ade20k_20211202_040826-0943a1f1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-b_uperhead_8x2_512x512_160k_ade20k/twins_svt-b_uperhead_8x2_512x512_160k_ade20k_20211202_040826.log.json) | +| FPN | Twins-SVT-L | 512x512 | 80000 | 11.20 | 17.80 | V100 | 46.55 | 47.74 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_svt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-l_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-l_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141005-1d59bee2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-l_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-l_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141005.log.json) | +| UPerNet | Twins-SVT-L (8x2) | 512x512 | 160000 | 8.41 | 10.73 | V100 | 49.65 | 50.63 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins/twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-l_uperhead_8x2_512x512_160k_ade20k/twins_svt-l_uperhead_8x2_512x512_160k_ade20k_20211130_141005-3e2cae61.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-l_uperhead_8x2_512x512_160k_ade20k/twins_svt-l_uperhead_8x2_512x512_160k_ade20k_20211130_141005.log.json) | + +Note: + +- `8x2` means 8 GPUs with 2 samples per GPU in training. Default setting of Twins on ADE20K is 8 GPUs with 4 samples per GPU in training. +- `UPerNet` and `FPN` are decoder heads utilized in corresponding Twins model, which is `UPerHead` and `FPNHead`, respectively. Specifically, models in [official repo](https://github.com/Meituan-AutoML/Twins) all use `UPerHead`. + +## Citation + +```bibtex +@article{chu2021twins, + title={Twins: Revisiting spatial attention design in vision transformers}, + author={Chu, Xiangxiang and Tian, Zhi and Wang, Yuqing and Zhang, Bo and Ren, Haibing and Wei, Xiaolin and Xia, Huaxia and Shen, Chunhua}, + journal={arXiv preprint arXiv:2104.13840}, + year={2021}altgvt +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/twins/metafile.yaml b/Seg_All_In_One_MMSeg/configs/twins/metafile.yaml new file mode 100644 index 0000000..0de78d9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/metafile.yaml @@ -0,0 +1,289 @@ +Models: +- Name: twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.26 + mIoU(ms+flip): 44.11 + Config: configs/twins/twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - Twins-PCPVT-S + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 6.6 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-s_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-s_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_204132-41acd132.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-s_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-s_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_204132.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.04 + mIoU(ms+flip): 46.92 + Config: configs/twins/twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - Twins-PCPVT-S + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 9.67 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-s_uperhead_8x4_512x512_160k_ade20k/twins_pcpvt-s_uperhead_8x4_512x512_160k_ade20k_20211201_233537-8e99c07a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-s_uperhead_8x4_512x512_160k_ade20k/twins_pcpvt-s_uperhead_8x4_512x512_160k_ade20k_20211201_233537.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_pcpvt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.66 + mIoU(ms+flip): 46.48 + Config: configs/twins/twins_pcpvt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - Twins-PCPVT-B + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 8.41 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-b_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-b_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141019-d396db72.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-b_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-b_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141019.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_pcpvt-b_uperhead_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.91 + mIoU(ms+flip): 48.64 + Config: configs/twins/twins_pcpvt-b_uperhead_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Twins-PCPVT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 6.46 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-b_uperhead_8x2_512x512_160k_ade20k/twins_pcpvt-b_uperhead_8x2_512x512_160k_ade20k_20211130_141020-02094ea5.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-b_uperhead_8x2_512x512_160k_ade20k/twins_pcpvt-b_uperhead_8x2_512x512_160k_ade20k_20211130_141020.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_pcpvt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.94 + mIoU(ms+flip): 46.7 + Config: configs/twins/twins_pcpvt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - Twins-PCPVT-L + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 10.78 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-l_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-l_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_105226-bc6d61dc.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-l_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_pcpvt-l_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_105226.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 49.35 + mIoU(ms+flip): 50.08 + Config: configs/twins/twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Twins-PCPVT-L + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 7.82 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-l_uperhead_8x2_512x512_160k_ade20k/twins_pcpvt-l_uperhead_8x2_512x512_160k_ade20k_20211201_075053-c6095c07.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_pcpvt-l_uperhead_8x2_512x512_160k_ade20k/twins_pcpvt-l_uperhead_8x2_512x512_160k_ade20k_20211201_075053.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 44.47 + mIoU(ms+flip): 45.42 + Config: configs/twins/twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - Twins-SVT-S + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 5.8 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-s_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-s_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141006-0a0d3317.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-s_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-s_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141006.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_svt-s_uperhead_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.08 + mIoU(ms+flip): 46.96 + Config: configs/twins/twins_svt-s_uperhead_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - SVT-S + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 4.93 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-s_uperhead_8x2_512x512_160k_ade20k/twins_svt-s_uperhead_8x2_512x512_160k_ade20k_20211130_141005-e48a2d94.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-s_uperhead_8x2_512x512_160k_ade20k/twins_svt-s_uperhead_8x2_512x512_160k_ade20k_20211130_141005.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_svt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.77 + mIoU(ms+flip): 47.47 + Config: configs/twins/twins_svt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - Twins-SVT-B + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 8.75 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-b_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-b_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_113849-88b2907c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-b_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-b_fpn_fpnhead_8x4_512x512_80k_ade20k_20211201_113849.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_svt-b_uperhead_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 48.04 + mIoU(ms+flip): 48.87 + Config: configs/twins/twins_svt-b_uperhead_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Twins-SVT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 6.77 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-b_uperhead_8x2_512x512_160k_ade20k/twins_svt-b_uperhead_8x2_512x512_160k_ade20k_20211202_040826-0943a1f1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-b_uperhead_8x2_512x512_160k_ade20k/twins_svt-b_uperhead_8x2_512x512_160k_ade20k_20211202_040826.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_svt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512 + In Collection: FPN + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.55 + mIoU(ms+flip): 47.74 + Config: configs/twins/twins_svt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 32 + Architecture: + - Twins-SVT-L + - FPN + Training Resources: 8x V100 GPUS + Memory (GB): 11.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-l_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-l_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141005-1d59bee2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-l_fpn_fpnhead_8x4_512x512_80k_ade20k/twins_svt-l_fpn_fpnhead_8x4_512x512_80k_ade20k_20211130_141005.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch +- Name: twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 49.65 + mIoU(ms+flip): 50.63 + Config: configs/twins/twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - Twins-SVT-L + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 8.41 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-l_uperhead_8x2_512x512_160k_ade20k/twins_svt-l_uperhead_8x2_512x512_160k_ade20k_20211130_141005-3e2cae61.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/twins/twins_svt-l_uperhead_8x2_512x512_160k_ade20k/twins_svt-l_uperhead_8x2_512x512_160k_ade20k_20211130_141005.log.json + Paper: + Title: 'Twins: Revisiting the Design of Spatial Attention in Vision Transformers' + URL: https://arxiv.org/pdf/2104.13840.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.20.0/mmseg/models/backbones/twins.py#L352 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..4739ad4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = ['./twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/pcpvt_base_20220308-0621964c.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + depths=[3, 4, 18, 3]), ) diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-b_uperhead_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-b_uperhead_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..ba97485 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-b_uperhead_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,13 @@ +_base_ = ['./twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/pcpvt_base_20220308-0621964c.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + depths=[3, 4, 18, 3], + drop_path_rate=0.3)) + +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..bff7c41 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = ['./twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/pcpvt_large_20220308-37579dc6.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + depths=[3, 8, 27, 3])) diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..666ff5b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-l_uperhead_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,13 @@ +_base_ = ['./twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/pcpvt_large_20220308-37579dc6.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + depths=[3, 8, 27, 3], + drop_path_rate=0.3)) + +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..3b480b9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/twins_pcpvt-s_fpn.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=None) diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..387cf60 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_pcpvt-s_uperhead_8xb4-160k_ade20k-512x512.py @@ -0,0 +1,31 @@ +_base_ = [ + '../_base_/models/twins_pcpvt-s_upernet.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict(custom_keys={ + 'pos_block': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_svt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..5e9fa00 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-b_fpn_fpnhead_8xb4-80k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = ['./twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/alt_gvt_base_20220308-1b7eb711.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=[96, 192, 384, 768], + num_heads=[3, 6, 12, 24], + depths=[2, 2, 18, 2]), + neck=dict(in_channels=[96, 192, 384, 768]), +) diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_svt-b_uperhead_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-b_uperhead_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..6ce2361 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-b_uperhead_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,12 @@ +_base_ = ['./twins_svt-s_uperhead_8xb2-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/alt_gvt_base_20220308-1b7eb711.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=[96, 192, 384, 768], + num_heads=[3, 6, 12, 24], + depths=[2, 2, 18, 2]), + decode_head=dict(in_channels=[96, 192, 384, 768]), + auxiliary_head=dict(in_channels=384)) diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_svt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..b7e5f9c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-l_fpn_fpnhead_8xb4-80k_ade20k-512x512.py @@ -0,0 +1,13 @@ +_base_ = ['./twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/alt_gvt_large_20220308-fb5936f3.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=[128, 256, 512, 1024], + num_heads=[4, 8, 16, 32], + depths=[2, 2, 18, 2], + drop_path_rate=0.3), + neck=dict(in_channels=[128, 256, 512, 1024]), +) diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_svt-l_uperhead_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-l_uperhead_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..69c69df --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-l_uperhead_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,13 @@ +_base_ = ['./twins_svt-s_uperhead_8xb2-160k_ade20k-512x512.py'] + +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/alt_gvt_large_20220308-fb5936f3.pth' # noqa + +model = dict( + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=[128, 256, 512, 1024], + num_heads=[4, 8, 16, 32], + depths=[2, 2, 18, 2], + drop_path_rate=0.3), + decode_head=dict(in_channels=[128, 256, 512, 1024]), + auxiliary_head=dict(in_channels=512)) diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..c1aad83 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-s_fpn_fpnhead_8xb4-80k_ade20k-512x512.py @@ -0,0 +1,28 @@ +_base_ = [ + '../_base_/models/twins_pcpvt-s_fpn.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/alt_gvt_small_20220308-7e1c3695.pth' # noqa + +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + type='SVT', + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=[64, 128, 256, 512], + num_heads=[2, 4, 8, 16], + mlp_ratios=[4, 4, 4, 4], + depths=[2, 2, 10, 4], + windiow_sizes=[7, 7, 7, 7], + norm_after_stage=True), + neck=dict(in_channels=[64, 128, 256, 512], out_channels=256, num_outs=4), + decode_head=dict(num_classes=150), +) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0001), + clip_grad=None) diff --git a/Seg_All_In_One_MMSeg/configs/twins/twins_svt-s_uperhead_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-s_uperhead_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..3846795 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/twins/twins_svt-s_uperhead_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,49 @@ +_base_ = [ + '../_base_/models/twins_pcpvt-s_upernet.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/twins/alt_gvt_small_20220308-7e1c3695.pth' # noqa + +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + type='SVT', + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + embed_dims=[64, 128, 256, 512], + num_heads=[2, 4, 8, 16], + mlp_ratios=[4, 4, 4, 4], + depths=[2, 2, 10, 4], + windiow_sizes=[7, 7, 7, 7], + norm_after_stage=True), + decode_head=dict(in_channels=[64, 128, 256, 512]), + auxiliary_head=dict(in_channels=256)) + +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict(custom_keys={ + 'pos_block': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] + +train_dataloader = dict(batch_size=2, num_workers=2) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/unet/README.md b/Seg_All_In_One_MMSeg/configs/unet/README.md new file mode 100644 index 0000000..7225fbb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/README.md @@ -0,0 +1,92 @@ +# UNet + +> [U-Net: Convolutional Networks for Biomedical Image Segmentation](https://arxiv.org/abs/1505.04597) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +There is large consent that successful training of deep networks requires many thousand annotated training samples. In this paper, we present a network and training strategy that relies on the strong use of data augmentation to use the available annotated samples more efficiently. The architecture consists of a contracting path to capture context and a symmetric expanding path that enables precise localization. We show that such a network can be trained end-to-end from very few images and outperforms the prior best method (a sliding-window convolutional network) on the ISBI challenge for segmentation of neuronal structures in electron microscopic stacks. Using the same network trained on transmitted light microscopy images (phase contrast and DIC) we won the ISBI cell tracking challenge 2015 in these categories by a large margin. Moreover, the network is fast. Segmentation of a 512x512 image takes less than a second on a recent GPU. The full implementation (based on Caffe) and the trained networks are available at [this http URL](https://lmb.informatik.uni-freiburg.de/people/ronneber/u-net/). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Loss | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ---------- | ----------- | ------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UNet + FCN | UNet-S5-D16 | Cross Entropy | 512x1024 | 160000 | 17.91 | 3.05 | V100 | 69.10 | 71.05 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes/fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes_20211210_145204-6860854e.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes/fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes_20211210_145204.log.json) | + +### DRIVE + +| Method | Backbone | Loss | Image Size | Crop Size | Stride | Lr schd | Mem (GB) | Inf time (fps) | Device | mDice | Dice | config | download | +| ---------------- | ----------- | -------------------- | ---------- | --------- | -----: | ------- | -------- | -------------: | ------ | ----: | ----: | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UNet + FCN | UNet-S5-D16 | Cross Entropy | 584x565 | 64x64 | 42x42 | 40000 | 0.680 | - | V100 | 88.38 | 78.67 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-40k_drive-64x64.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_64x64_40k_drive/fcn_unet_s5-d16_64x64_40k_drive_20201223_191051-5daf6d3b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/unet_s5-d16_64x64_40k_drive/unet_s5-d16_64x64_40k_drive-20201223_191051.log.json) | +| UNet + FCN | UNet-S5-D16 | Cross Entropy + Dice | 584x565 | 64x64 | 42x42 | 40000 | 0.582 | - | V100 | 88.71 | 79.32 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/fcn_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201820-785de5c2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/fcn_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201820.log.json) | +| UNet + PSPNet | UNet-S5-D16 | Cross Entropy | 584x565 | 64x64 | 42x42 | 40000 | 0.599 | - | V100 | 88.35 | 78.62 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_pspnet_4xb4-40k_drive-64x64.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_64x64_40k_drive/pspnet_unet_s5-d16_64x64_40k_drive_20201227_181818-aac73387.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_64x64_40k_drive/pspnet_unet_s5-d16_64x64_40k_drive-20201227_181818.log.json) | +| UNet + PSPNet | UNet-S5-D16 | Cross Entropy + Dice | 584x565 | 64x64 | 42x42 | 40000 | 0.585 | - | V100 | 88.76 | 79.42 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/pspnet_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201821-22b3e3ba.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/pspnet_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201821.log.json) | +| UNet + DeepLabV3 | UNet-S5-D16 | Cross Entropy | 584x565 | 64x64 | 42x42 | 40000 | 0.596 | - | V100 | 88.38 | 78.69 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_drive-64x64.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_64x64_40k_drive/deeplabv3_unet_s5-d16_64x64_40k_drive_20201226_094047-0671ff20.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_64x64_40k_drive/deeplabv3_unet_s5-d16_64x64_40k_drive-20201226_094047.log.json) | +| UNet + DeepLabV3 | UNet-S5-D16 | Cross Entropy + Dice | 584x565 | 64x64 | 42x42 | 40000 | 0.582 | - | V100 | 88.84 | 79.56 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201825-6bf0efd7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201825.log.json) | + +### STARE + +| Method | Backbone | Loss | Image Size | Crop Size | Stride | Lr schd | Mem (GB) | Inf time (fps) | Device | mDice | Dice | config | download | +| ---------------- | ----------- | -------------------- | ---------- | --------- | -----: | ------- | -------- | -------------: | ------ | ----: | ----: | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| UNet + FCN | UNet-S5-D16 | Cross Entropy | 605x700 | 128x128 | 85x85 | 40000 | 0.968 | - | V100 | 89.78 | 81.02 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-40k_stare-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_128x128_40k_stare/fcn_unet_s5-d16_128x128_40k_stare_20201223_191051-7d77e78b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/unet_s5-d16_128x128_40k_stare/unet_s5-d16_128x128_40k_stare-20201223_191051.log.json) | +| UNet + FCN | UNet-S5-D16 | Cross Entropy + Dice | 605x700 | 128x128 | 85x85 | 40000 | 0.986 | - | V100 | 90.65 | 82.70 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201821-f75705a9.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201821.log.json) | +| UNet + PSPNet | UNet-S5-D16 | Cross Entropy | 605x700 | 128x128 | 85x85 | 40000 | 0.982 | - | V100 | 89.89 | 81.22 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_pspnet_4xb4-40k_stare-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_128x128_40k_stare/pspnet_unet_s5-d16_128x128_40k_stare_20201227_181818-3c2923c4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_128x128_40k_stare/pspnet_unet_s5-d16_128x128_40k_stare-20201227_181818.log.json) | +| UNet + PSPNet | UNet-S5-D16 | Cross Entropy + Dice | 605x700 | 128x128 | 85x85 | 40000 | 1.028 | - | V100 | 90.72 | 82.84 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201823-f1063ef7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201823.log.json) | +| UNet + DeepLabV3 | UNet-S5-D16 | Cross Entropy | 605x700 | 128x128 | 85x85 | 40000 | 0.999 | - | V100 | 89.73 | 80.93 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_stare-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_128x128_40k_stare/deeplabv3_unet_s5-d16_128x128_40k_stare_20201226_094047-93dcb93c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_128x128_40k_stare/deeplabv3_unet_s5-d16_128x128_40k_stare-20201226_094047.log.json) | +| UNet + DeepLabV3 | UNet-S5-D16 | Cross Entropy + Dice | 605x700 | 128x128 | 85x85 | 40000 | 1.010 | - | V100 | 90.65 | 82.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201825-21db614c.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201825.log.json) | + +### CHASE_DB1 + +| Method | Backbone | Loss | Image Size | Crop Size | Stride | Lr schd | Mem (GB) | Inf time (fps) | Device | mDice | Dice | config | download | +| ---------------- | ----------- | -------------------- | ---------- | --------- | -----: | ------- | -------- | -------------: | ------ | ----: | ----: | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UNet + FCN | UNet-S5-D16 | Cross Entropy | 960x999 | 128x128 | 85x85 | 40000 | 0.968 | - | V100 | 89.46 | 80.24 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-40k_chase-db1-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_128x128_40k_chase_db1/fcn_unet_s5-d16_128x128_40k_chase_db1_20201223_191051-11543527.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/unet_s5-d16_128x128_40k_chase_db1/unet_s5-d16_128x128_40k_chase_db1-20201223_191051.log.json) | +| UNet + FCN | UNet-S5-D16 | Cross Entropy + Dice | 960x999 | 128x128 | 85x85 | 40000 | 0.986 | - | V100 | 89.52 | 80.40 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201821-1c4eb7cf.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201821.log.json) | +| UNet + PSPNet | UNet-S5-D16 | Cross Entropy | 960x999 | 128x128 | 85x85 | 40000 | 0.982 | - | V100 | 89.52 | 80.36 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_pspnet_4xb4-40k_chase-db1-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_128x128_40k_chase_db1/pspnet_unet_s5-d16_128x128_40k_chase_db1_20201227_181818-68d4e609.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_128x128_40k_chase_db1/pspnet_unet_s5-d16_128x128_40k_chase_db1-20201227_181818.log.json) | +| UNet + PSPNet | UNet-S5-D16 | Cross Entropy + Dice | 960x999 | 128x128 | 85x85 | 40000 | 1.028 | - | V100 | 89.45 | 80.28 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201823-c0802c4d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201823.log.json) | +| UNet + DeepLabV3 | UNet-S5-D16 | Cross Entropy | 960x999 | 128x128 | 85x85 | 40000 | 0.999 | - | V100 | 89.57 | 80.47 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_chase-db1-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_128x128_40k_chase_db1/deeplabv3_unet_s5-d16_128x128_40k_chase_db1_20201226_094047-4c5aefa3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_128x128_40k_chase_db1/deeplabv3_unet_s5-d16_128x128_40k_chase_db1-20201226_094047.log.json) | +| UNet + DeepLabV3 | UNet-S5-D16 | Cross Entropy + Dice | 960x999 | 128x128 | 85x85 | 40000 | 1.010 | - | V100 | 89.49 | 80.37 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201825-4ef29df5.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201825.log.json) | + +### HRF + +| Method | Backbone | Loss | Image Size | Crop Size | Stride | Lr schd | Mem (GB) | Inf time (fps) | Device | mDice | Dice | config | download | +| ---------------- | ----------- | -------------------- | ---------- | --------- | ------: | ------- | -------- | -------------: | ------ | ----: | ----: | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UNet + FCN | UNet-S5-D16 | Cross Entropy | 2336x3504 | 256x256 | 170x170 | 40000 | 2.525 | - | V100 | 88.92 | 79.45 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-40k_hrf-256x256.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_256x256_40k_hrf/fcn_unet_s5-d16_256x256_40k_hrf_20201223_173724-d89cf1ed.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/unet_s5-d16_256x256_40k_hrf/unet_s5-d16_256x256_40k_hrf-20201223_173724.log.json) | +| UNet + FCN | UNet-S5-D16 | Cross Entropy + Dice | 2336x3504 | 256x256 | 170x170 | 40000 | 2.623 | - | V100 | 89.64 | 80.87 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/fcn_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_201821-c314da8a.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/fcn_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_201821.log.json) | +| UNet + PSPNet | UNet-S5-D16 | Cross Entropy | 2336x3504 | 256x256 | 170x170 | 40000 | 2.588 | - | V100 | 89.24 | 80.07 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_pspnet_4xb4-40k_hrf-256x256.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_256x256_40k_hrf/pspnet_unet_s5-d16_256x256_40k_hrf_20201227_181818-fdb7e29b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_256x256_40k_hrf/pspnet_unet_s5-d16_256x256_40k_hrf-20201227_181818.log.json) | +| UNet + PSPNet | UNet-S5-D16 | Cross Entropy + Dice | 2336x3504 | 256x256 | 170x170 | 40000 | 2.798 | - | V100 | 89.69 | 80.96 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/pspnet_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_201823-53d492fa.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/pspnet_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_201823.log.json) | +| UNet + DeepLabV3 | UNet-S5-D16 | Cross Entropy | 2336x3504 | 256x256 | 170x170 | 40000 | 2.604 | - | V100 | 89.32 | 80.21 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_hrf-256x256.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_256x256_40k_hrf/deeplabv3_unet_s5-d16_256x256_40k_hrf_20201226_094047-3a1fdf85.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_256x256_40k_hrf/deeplabv3_unet_s5-d16_256x256_40k_hrf-20201226_094047.log.json) | +| UNet + DeepLabV3 | UNet-S5-D16 | Cross Entropy + Dice | 2336x3504 | 256x256 | 170x170 | 40000 | 2.607 | - | V100 | 89.56 | 80.71 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_202032-59daf7a4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_202032.log.json) | + +Note: + +- In `DRIVE`, `STARE`, `CHASE_DB1`, and `HRF` dataset, `mDice` is mean dice of background and vessel, while `Dice` is dice metric of vessel(foreground) only. + +## Citation + +```bibtex +@inproceedings{ronneberger2015u, + title={U-net: Convolutional networks for biomedical image segmentation}, + author={Ronneberger, Olaf and Fischer, Philipp and Brox, Thomas}, + booktitle={International Conference on Medical image computing and computer-assisted intervention}, + pages={234--241}, + year={2015}, + organization={Springer} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/unet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/unet/metafile.yaml new file mode 100644 index 0000000..1eafbc6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/metafile.yaml @@ -0,0 +1,642 @@ +Collections: +- Name: UNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - DRIVE + - STARE + - CHASE_DB1 + - HRF + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + README: configs/unet/README.md + Frameworks: + - PyTorch +Models: +- Name: unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 69.1 + mIoU(ms+flip): 71.05 + Config: configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 17.91 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes/fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes_20211210_145204-6860854e.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes/fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes_20211210_145204.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_fcn_4xb4-40k_drive-64x64 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: DRIVE + Metrics: + mDice: 88.38 + Dice: 78.67 + Config: configs/unet/unet-s5-d16_fcn_4xb4-40k_drive-64x64.py + Metadata: + Training Data: DRIVE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 0.68 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_64x64_40k_drive/fcn_unet_s5-d16_64x64_40k_drive_20201223_191051-5daf6d3b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/unet_s5-d16_64x64_40k_drive/unet_s5-d16_64x64_40k_drive-20201223_191051.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_drive-64x64 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: DRIVE + Metrics: + mDice: 88.71 + Dice: 79.32 + Config: configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py + Metadata: + Training Data: DRIVE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 0.582 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/fcn_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201820-785de5c2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/fcn_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201820.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_pspnet_4xb4-40k_drive-64x64 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: DRIVE + Metrics: + mDice: 88.35 + Dice: 78.62 + Config: configs/unet/unet-s5-d16_pspnet_4xb4-40k_drive-64x64.py + Metadata: + Training Data: DRIVE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 0.599 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_64x64_40k_drive/pspnet_unet_s5-d16_64x64_40k_drive_20201227_181818-aac73387.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_64x64_40k_drive/pspnet_unet_s5-d16_64x64_40k_drive-20201227_181818.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_drive-64x64 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: DRIVE + Metrics: + mDice: 88.76 + Dice: 79.42 + Config: configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py + Metadata: + Training Data: DRIVE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 0.585 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/pspnet_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201821-22b3e3ba.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/pspnet_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201821.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_deeplabv3_4xb4-40k_drive-64x64 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: DRIVE + Metrics: + mDice: 88.38 + Dice: 78.69 + Config: configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_drive-64x64.py + Metadata: + Training Data: DRIVE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 0.596 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_64x64_40k_drive/deeplabv3_unet_s5-d16_64x64_40k_drive_20201226_094047-0671ff20.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_64x64_40k_drive/deeplabv3_unet_s5-d16_64x64_40k_drive-20201226_094047.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_drive-64x64 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: DRIVE + Metrics: + mDice: 88.84 + Dice: 79.56 + Config: configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py + Metadata: + Training Data: DRIVE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 0.582 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201825-6bf0efd7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_64x64_40k_drive_20211210_201825.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_fcn_4xb4-40k_stare-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: STARE + Metrics: + mDice: 89.78 + Dice: 81.02 + Config: configs/unet/unet-s5-d16_fcn_4xb4-40k_stare-128x128.py + Metadata: + Training Data: STARE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 0.968 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_128x128_40k_stare/fcn_unet_s5-d16_128x128_40k_stare_20201223_191051-7d77e78b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/unet_s5-d16_128x128_40k_stare/unet_s5-d16_128x128_40k_stare-20201223_191051.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_stare-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: STARE + Metrics: + mDice: 90.65 + Dice: 82.7 + Config: configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py + Metadata: + Training Data: STARE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 0.986 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201821-f75705a9.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201821.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_pspnet_4xb4-40k_stare-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: STARE + Metrics: + mDice: 89.89 + Dice: 81.22 + Config: configs/unet/unet-s5-d16_pspnet_4xb4-40k_stare-128x128.py + Metadata: + Training Data: STARE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 0.982 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_128x128_40k_stare/pspnet_unet_s5-d16_128x128_40k_stare_20201227_181818-3c2923c4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_128x128_40k_stare/pspnet_unet_s5-d16_128x128_40k_stare-20201227_181818.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_stare-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: STARE + Metrics: + mDice: 90.72 + Dice: 82.84 + Config: configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py + Metadata: + Training Data: STARE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.028 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201823-f1063ef7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201823.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_deeplabv3_4xb4-40k_stare-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: STARE + Metrics: + mDice: 89.73 + Dice: 80.93 + Config: configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_stare-128x128.py + Metadata: + Training Data: STARE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 0.999 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_128x128_40k_stare/deeplabv3_unet_s5-d16_128x128_40k_stare_20201226_094047-93dcb93c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_128x128_40k_stare/deeplabv3_unet_s5-d16_128x128_40k_stare-20201226_094047.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_stare-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: STARE + Metrics: + mDice: 90.65 + Dice: 82.71 + Config: configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py + Metadata: + Training Data: STARE + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 1.01 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201825-21db614c.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_stare_20211210_201825.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_fcn_4xb4-40k_chase-db1-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: CHASE_DB1 + Metrics: + mDice: 89.46 + Dice: 80.24 + Config: configs/unet/unet-s5-d16_fcn_4xb4-40k_chase-db1-128x128.py + Metadata: + Training Data: CHASE_DB1 + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 0.968 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_128x128_40k_chase_db1/fcn_unet_s5-d16_128x128_40k_chase_db1_20201223_191051-11543527.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/unet_s5-d16_128x128_40k_chase_db1/unet_s5-d16_128x128_40k_chase_db1-20201223_191051.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: CHASE_DB1 + Metrics: + mDice: 89.52 + Dice: 80.4 + Config: configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py + Metadata: + Training Data: CHASE_DB1 + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 0.986 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201821-1c4eb7cf.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/fcn_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201821.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_pspnet_4xb4-40k_chase-db1-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: CHASE_DB1 + Metrics: + mDice: 89.52 + Dice: 80.36 + Config: configs/unet/unet-s5-d16_pspnet_4xb4-40k_chase-db1-128x128.py + Metadata: + Training Data: CHASE_DB1 + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 0.982 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_128x128_40k_chase_db1/pspnet_unet_s5-d16_128x128_40k_chase_db1_20201227_181818-68d4e609.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_128x128_40k_chase_db1/pspnet_unet_s5-d16_128x128_40k_chase_db1-20201227_181818.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: CHASE_DB1 + Metrics: + mDice: 89.45 + Dice: 80.28 + Config: configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py + Metadata: + Training Data: CHASE_DB1 + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 1.028 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201823-c0802c4d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/pspnet_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201823.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet_s5-d16_deeplabv3_4xb4-40k_chase-db1-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: CHASE_DB1 + Metrics: + mDice: 89.57 + Dice: 80.47 + Config: configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_chase-db1-128x128.py + Metadata: + Training Data: CHASE_DB1 + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 0.999 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_128x128_40k_chase_db1/deeplabv3_unet_s5-d16_128x128_40k_chase_db1_20201226_094047-4c5aefa3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_128x128_40k_chase_db1/deeplabv3_unet_s5-d16_128x128_40k_chase_db1-20201226_094047.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: CHASE_DB1 + Metrics: + mDice: 89.49 + Dice: 80.37 + Config: configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py + Metadata: + Training Data: CHASE_DB1 + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 1.01 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201825-4ef29df5.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_128x128_40k_chase-db1_20211210_201825.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_fcn_4xb4-40k_hrf-256x256 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: HRF + Metrics: + mDice: 88.92 + Dice: 79.45 + Config: configs/unet/unet-s5-d16_fcn_4xb4-40k_hrf-256x256.py + Metadata: + Training Data: HRF + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 2.525 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_256x256_40k_hrf/fcn_unet_s5-d16_256x256_40k_hrf_20201223_173724-d89cf1ed.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/unet_s5-d16_256x256_40k_hrf/unet_s5-d16_256x256_40k_hrf-20201223_173724.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: HRF + Metrics: + mDice: 89.64 + Dice: 80.87 + Config: configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py + Metadata: + Training Data: HRF + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - FCN + Training Resources: 4x V100 GPUS + Memory (GB): 2.623 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/fcn_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_201821-c314da8a.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/fcn_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/fcn_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_201821.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_pspnet_4xb4-40k_hrf-256x256 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: HRF + Metrics: + mDice: 89.24 + Dice: 80.07 + Config: configs/unet/unet-s5-d16_pspnet_4xb4-40k_hrf-256x256.py + Metadata: + Training Data: HRF + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 2.588 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_256x256_40k_hrf/pspnet_unet_s5-d16_256x256_40k_hrf_20201227_181818-fdb7e29b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_256x256_40k_hrf/pspnet_unet_s5-d16_256x256_40k_hrf-20201227_181818.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: HRF + Metrics: + mDice: 89.69 + Dice: 80.96 + Config: configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py + Metadata: + Training Data: HRF + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - PSPNet + Training Resources: 4x V100 GPUS + Memory (GB): 2.798 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/pspnet_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_201823-53d492fa.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/pspnet_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/pspnet_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_201823.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_deeplabv3_4xb4-40k_hrf-256x256 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: HRF + Metrics: + mDice: 89.32 + Dice: 80.21 + Config: configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_hrf-256x256.py + Metadata: + Training Data: HRF + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 2.604 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_256x256_40k_hrf/deeplabv3_unet_s5-d16_256x256_40k_hrf_20201226_094047-3a1fdf85.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_256x256_40k_hrf/deeplabv3_unet_s5-d16_256x256_40k_hrf-20201226_094047.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch +- Name: unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256 + In Collection: UNet + Results: + Task: Semantic Segmentation + Dataset: HRF + Metrics: + mDice: 89.56 + Dice: 80.71 + Config: configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py + Metadata: + Training Data: HRF + Batch Size: 16 + Architecture: + - UNet-S5-D16 + - UNet + - DeepLabV3 + Training Resources: 4x V100 GPUS + Memory (GB): 2.607 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_202032-59daf7a4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/unet/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf/deeplabv3_unet_s5-d16_ce-1.0-dice-3.0_256x256_40k_hrf_20211210_202032.log.json + Paper: + Title: 'U-Net: Convolutional Networks for Biomedical Image Segmentation' + URL: https://arxiv.org/abs/1505.04597 + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/unet.py#L225 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/unet/my_unet_deeplabv3_g1-40k_check_4000_my_dataset_model-128x128-testslide.py b/Seg_All_In_One_MMSeg/configs/unet/my_unet_deeplabv3_g1-40k_check_4000_my_dataset_model-128x128-testslide.py new file mode 100644 index 0000000..d2d4507 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/my_unet_deeplabv3_g1-40k_check_4000_my_dataset_model-128x128-testslide.py @@ -0,0 +1,107 @@ +_base_ = [ + '../_base_/models/deeplabv3_unet_s5-d16.py', + '../_base_/datasets/my_dataset_model.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k_check_4000.py', +] + +norm_cfg = dict( + type='BN', +) + +crop_size = (128, 128) + +data_preprocessor = dict( + size=(128, 128), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, +) + +model = dict( + data_preprocessor=dict( + size=(128, 128), + mean=[ + 94.94709810464303, + 61.72942233949928, + 75.93763705236906, + ], + std=[ + 44.005506081132594, + 42.69595666984776, + 44.99354156225523, + ], + bgr_to_rgb=False, + ), + decode_head=dict( + num_classes=36, + loss_decode=[ + dict( + type='CrossEntropyLoss', + loss_name='loss_ce', + loss_weight=1.0, + ), + dict( + type='DiceLoss', + loss_name='loss_dice', + loss_weight=3.0, + ), + ], + align_corners=True, + norm_cfg=dict( + type='BN', + ), + ), + test_cfg=dict( + mode='slide', + crop_size=(128, 128), + stride=(85, 85), + ), + auxiliary_head=dict( + num_classes=36, + norm_cfg=dict( + type='BN', + ), + ), +) + +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict( + type='AdamW', + lr=0.0001, + weight_decay=0.0005, + ), + clip_grad=dict( + max_norm=1, + norm_type=2, + ), +) + +param_scheduler = [ + dict( + type='LinearLR', + start_factor=1e-06, + by_epoch=False, + begin=0, + end=1500, + ), + dict( + type='PolyLR', + power=0.9, + begin=1500, + end=40000, + eta_min=1e-05, + by_epoch=False, + ), +] + diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_drive-64x64.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_drive-64x64.py new file mode 100644 index 0000000..e4af542 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_drive-64x64.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/deeplabv3_unet_s5-d16.py', '../_base_/datasets/drive.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (64, 64) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(64, 64), stride=(42, 42))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_hrf-256x256.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_hrf-256x256.py new file mode 100644 index 0000000..b45405f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_hrf-256x256.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/deeplabv3_unet_s5-d16.py', '../_base_/datasets/hrf.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (256, 256) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(256, 256), stride=(170, 170))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_stare-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_stare-128x128.py new file mode 100644 index 0000000..554caca --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-40k_stare-128x128.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/deeplabv3_unet_s5-d16.py', '../_base_/datasets/stare.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (128, 128) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(128, 128), stride=(85, 85))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py new file mode 100644 index 0000000..4f30bba --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py @@ -0,0 +1,6 @@ +_base_ = './unet_s5-d16_deeplabv3_4xb4-40k_chase-db1-128x128.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py new file mode 100644 index 0000000..823fc6d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_deeplabv3_4xb4-40k_drive-64x64.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py new file mode 100644 index 0000000..174eaf8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_deeplabv3_4xb4-40k_hrf-256x256.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py new file mode 100644 index 0000000..35972be --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_deeplabv3_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_deeplabv3_4xb4-40k_stare-128x128.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..c2e995d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py @@ -0,0 +1,16 @@ +_base_ = [ + '../_base_/models/fcn_unet_s5-d16.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=19), + auxiliary_head=dict(num_classes=19), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) +train_dataloader = dict(batch_size=4, num_workers=4) +val_dataloader = dict(batch_size=1, num_workers=4) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py new file mode 100644 index 0000000..a5768ba --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py @@ -0,0 +1,36 @@ +_base_ = [ + '../_base_/models/fcn_unet_s5-d16.py', '../_base_/datasets/hsi_drive.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (192, 384) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=crop_size, + mean=None, + std=None, + bgr_to_rgb=None, + pad_val=0, + seg_pad_val=255) + +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(in_channels=25), + decode_head=dict( + ignore_index=0, + num_classes=11, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + avg_non_ignore=True)), + auxiliary_head=dict( + ignore_index=0, + num_classes=11, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + avg_non_ignore=True)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_chase-db1-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_chase-db1-128x128.py new file mode 100644 index 0000000..bfc2109 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_chase-db1-128x128.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/fcn_unet_s5-d16.py', '../_base_/datasets/chase_db1.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (128, 128) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(128, 128), stride=(85, 85))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_drive-64x64.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_drive-64x64.py new file mode 100644 index 0000000..10a45d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_drive-64x64.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/fcn_unet_s5-d16.py', '../_base_/datasets/drive.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (64, 64) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(64, 64), stride=(42, 42))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_hrf-256x256.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_hrf-256x256.py new file mode 100644 index 0000000..7de57f2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_hrf-256x256.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/fcn_unet_s5-d16.py', '../_base_/datasets/hrf.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (256, 256) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(256, 256), stride=(170, 170))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_stare-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_stare-128x128.py new file mode 100644 index 0000000..8eeef77 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-40k_stare-128x128.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/fcn_unet_s5-d16.py', '../_base_/datasets/stare.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (128, 128) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(128, 128), stride=(85, 85))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py new file mode 100644 index 0000000..5a26ccb --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_fcn_4xb4-40k_chase-db1-128x128.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py new file mode 100644 index 0000000..c3b1488 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_fcn_4xb4-40k_drive-64x64.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py new file mode 100644 index 0000000..dd3a6af --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_fcn_4xb4-40k_hrf-256x256.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py new file mode 100644 index 0000000..c8fecf3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_fcn_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_fcn_4xb4-40k_stare-128x128.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_chase-db1-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_chase-db1-128x128.py new file mode 100644 index 0000000..ca6e513 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_chase-db1-128x128.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/pspnet_unet_s5-d16.py', + '../_base_/datasets/chase_db1.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (128, 128) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(128, 128), stride=(85, 85))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_drive-64x64.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_drive-64x64.py new file mode 100644 index 0000000..503b901 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_drive-64x64.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/pspnet_unet_s5-d16.py', '../_base_/datasets/drive.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (64, 64) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(64, 64), stride=(42, 42))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_hrf-256x256.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_hrf-256x256.py new file mode 100644 index 0000000..245365c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_hrf-256x256.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/pspnet_unet_s5-d16.py', '../_base_/datasets/hrf.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (256, 256) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(256, 256), stride=(170, 170))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_stare-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_stare-128x128.py new file mode 100644 index 0000000..c1eeeb9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-40k_stare-128x128.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/pspnet_unet_s5-d16.py', '../_base_/datasets/stare.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (128, 128) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(128, 128), stride=(85, 85))) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py new file mode 100644 index 0000000..69a4bba --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_chase-db1-128x128.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_pspnet_4xb4-40k_chase-db1-128x128.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py new file mode 100644 index 0000000..1abbd53 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_drive-64x64.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_pspnet_4xb4-40k_drive-64x64.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py new file mode 100644 index 0000000..b3256d7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_hrf-256x256.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_pspnet_4xb4-40k_hrf-256x256.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py new file mode 100644 index 0000000..82aa3da --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet-s5-d16_pspnet_4xb4-ce-1.0-dice-3.0-40k_stare-128x128.py @@ -0,0 +1,6 @@ +_base_ = './unet-s5-d16_pspnet_4xb4-40k_stare-128x128.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ])) diff --git a/Seg_All_In_One_MMSeg/configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_chase-db1-128x128.py b/Seg_All_In_One_MMSeg/configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_chase-db1-128x128.py new file mode 100644 index 0000000..82494f3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_chase-db1-128x128.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/deeplabv3_unet_s5-d16.py', + '../_base_/datasets/chase_db1.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (128, 128) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + test_cfg=dict(crop_size=(128, 128), stride=(85, 85))) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/README.md b/Seg_All_In_One_MMSeg/configs/upernet/README.md new file mode 100644 index 0000000..c2babbd --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/README.md @@ -0,0 +1,68 @@ +# UPerNet + +> [Unified Perceptual Parsing for Scene Understanding](https://arxiv.org/pdf/1807.10221.pdf) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Humans recognize the visual world at multiple levels: we effortlessly categorize scenes and detect objects inside, while also identifying the textures and surfaces of the objects along with their different compositional parts. In this paper, we study a new task called Unified Perceptual Parsing, which requires the machine vision systems to recognize as many visual concepts as possible from a given image. A multi-task framework called UPerNet and a training strategy are developed to learn from heterogeneous image annotations. We benchmark our framework on Unified Perceptual Parsing and show that it is able to effectively segment a wide range of concepts from images. The trained networks are further applied to discover visual knowledge in natural scenes. Models are available at [this https URL](https://github.com/CSAILVision/unifiedparsing). + + + +
+ +
+ +## Results and models + +### Cityscapes + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UPerNet | R-50 | 512x1024 | 40000 | 6.4 | 4.25 | V100 | 77.10 | 78.37 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x1024_40k_cityscapes/upernet_r50_512x1024_40k_cityscapes_20200605_094827-aa54cb54.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x1024_40k_cityscapes/upernet_r50_512x1024_40k_cityscapes_20200605_094827.log.json) | +| UPerNet | R-101 | 512x1024 | 40000 | 7.4 | 3.79 | V100 | 78.69 | 80.11 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r101_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x1024_40k_cityscapes/upernet_r101_512x1024_40k_cityscapes_20200605_094933-ebce3b10.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x1024_40k_cityscapes/upernet_r101_512x1024_40k_cityscapes_20200605_094933.log.json) | +| UPerNet | R-50 | 769x769 | 40000 | 7.2 | 1.76 | V100 | 77.98 | 79.70 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r50_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_769x769_40k_cityscapes/upernet_r50_769x769_40k_cityscapes_20200530_033048-92d21539.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_769x769_40k_cityscapes/upernet_r50_769x769_40k_cityscapes_20200530_033048.log.json) | +| UPerNet | R-101 | 769x769 | 40000 | 8.4 | 1.56 | V100 | 79.03 | 80.77 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r101_4xb2-40k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_769x769_40k_cityscapes/upernet_r101_769x769_40k_cityscapes_20200530_040819-83c95d01.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_769x769_40k_cityscapes/upernet_r101_769x769_40k_cityscapes_20200530_040819.log.json) | +| UPerNet | R-50 | 512x1024 | 80000 | - | - | V100 | 78.19 | 79.19 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r50_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x1024_80k_cityscapes/upernet_r50_512x1024_80k_cityscapes_20200607_052207-848beca8.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x1024_80k_cityscapes/upernet_r50_512x1024_80k_cityscapes_20200607_052207.log.json) | +| UPerNet | R-101 | 512x1024 | 80000 | - | - | V100 | 79.40 | 80.46 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r101_4xb2-80k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x1024_80k_cityscapes/upernet_r101_512x1024_80k_cityscapes_20200607_002403-f05f2345.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x1024_80k_cityscapes/upernet_r101_512x1024_80k_cityscapes_20200607_002403.log.json) | +| UPerNet | R-50 | 769x769 | 80000 | - | - | V100 | 79.39 | 80.92 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r50_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_769x769_80k_cityscapes/upernet_r50_769x769_80k_cityscapes_20200607_005107-82ae7d15.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_769x769_80k_cityscapes/upernet_r50_769x769_80k_cityscapes_20200607_005107.log.json) | +| UPerNet | R-101 | 769x769 | 80000 | - | - | V100 | 80.10 | 81.49 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r101_4xb2-80k_cityscapes-769x769.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_769x769_80k_cityscapes/upernet_r101_769x769_80k_cityscapes_20200607_001014-082fc334.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_769x769_80k_cityscapes/upernet_r101_769x769_80k_cityscapes_20200607_001014.log.json) | + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UPerNet | R-50 | 512x512 | 80000 | 8.1 | 23.40 | V100 | 40.70 | 41.81 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r50_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_80k_ade20k/upernet_r50_512x512_80k_ade20k_20200614_144127-ecc8377b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_80k_ade20k/upernet_r50_512x512_80k_ade20k_20200614_144127.log.json) | +| UPerNet | R-101 | 512x512 | 80000 | 9.1 | 20.34 | V100 | 42.91 | 43.96 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r101_4xb4-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_80k_ade20k/upernet_r101_512x512_80k_ade20k_20200614_185117-32e4db94.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_80k_ade20k/upernet_r101_512x512_80k_ade20k_20200614_185117.log.json) | +| UPerNet | R-50 | 512x512 | 160000 | - | - | V100 | 42.05 | 42.78 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r50_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_160k_ade20k/upernet_r50_512x512_160k_ade20k_20200615_184328-8534de8d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_160k_ade20k/upernet_r50_512x512_160k_ade20k_20200615_184328.log.json) | +| UPerNet | R-101 | 512x512 | 160000 | - | - | V100 | 43.82 | 44.85 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r101_4xb4-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_160k_ade20k/upernet_r101_512x512_160k_ade20k_20200615_161951-91b32684.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_160k_ade20k/upernet_r101_512x512_160k_ade20k_20200615_161951.log.json) | + +### Pascal VOC 2012 + Aug + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | -------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UPerNet | R-50 | 512x512 | 20000 | 6.4 | 23.17 | V100 | 74.82 | 76.35 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r50_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_20k_voc12aug/upernet_r50_512x512_20k_voc12aug_20200617_165330-5b5890a7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_20k_voc12aug/upernet_r50_512x512_20k_voc12aug_20200617_165330.log.json) | +| UPerNet | R-101 | 512x512 | 20000 | 7.5 | 19.98 | V100 | 77.10 | 78.29 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r101_4xb4-20k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_20k_voc12aug/upernet_r101_512x512_20k_voc12aug_20200617_165629-f14e7f27.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_20k_voc12aug/upernet_r101_512x512_20k_voc12aug_20200617_165629.log.json) | +| UPerNet | R-50 | 512x512 | 40000 | - | - | V100 | 75.92 | 77.44 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r50_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_40k_voc12aug/upernet_r50_512x512_40k_voc12aug_20200613_162257-ca9bcc6b.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_40k_voc12aug/upernet_r50_512x512_40k_voc12aug_20200613_162257.log.json) | +| UPerNet | R-101 | 512x512 | 40000 | - | - | V100 | 77.43 | 78.56 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet/upernet_r101_4xb4-40k_voc12aug-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_40k_voc12aug/upernet_r101_512x512_40k_voc12aug_20200613_163549-e26476ac.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_40k_voc12aug/upernet_r101_512x512_40k_voc12aug_20200613_163549.log.json) | + +## Citation + +```bibtex +@inproceedings{xiao2018unified, + title={Unified perceptual parsing for scene understanding}, + author={Xiao, Tete and Liu, Yingcheng and Zhou, Bolei and Jiang, Yuning and Sun, Jian}, + booktitle={Proceedings of the European Conference on Computer Vision (ECCV)}, + pages={418--434}, + year={2018} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/upernet/metafile.yaml b/Seg_All_In_One_MMSeg/configs/upernet/metafile.yaml new file mode 100644 index 0000000..f6ad818 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/metafile.yaml @@ -0,0 +1,391 @@ +Collections: +- Name: UPerNet + License: Apache License 2.0 + Metadata: + Training Data: + - Cityscapes + - ADE20K + - Pascal VOC 2012 + Aug + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + README: configs/upernet/README.md + Frameworks: + - PyTorch +Models: +- Name: upernet_r50_4xb2-40k_cityscapes-512x1024 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.1 + mIoU(ms+flip): 78.37 + Config: configs/upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50 + - UPerNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x1024_40k_cityscapes/upernet_r50_512x1024_40k_cityscapes_20200605_094827-aa54cb54.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x1024_40k_cityscapes/upernet_r50_512x1024_40k_cityscapes_20200605_094827.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r101_4xb2-40k_cityscapes-512x1024 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.69 + mIoU(ms+flip): 80.11 + Config: configs/upernet/upernet_r101_4xb2-40k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101 + - UPerNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x1024_40k_cityscapes/upernet_r101_512x1024_40k_cityscapes_20200605_094933-ebce3b10.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x1024_40k_cityscapes/upernet_r101_512x1024_40k_cityscapes_20200605_094933.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r50_4xb2-40k_cityscapes-769x769 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 77.98 + mIoU(ms+flip): 79.7 + Config: configs/upernet/upernet_r50_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50 + - UPerNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_769x769_40k_cityscapes/upernet_r50_769x769_40k_cityscapes_20200530_033048-92d21539.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_769x769_40k_cityscapes/upernet_r50_769x769_40k_cityscapes_20200530_033048.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r101_4xb2-40k_cityscapes-769x769 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.03 + mIoU(ms+flip): 80.77 + Config: configs/upernet/upernet_r101_4xb2-40k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101 + - UPerNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_769x769_40k_cityscapes/upernet_r101_769x769_40k_cityscapes_20200530_040819-83c95d01.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_769x769_40k_cityscapes/upernet_r101_769x769_40k_cityscapes_20200530_040819.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r50_4xb2-80k_cityscapes-512x1024 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 78.19 + mIoU(ms+flip): 79.19 + Config: configs/upernet/upernet_r50_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50 + - UPerNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x1024_80k_cityscapes/upernet_r50_512x1024_80k_cityscapes_20200607_052207-848beca8.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x1024_80k_cityscapes/upernet_r50_512x1024_80k_cityscapes_20200607_052207.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r101_4xb2-80k_cityscapes-512x1024 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.4 + mIoU(ms+flip): 80.46 + Config: configs/upernet/upernet_r101_4xb2-80k_cityscapes-512x1024.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101 + - UPerNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x1024_80k_cityscapes/upernet_r101_512x1024_80k_cityscapes_20200607_002403-f05f2345.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x1024_80k_cityscapes/upernet_r101_512x1024_80k_cityscapes_20200607_002403.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r50_4xb2-80k_cityscapes-769x769 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 79.39 + mIoU(ms+flip): 80.92 + Config: configs/upernet/upernet_r50_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-50 + - UPerNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_769x769_80k_cityscapes/upernet_r50_769x769_80k_cityscapes_20200607_005107-82ae7d15.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_769x769_80k_cityscapes/upernet_r50_769x769_80k_cityscapes_20200607_005107.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r101_4xb2-80k_cityscapes-769x769 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Cityscapes + Metrics: + mIoU: 80.1 + mIoU(ms+flip): 81.49 + Config: configs/upernet/upernet_r101_4xb2-80k_cityscapes-769x769.py + Metadata: + Training Data: Cityscapes + Batch Size: 8 + Architecture: + - R-101 + - UPerNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_769x769_80k_cityscapes/upernet_r101_769x769_80k_cityscapes_20200607_001014-082fc334.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_769x769_80k_cityscapes/upernet_r101_769x769_80k_cityscapes_20200607_001014.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r50_4xb4-80k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 40.7 + mIoU(ms+flip): 41.81 + Config: configs/upernet/upernet_r50_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50 + - UPerNet + Training Resources: 4x V100 GPUS + Memory (GB): 8.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_80k_ade20k/upernet_r50_512x512_80k_ade20k_20200614_144127-ecc8377b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_80k_ade20k/upernet_r50_512x512_80k_ade20k_20200614_144127.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r101_4xb4-80k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.91 + mIoU(ms+flip): 43.96 + Config: configs/upernet/upernet_r101_4xb4-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101 + - UPerNet + Training Resources: 4x V100 GPUS + Memory (GB): 9.1 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_80k_ade20k/upernet_r101_512x512_80k_ade20k_20200614_185117-32e4db94.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_80k_ade20k/upernet_r101_512x512_80k_ade20k_20200614_185117.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r50_4xb4-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.05 + mIoU(ms+flip): 42.78 + Config: configs/upernet/upernet_r50_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-50 + - UPerNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_160k_ade20k/upernet_r50_512x512_160k_ade20k_20200615_184328-8534de8d.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_160k_ade20k/upernet_r50_512x512_160k_ade20k_20200615_184328.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r101_4xb4-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.82 + mIoU(ms+flip): 44.85 + Config: configs/upernet/upernet_r101_4xb4-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - R-101 + - UPerNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_160k_ade20k/upernet_r101_512x512_160k_ade20k_20200615_161951-91b32684.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_160k_ade20k/upernet_r101_512x512_160k_ade20k_20200615_161951.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r50_4xb4-20k_voc12aug-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 74.82 + mIoU(ms+flip): 76.35 + Config: configs/upernet/upernet_r50_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50 + - UPerNet + Training Resources: 4x V100 GPUS + Memory (GB): 6.4 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_20k_voc12aug/upernet_r50_512x512_20k_voc12aug_20200617_165330-5b5890a7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_20k_voc12aug/upernet_r50_512x512_20k_voc12aug_20200617_165330.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r101_4xb4-20k_voc12aug-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.1 + mIoU(ms+flip): 78.29 + Config: configs/upernet/upernet_r101_4xb4-20k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101 + - UPerNet + Training Resources: 4x V100 GPUS + Memory (GB): 7.5 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_20k_voc12aug/upernet_r101_512x512_20k_voc12aug_20200617_165629-f14e7f27.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_20k_voc12aug/upernet_r101_512x512_20k_voc12aug_20200617_165629.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r50_4xb4-40k_voc12aug-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 75.92 + mIoU(ms+flip): 77.44 + Config: configs/upernet/upernet_r50_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-50 + - UPerNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_40k_voc12aug/upernet_r50_512x512_40k_voc12aug_20200613_162257-ca9bcc6b.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r50_512x512_40k_voc12aug/upernet_r50_512x512_40k_voc12aug_20200613_162257.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch +- Name: upernet_r101_4xb4-40k_voc12aug-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: Pascal VOC 2012 + Aug + Metrics: + mIoU: 77.43 + mIoU(ms+flip): 78.56 + Config: configs/upernet/upernet_r101_4xb4-40k_voc12aug-512x512.py + Metadata: + Training Data: Pascal VOC 2012 + Aug + Batch Size: 16 + Architecture: + - R-101 + - UPerNet + Training Resources: 4x V100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_40k_voc12aug/upernet_r101_512x512_40k_voc12aug_20200613_163549-e26476ac.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/upernet/upernet_r101_512x512_40k_voc12aug/upernet_r101_512x512_40k_voc12aug_20200613_163549.log.json + Paper: + Title: Unified Perceptual Parsing for Scene Understanding + URL: https://arxiv.org/pdf/1807.10221.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/decode_heads/uper_head.py#L13 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..8f5f6ae --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './upernet_r50_4xb2-40k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..28b5d3e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './upernet_r50_4xb2-40k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..cafd8a2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,2 @@ +_base_ = './upernet_r50_4xb2-80k_cityscapes-512x1024.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..e175720 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,2 @@ +_base_ = './upernet_r50_4xb2-80k_cityscapes-769x769.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..7a61527 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './upernet_r50_4xb4-160k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..be8f084 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './upernet_r50_4xb4-20k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..db1d976 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,2 @@ +_base_ = './upernet_r50_4xb4-40k_voc12aug-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..84549a4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r101_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,2 @@ +_base_ = './upernet_r50_4xb4-80k_ade20k-512x512.py' +model = dict(pretrained='open-mmlab://resnet101_v1c', backbone=dict(depth=101)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..dbff0e7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,6 @@ +_base_ = './upernet_r50_4xb2-40k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict(in_channels=[64, 128, 256, 512]), + auxiliary_head=dict(in_channels=256)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..dee6349 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,6 @@ +_base_ = './upernet_r50_4xb2-80k_cityscapes-512x1024.py' +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict(in_channels=[64, 128, 256, 512]), + auxiliary_head=dict(in_channels=256)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..9ac6c35 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict(in_channels=[64, 128, 256, 512], num_classes=150), + auxiliary_head=dict(in_channels=256, num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..5cae4f5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict(in_channels=[64, 128, 256, 512], num_classes=21), + auxiliary_head=dict(in_channels=256, num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..652ded7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict(in_channels=[64, 128, 256, 512], num_classes=21), + auxiliary_head=dict(in_channels=256, num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..1a7956d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r18_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +model = dict( + pretrained='open-mmlab://resnet18_v1c', + backbone=dict(depth=18), + decode_head=dict(in_channels=[64, 128, 256, 512], num_classes=150), + auxiliary_head=dict(in_channels=256, num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..4751fc1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-40k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-40k_cityscapes-769x769.py new file mode 100644 index 0000000..6f05b6c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-40k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-80k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-80k_cityscapes-512x1024.py new file mode 100644 index 0000000..f3488c6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-80k_cityscapes-512x1024.py @@ -0,0 +1,7 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-80k_cityscapes-769x769.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-80k_cityscapes-769x769.py new file mode 100644 index 0000000..6a8f48e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb2-80k_cityscapes-769x769.py @@ -0,0 +1,12 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', + '../_base_/datasets/cityscapes_769x769.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (769, 769) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(align_corners=True), + auxiliary_head=dict(align_corners=True), + test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513))) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..5d15b2a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-20k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-20k_voc12aug-512x512.py new file mode 100644 index 0000000..9e96b4e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-20k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_20k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-40k_voc12aug-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-40k_voc12aug-512x512.py new file mode 100644 index 0000000..cada949 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-40k_voc12aug-512x512.py @@ -0,0 +1,11 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', + '../_base_/datasets/pascal_voc12_aug.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_40k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=21), + auxiliary_head=dict(num_classes=21)) diff --git a/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-80k_ade20k-512x512.py new file mode 100644 index 0000000..322d5d8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/upernet/upernet_r50_4xb4-80k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = [ + '../_base_/models/upernet_r50.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/configs/vit/README.md b/Seg_All_In_One_MMSeg/configs/vit/README.md new file mode 100644 index 0000000..f75326e --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/README.md @@ -0,0 +1,70 @@ +# Vision Transformer + +> [An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale](https://arxiv.org/pdf/2010.11929.pdf) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +While the Transformer architecture has become the de-facto standard for natural language processing tasks, its applications to computer vision remain limited. In vision, attention is either applied in conjunction with convolutional networks, or used to replace certain components of convolutional networks while keeping their overall structure in place. We show that this reliance on CNNs is not necessary and a pure transformer applied directly to sequences of image patches can perform very well on image classification tasks. When pre-trained on large amounts of data and transferred to multiple mid-sized or small image recognition benchmarks (ImageNet, CIFAR-100, VTAB, etc.), Vision Transformer (ViT) attains excellent results compared to state-of-the-art convolutional networks while requiring substantially fewer computational resources to train. + + + +
+ +
+ +## Usage + +To use other repositories' pre-trained models, it is necessary to convert keys. + +We provide a script [`vit2mmseg.py`](../../tools/model_converters/vit2mmseg.py) in the tools directory to convert the key of models from [timm](https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py) to MMSegmentation style. + +```shell +python tools/model_converters/vit2mmseg.py ${PRETRAIN_PATH} ${STORE_PATH} +``` + +E.g. + +```shell +python tools/model_converters/vit2mmseg.py https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_base_p16_224-80ecf9dd.pth pretrain/jx_vit_base_p16_224-80ecf9dd.pth +``` + +This script convert model from `PRETRAIN_PATH` and store the converted model in `STORE_PATH`. + +## Results and models + +### ADE20K + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | ----------------- | --------- | ------: | -------- | -------------- | ------ | ----: | ------------: | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| UPerNet | ViT-B + MLN | 512x512 | 80000 | 9.20 | 6.94 | V100 | 47.71 | 49.51 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_mln_512x512_80k_ade20k/upernet_vit-b16_mln_512x512_80k_ade20k_20210624_130547-0403cee1.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_mln_512x512_80k_ade20k/20210624_130547.log.json) | +| UPerNet | ViT-B + MLN | 512x512 | 160000 | 9.20 | 7.58 | V100 | 46.75 | 48.46 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_mln_512x512_160k_ade20k/upernet_vit-b16_mln_512x512_160k_ade20k_20210624_130547-852fa768.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_mln_512x512_160k_ade20k/20210623_192432.log.json) | +| UPerNet | ViT-B + LN + MLN | 512x512 | 160000 | 9.21 | 6.82 | V100 | 47.73 | 49.95 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_ln_mln_512x512_160k_ade20k/upernet_vit-b16_ln_mln_512x512_160k_ade20k_20210621_172828-f444c077.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_ln_mln_512x512_160k_ade20k/20210621_172828.log.json) | +| UPerNet | DeiT-S | 512x512 | 80000 | 4.68 | 29.85 | V100 | 42.96 | 43.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_deit-s16_upernet_8xb2-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_512x512_80k_ade20k/upernet_deit-s16_512x512_80k_ade20k_20210624_095228-afc93ec2.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_512x512_80k_ade20k/20210624_095228.log.json) | +| UPerNet | DeiT-S | 512x512 | 160000 | 4.68 | 29.19 | V100 | 42.87 | 43.79 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_deit-s16_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_512x512_160k_ade20k/upernet_deit-s16_512x512_160k_ade20k_20210621_160903-5110d916.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_512x512_160k_ade20k/20210621_160903.log.json) | +| UPerNet | DeiT-S + MLN | 512x512 | 160000 | 5.69 | 11.18 | V100 | 43.82 | 45.07 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_deit-s16_mln_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_mln_512x512_160k_ade20k/upernet_deit-s16_mln_512x512_160k_ade20k_20210621_161021-fb9a5dfb.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_mln_512x512_160k_ade20k/20210621_161021.log.json) | +| UPerNet | DeiT-S + LN + MLN | 512x512 | 160000 | 5.69 | 12.39 | V100 | 43.52 | 45.01 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_deit-s16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_ln_mln_512x512_160k_ade20k/upernet_deit-s16_ln_mln_512x512_160k_ade20k_20210621_161021-c0cd652f.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_ln_mln_512x512_160k_ade20k/20210621_161021.log.json) | +| UPerNet | DeiT-B | 512x512 | 80000 | 7.75 | 9.69 | V100 | 45.24 | 46.73 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_deit-b16_upernet_8xb2-80k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_512x512_80k_ade20k/upernet_deit-b16_512x512_80k_ade20k_20210624_130529-1e090789.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_512x512_80k_ade20k/20210624_130529.log.json) | +| UPerNet | DeiT-B | 512x512 | 160000 | 7.75 | 10.39 | V100 | 45.36 | 47.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_deit-b16_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_512x512_160k_ade20k/upernet_deit-b16_512x512_160k_ade20k_20210621_180100-828705d7.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_512x512_160k_ade20k/20210621_180100.log.json) | +| UPerNet | DeiT-B + MLN | 512x512 | 160000 | 9.21 | 7.78 | V100 | 45.46 | 47.16 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_deit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_mln_512x512_160k_ade20k/upernet_deit-b16_mln_512x512_160k_ade20k_20210621_191949-4e1450f3.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_mln_512x512_160k_ade20k/20210621_191949.log.json) | +| UPerNet | DeiT-B + LN + MLN | 512x512 | 160000 | 9.21 | 7.75 | V100 | 45.37 | 47.23 | [config](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_deit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_ln_mln_512x512_160k_ade20k/upernet_deit-b16_ln_mln_512x512_160k_ade20k_20210623_153535-8a959c14.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_ln_mln_512x512_160k_ade20k/20210623_153535.log.json) | + +## Citation + +```bibtex +@article{dosoViTskiy2020, + title={An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale}, + author={DosoViTskiy, Alexey and Beyer, Lucas and Kolesnikov, Alexander and Weissenborn, Dirk and Zhai, Xiaohua and Unterthiner, Thomas and Dehghani, Mostafa and Minderer, Matthias and Heigold, Georg and Gelly, Sylvain and Uszkoreit, Jakob and Houlsby, Neil}, + journal={arXiv preprint arXiv:2010.11929}, + year={2020} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/vit/metafile.yaml b/Seg_All_In_One_MMSeg/configs/vit/metafile.yaml new file mode 100644 index 0000000..68e254a --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/metafile.yaml @@ -0,0 +1,265 @@ +Models: +- Name: vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.71 + mIoU(ms+flip): 49.51 + Config: configs/vit/vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ViT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 9.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_mln_512x512_80k_ade20k/upernet_vit-b16_mln_512x512_80k_ade20k_20210624_130547-0403cee1.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_mln_512x512_80k_ade20k/20210624_130547.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 46.75 + mIoU(ms+flip): 48.46 + Config: configs/vit/vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ViT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 9.2 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_mln_512x512_160k_ade20k/upernet_vit-b16_mln_512x512_160k_ade20k_20210624_130547-852fa768.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_mln_512x512_160k_ade20k/20210623_192432.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 47.73 + mIoU(ms+flip): 49.95 + Config: configs/vit/vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - ViT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 9.21 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_ln_mln_512x512_160k_ade20k/upernet_vit-b16_ln_mln_512x512_160k_ade20k_20210621_172828-f444c077.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_vit-b16_ln_mln_512x512_160k_ade20k/20210621_172828.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_deit-s16_upernet_8xb2-80k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.96 + mIoU(ms+flip): 43.79 + Config: configs/vit/vit_deit-s16_upernet_8xb2-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - DeiT-S + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 4.68 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_512x512_80k_ade20k/upernet_deit-s16_512x512_80k_ade20k_20210624_095228-afc93ec2.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_512x512_80k_ade20k/20210624_095228.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_deit-s16_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 42.87 + mIoU(ms+flip): 43.79 + Config: configs/vit/vit_deit-s16_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - DeiT-S + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 4.68 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_512x512_160k_ade20k/upernet_deit-s16_512x512_160k_ade20k_20210621_160903-5110d916.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_512x512_160k_ade20k/20210621_160903.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_deit-s16_mln_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.82 + mIoU(ms+flip): 45.07 + Config: configs/vit/vit_deit-s16_mln_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - DeiT-S + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 5.69 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_mln_512x512_160k_ade20k/upernet_deit-s16_mln_512x512_160k_ade20k_20210621_161021-fb9a5dfb.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_mln_512x512_160k_ade20k/20210621_161021.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_deit-s16-ln_mln_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 43.52 + mIoU(ms+flip): 45.01 + Config: configs/vit/vit_deit-s16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - DeiT-S + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 5.69 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_ln_mln_512x512_160k_ade20k/upernet_deit-s16_ln_mln_512x512_160k_ade20k_20210621_161021-c0cd652f.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-s16_ln_mln_512x512_160k_ade20k/20210621_161021.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_deit-b16_upernet_8xb2-80k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.24 + mIoU(ms+flip): 46.73 + Config: configs/vit/vit_deit-b16_upernet_8xb2-80k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - DeiT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 7.75 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_512x512_80k_ade20k/upernet_deit-b16_512x512_80k_ade20k_20210624_130529-1e090789.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_512x512_80k_ade20k/20210624_130529.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_deit-b16_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.36 + mIoU(ms+flip): 47.16 + Config: configs/vit/vit_deit-b16_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - DeiT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 7.75 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_512x512_160k_ade20k/upernet_deit-b16_512x512_160k_ade20k_20210621_180100-828705d7.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_512x512_160k_ade20k/20210621_180100.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_deit-b16_mln_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.46 + mIoU(ms+flip): 47.16 + Config: configs/vit/vit_deit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - DeiT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 9.21 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_mln_512x512_160k_ade20k/upernet_deit-b16_mln_512x512_160k_ade20k_20210621_191949-4e1450f3.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_mln_512x512_160k_ade20k/20210621_191949.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch +- Name: vit_deit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512 + In Collection: UPerNet + Results: + Task: Semantic Segmentation + Dataset: ADE20K + Metrics: + mIoU: 45.37 + mIoU(ms+flip): 47.23 + Config: configs/vit/vit_deit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py + Metadata: + Training Data: ADE20K + Batch Size: 16 + Architecture: + - DeiT-B + - UPerNet + Training Resources: 8x V100 GPUS + Memory (GB): 9.21 + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_ln_mln_512x512_160k_ade20k/upernet_deit-b16_ln_mln_512x512_160k_ade20k_20210623_153535-8a959c14.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vit/upernet_deit-b16_ln_mln_512x512_160k_ade20k/20210623_153535.log.json + Paper: + Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale' + URL: https://arxiv.org/pdf/2010.11929.pdf + Code: https://github.com/open-mmlab/mmsegmentation/blob/v0.17.0/mmseg/models/backbones/vit.py#L98 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..39d1c54 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,5 @@ +_base_ = './vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py' + +model = dict( + pretrained='pretrain/deit_base_patch16_224-b5f2ef4d.pth', + backbone=dict(drop_path_rate=0.1, final_norm=True)) diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..706673f --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,6 @@ +_base_ = './vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py' + +model = dict( + pretrained='pretrain/deit_base_patch16_224-b5f2ef4d.pth', + backbone=dict(drop_path_rate=0.1), +) diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..23a2358 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,6 @@ +_base_ = './vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py' + +model = dict( + pretrained='pretrain/deit_base_patch16_224-b5f2ef4d.pth', + backbone=dict(drop_path_rate=0.1), + neck=None) diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_upernet_8xb2-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_upernet_8xb2-80k_ade20k-512x512.py new file mode 100644 index 0000000..4c8bc93 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-b16_upernet_8xb2-80k_ade20k-512x512.py @@ -0,0 +1,6 @@ +_base_ = './vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py' + +model = dict( + pretrained='pretrain/deit_base_patch16_224-b5f2ef4d.pth', + backbone=dict(drop_path_rate=0.1), + neck=None) diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..8e626fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,9 @@ +_base_ = './vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py' + +model = dict( + pretrained='pretrain/deit_small_patch16_224-cd65a155.pth', + backbone=dict( + num_heads=6, embed_dims=384, drop_path_rate=0.1, final_norm=True), + decode_head=dict(num_classes=150, in_channels=[384, 384, 384, 384]), + neck=dict(in_channels=[384, 384, 384, 384], out_channels=384), + auxiliary_head=dict(num_classes=150, in_channels=384)) diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_mln_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_mln_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..9a69a89 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_mln_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = './vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py' + +model = dict( + pretrained='pretrain/deit_small_patch16_224-cd65a155.pth', + backbone=dict(num_heads=6, embed_dims=384, drop_path_rate=0.1), + decode_head=dict(num_classes=150, in_channels=[384, 384, 384, 384]), + neck=dict(in_channels=[384, 384, 384, 384], out_channels=384), + auxiliary_head=dict(num_classes=150, in_channels=384)) diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..9ef699d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = './vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py' + +model = dict( + pretrained='pretrain/deit_small_patch16_224-cd65a155.pth', + backbone=dict(num_heads=6, embed_dims=384, drop_path_rate=0.1), + decode_head=dict(num_classes=150, in_channels=[384, 384, 384, 384]), + neck=None, + auxiliary_head=dict(num_classes=150, in_channels=384)) diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_upernet_8xb2-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_upernet_8xb2-80k_ade20k-512x512.py new file mode 100644 index 0000000..9ef699d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_deit-s16_upernet_8xb2-80k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = './vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py' + +model = dict( + pretrained='pretrain/deit_small_patch16_224-cd65a155.pth', + backbone=dict(num_heads=6, embed_dims=384, drop_path_rate=0.1), + decode_head=dict(num_classes=150, in_channels=[384, 384, 384, 384]), + neck=None, + auxiliary_head=dict(num_classes=150, in_channels=384)) diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..2dd81b4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,45 @@ +_base_ = [ + '../_base_/models/upernet_vit-b16_ln_mln.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='pretrain/vit_base_patch16_224.pth', + backbone=dict(drop_path_rate=0.1, final_norm=True), + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) + +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'pos_embed': dict(decay_mult=0.), + 'cls_token': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..1a7ec16 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16_mln_upernet_8xb2-160k_ade20k-512x512.py @@ -0,0 +1,44 @@ +_base_ = [ + '../_base_/models/upernet_vit-b16_ln_mln.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_160k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='pretrain/vit_base_patch16_224.pth', + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) + +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'pos_embed': dict(decay_mult=0.), + 'cls_token': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=160000, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py new file mode 100644 index 0000000..ef73450 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vit/vit_vit-b16_mln_upernet_8xb2-80k_ade20k-512x512.py @@ -0,0 +1,44 @@ +_base_ = [ + '../_base_/models/upernet_vit-b16_ln_mln.py', + '../_base_/datasets/ade20k.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='pretrain/vit_base_patch16_224.pth', + decode_head=dict(num_classes=150), + auxiliary_head=dict(num_classes=150)) + +# AdamW optimizer, no weight decay for position embedding & layer norm +# in backbone +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + paramwise_cfg=dict( + custom_keys={ + 'pos_embed': dict(decay_mult=0.), + 'cls_token': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + eta_min=0.0, + power=1.0, + begin=1500, + end=80000, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) +val_dataloader = dict(batch_size=1) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/configs/vpd/README.md b/Seg_All_In_One_MMSeg/configs/vpd/README.md new file mode 100644 index 0000000..e90085b --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vpd/README.md @@ -0,0 +1,50 @@ +# VPD + +> [Unleashing Text-to-Image Diffusion Models for Visual Perception](https://arxiv.org/abs/2303.02153) + +## Introduction + + + +Official Repo + +## Abstract + + + +Diffusion models (DMs) have become the new trend of generative models and have demonstrated a powerful ability of conditional synthesis. Among those, text-to-image diffusion models pre-trained on large-scale image-text pairs are highly controllable by customizable prompts. Unlike the unconditional generative models that focus on low-level attributes and details, text-to-image diffusion models contain more high-level knowledge thanks to the vision-language pre-training. In this paper, we propose VPD (Visual Perception with a pre-trained Diffusion model), a new framework that exploits the semantic information of a pre-trained text-to-image diffusion model in visual perception tasks. Instead of using the pre-trained denoising autoencoder in a diffusion-based pipeline, we simply use it as a backbone and aim to study how to take full advantage of the learned knowledge. Specifically, we prompt the denoising decoder with proper textual inputs and refine the text features with an adapter, leading to a better alignment to the pre-trained stage and making the visual contents interact with the text prompts. We also propose to utilize the cross-attention maps between the visual features and the text features to provide explicit guidance. Compared with other pre-training methods, we show that vision-language pre-trained diffusion models can be faster adapted to downstream visual perception tasks using the proposed VPD. Extensive experiments on semantic segmentation, referring image segmentation and depth estimation demonstrates the effectiveness of our method. Notably, VPD attains 0.254 RMSE on NYUv2 depth estimation and 73.3% oIoU on RefCOCO-val referring image segmentation, establishing new records on these two benchmarks. + + + +
+ +
+ +## Usage + +To run training or inference with VPD model, please install the required packages via + +```sh +pip install -r requirements/albu.txt +pip install -r requirements/optional.txt +``` + +## Results and models + +### NYU + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | RMSE | d1 | d2 | d3 | REL | log_10 | config | download | +| ------ | --------------------- | --------- | ------- | -------- | -------------- | ------ | ----- | ----- | ----- | ----- | ----- | ------ | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| VPD | Stable-Diffusion-v1-5 | 480x480 | 25000 | - | - | A100 | 0.253 | 0.964 | 0.995 | 0.999 | 0.069 | 0.030 | [config](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/vpd/vpd_sd_4xb8-25k_nyu-480x480.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vpd/vpd_sd_4xb8-25k_nyu-480x480_20230908-66144bc4.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vpd/vpd_sd_4xb8-25k_nyu-480x480_20230908.json) | +| VPD | Stable-Diffusion-v1-5 | 512x512 | 25000 | - | - | A100 | 0.258 | 0.963 | 0.995 | 0.999 | 0.072 | 0.031 | [config](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/vpd/vpd_sd_4xb8-25k_nyu-512x512.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/vpd/vpd_sd_4xb8-25k_nyu-512x512_20230918-60cefcff.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/vpd/vpd_sd_4xb8-25k_nyu-512x512_20230918.json) | + +## Citation + +```bibtex +@article{zhao2023unleashing, + title={Unleashing Text-to-Image Diffusion Models for Visual Perception}, + author={Zhao, Wenliang and Rao, Yongming and Liu, Zuyan and Liu, Benlin and Zhou, Jie and Lu, Jiwen}, + journal={ICCV}, + year={2023} +} +``` diff --git a/Seg_All_In_One_MMSeg/configs/vpd/metafile.yaml b/Seg_All_In_One_MMSeg/configs/vpd/metafile.yaml new file mode 100644 index 0000000..ccdc0e8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vpd/metafile.yaml @@ -0,0 +1,56 @@ +Collections: +- Name: VPD + License: Apache License 2.0 + Metadata: + Training Data: + - NYU + Paper: + Title: Unleashing Text-to-Image Diffusion Models for Visual Perception + URL: https://arxiv.org/abs/2303.02153 + README: configs/vpd/README.md + Frameworks: + - PyTorch +Models: +- Name: vpd_sd_4xb8-25k_nyu-480x480 + In Collection: VPD + Results: + Task: Depth Estimation + Dataset: NYU + Metrics: + RMSE: 0.253 + Config: configs/vpd/vpd_sd_4xb8-25k_nyu-480x480.py + Metadata: + Training Data: NYU + Batch Size: 32 + Architecture: + - Stable-Diffusion + Training Resources: 8x A100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vpd/vpd_sd_4xb8-25k_nyu-480x480_20230908-66144bc4.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vpd/vpd_sd_4xb8-25k_nyu-480x480_20230908.json + Paper: + Title: 'High-Resolution Image Synthesis with Latent Diffusion Models' + URL: https://arxiv.org/abs/2112.10752 + Code: https://github.com/open-mmlab/mmsegmentation/tree/main/mmseg/models/backbones/vpd.py#L333 + Framework: PyTorch +- Name: vpd_sd_4xb8-25k_nyu-512x512 + In Collection: VPD + Alias: vpd_depth + Results: + Task: Depth Estimation + Dataset: NYU + Metrics: + RMSE: 0.258 + Config: configs/vpd/vpd_sd_4xb8-25k_nyu-512x512.py + Metadata: + Training Data: NYU + Batch Size: 32 + Architecture: + - Stable-Diffusion + Training Resources: 8x A100 GPUS + Weights: https://download.openmmlab.com/mmsegmentation/v0.5/vpd/vpd_sd_4xb8-25k_nyu-512x512_20230918-60cefcff.pth + Training log: https://download.openmmlab.com/mmsegmentation/v0.5/vpd/vpd_sd_4xb8-25k_nyu-512x512_20230918.json + Paper: + Title: 'High-Resolution Image Synthesis with Latent Diffusion Models' + URL: https://arxiv.org/abs/2112.10752 + Code: https://github.com/open-mmlab/mmsegmentation/tree/main/mmseg/models/backbones/vpd.py#L333 + Framework: PyTorch diff --git a/Seg_All_In_One_MMSeg/configs/vpd/vpd_sd_4xb8-25k_nyu-480x480.py b/Seg_All_In_One_MMSeg/configs/vpd/vpd_sd_4xb8-25k_nyu-480x480.py new file mode 100644 index 0000000..0d14d8d --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vpd/vpd_sd_4xb8-25k_nyu-480x480.py @@ -0,0 +1,38 @@ +_base_ = [ + '../_base_/models/vpd_sd.py', '../_base_/datasets/nyu.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_25k.py' +] + +crop_size = (480, 480) + +model = dict( + type='DepthEstimator', + data_preprocessor=dict(size=crop_size), + backbone=dict( + class_embed_path='https://download.openmmlab.com/mmsegmentation/' + 'v0.5/vpd/nyu_class_embeddings.pth', + class_embed_select=True, + pad_shape=512, + unet_cfg=dict(use_attn=False), + ), + decode_head=dict( + type='VPDDepthHead', + in_channels=[320, 640, 1280, 1280], + max_depth=10, + fmap_border=(1, 1), + ), + test_cfg=dict(mode='slide_flip', crop_size=crop_size, stride=(160, 160))) + +default_hooks = dict( + checkpoint=dict(save_best='rmse', rule='less', max_keep_ckpts=1)) + +# custom optimizer +optim_wrapper = dict( + constructor='ForceDefaultOptimWrapperConstructor', + paramwise_cfg=dict( + bias_decay_mult=0, + force_default_settings=True, + custom_keys={ + 'backbone.encoder_vq': dict(lr_mult=0), + 'backbone.unet': dict(lr_mult=0.01), + })) diff --git a/Seg_All_In_One_MMSeg/configs/vpd/vpd_sd_4xb8-25k_nyu-512x512.py b/Seg_All_In_One_MMSeg/configs/vpd/vpd_sd_4xb8-25k_nyu-512x512.py new file mode 100644 index 0000000..e89eb9c --- /dev/null +++ b/Seg_All_In_One_MMSeg/configs/vpd/vpd_sd_4xb8-25k_nyu-512x512.py @@ -0,0 +1,37 @@ +_base_ = [ + '../_base_/models/vpd_sd.py', '../_base_/datasets/nyu_512x512.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_25k.py' +] + +crop_size = (512, 512) + +model = dict( + type='DepthEstimator', + data_preprocessor=dict(size=crop_size), + backbone=dict( + class_embed_path='https://download.openmmlab.com/mmsegmentation/' + 'v0.5/vpd/nyu_class_embeddings.pth', + class_embed_select=True, + pad_shape=512, + unet_cfg=dict(use_attn=False), + ), + decode_head=dict( + type='VPDDepthHead', + in_channels=[320, 640, 1280, 1280], + max_depth=10, + ), + test_cfg=dict(mode='slide_flip', crop_size=crop_size, stride=(128, 128))) + +default_hooks = dict( + checkpoint=dict(save_best='rmse', rule='less', max_keep_ckpts=1)) + +# custom optimizer +optim_wrapper = dict( + constructor='ForceDefaultOptimWrapperConstructor', + paramwise_cfg=dict( + bias_decay_mult=0, + force_default_settings=True, + custom_keys={ + 'backbone.encoder_vq': dict(lr_mult=0), + 'backbone.unet': dict(lr_mult=0.01), + })) diff --git a/Seg_All_In_One_MMSeg/dataset-index.yml b/Seg_All_In_One_MMSeg/dataset-index.yml new file mode 100644 index 0000000..30ff0ee --- /dev/null +++ b/Seg_All_In_One_MMSeg/dataset-index.yml @@ -0,0 +1,80 @@ +openxlab: true +ade20k: + dataset: OpenDataLab/ADE20K_2016 + download_root: data + data_root: data/ade + +cityscapes: + dataset: OpenDataLab/CityScapes + download_root: data + data_root: data/cityscapes + +voc2012: + dataset: OpenDataLab/PASCAL_VOC2012 + download_root: data + data_root: data/VOCdevkit/VOC2012 + +cocostuff: + dataset: OpenDataLab/COCO-Stuff + download_root: data + data_root: data/coco_stuff164k + +mapillary: + dataset: OpenDataLab/Mapillary + download_root: data + data_root: data/mapillary + +pascal_context: + dataset: OpenDataLab/VOC2010 + download_root: data + data_root: data/VOCdevkit/VOC2010 + +isaid: + dataset: OpenDataLab/iSAID + download_root: data + data_root: data/iSAID + +isprs_potsdam: + dataset: OpenDataLab/ISPRS_Potsdam + download_root: data + data_root: data/potsdam + +loveda: + dataset: OpenDataLab/LoveDA + download_root: data + data_root: data/loveDA + +chase_db1: + dataset: OpenDataLab/CHASE_DB1 + download_root: data + data_root: data/CHASE_DB1 + +drive: + dataset: OpenDataLab/DRIVE + download_root: data + data_root: data/DRIVE + +hrf: + dataset: OpenDataLab/HRF + download_root: data + data_root: data/HRF + +stare: + dataset: OpenDataLab/STARE + download_root: data + data_root: data/STARE + +synapse: + dataset: OpenDataLab/SurgVisDom + download_root: data + data_root: data/synapse + +refuge: + dataset: OpenDataLab/REFUGE_Challenge + download_root: data + data_root: data/REFUGE + +lip: + dataset: OpenDataLab/LIP + download_root: data + data_root: data/LIP diff --git a/Seg_All_In_One_MMSeg/demo/MMSegmentation_Tutorial.ipynb b/Seg_All_In_One_MMSeg/demo/MMSegmentation_Tutorial.ipynb new file mode 100644 index 0000000..ac8601b --- /dev/null +++ b/Seg_All_In_One_MMSeg/demo/MMSegmentation_Tutorial.ipynb @@ -0,0 +1,555 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "view-in-github" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FVmnaxFJvsb8" + }, + "source": [ + "# MMSegmentation Tutorial\n", + "Welcome to MMSegmentation! \n", + "\n", + "In this tutorial, we demo\n", + "* How to do inference with MMSeg trained weight\n", + "* How to train on your own dataset and visualize the results. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QS8YHrEhbpas" + }, + "source": [ + "## Install MMSegmentation\n", + "This step may take several minutes. \n", + "\n", + "We use PyTorch 1.12 and CUDA 11.3 for this tutorial. You may install other versions by change the version number in pip install command. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "UWyLrLYaNEaL", + "outputId": "32a47fe3-f10d-47a1-f6b9-b7c235abdab1" + }, + "outputs": [], + "source": [ + "# Check nvcc version\n", + "!nvcc -V\n", + "# Check GCC version\n", + "!gcc --version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Ki3WUBjKbutg", + "outputId": "14bd14b0-4d8c-4fa9-e3f9-da35c0efc0d5" + }, + "outputs": [], + "source": [ + "# Install PyTorch\n", + "!conda install pytorch==1.12.0 torchvision==0.13.0 torchaudio==0.12.0 cudatoolkit=11.3 -c pytorch\n", + "# Install mim\n", + "!pip install -U openmim\n", + "# Install mmengine\n", + "!mim install mmengine\n", + "# Install MMCV\n", + "!mim install 'mmcv >= 2.0.0rc1'\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "nR-hHRvbNJJZ", + "outputId": "10c3b131-d4db-458c-fc10-b94b1c6ed546" + }, + "outputs": [], + "source": [ + "!rm -rf mmsegmentation\n", + "!git clone -b main https://github.com/open-mmlab/mmsegmentation.git \n", + "%cd mmsegmentation\n", + "!pip install -e ." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "mAE_h7XhPT7d", + "outputId": "83bf0f8e-fc69-40b1-f9fe-0025724a217c" + }, + "outputs": [], + "source": [ + "# Check Pytorch installation\n", + "import torch, torchvision\n", + "print(torch.__version__, torch.cuda.is_available())\n", + "\n", + "# Check MMSegmentation installation\n", + "import mmseg\n", + "print(mmseg.__version__)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ta51clKX4cwM" + }, + "source": [ + "## Finetune a semantic segmentation model on a new dataset\n", + "\n", + "To finetune on a customized dataset, the following steps are necessary. \n", + "1. Add a new dataset class. \n", + "2. Create a config file accordingly. \n", + "3. Perform training and evaluation. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AcZg6x_K5Zs3" + }, + "source": [ + "### Add a new dataset\n", + "\n", + "Datasets in MMSegmentation require image and semantic segmentation maps to be placed in folders with the same prefix. To support a new dataset, we may need to modify the original file structure. \n", + "\n", + "In this tutorial, we give an example of converting the dataset. You may refer to [docs](https://github.com/open-mmlab/mmsegmentation/blob/master/docs/en/tutorials/customize_datasets.md#customize-datasets-by-reorganizing-data) for details about dataset reorganization. \n", + "\n", + "We use [Stanford Background Dataset](http://dags.stanford.edu/projects/scenedataset.html) as an example. The dataset contains 715 images chosen from existing public datasets [LabelMe](http://labelme.csail.mit.edu), [MSRC](http://research.microsoft.com/en-us/projects/objectclassrecognition), [PASCAL VOC](http://pascallin.ecs.soton.ac.uk/challenges/VOC) and [Geometric Context](http://www.cs.illinois.edu/homes/dhoiem/). Images from these datasets are mainly outdoor scenes, each containing approximately 320-by-240 pixels. \n", + "In this tutorial, we use the region annotations as labels. There are 8 classes in total, i.e. sky, tree, road, grass, water, building, mountain, and foreground object. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TFIt7MHq5Wls", + "outputId": "74a126e4-c8a4-4d2f-a910-b58b71843a23" + }, + "outputs": [], + "source": [ + "# download and unzip\n", + "!wget http://dags.stanford.edu/data/iccv09Data.tar.gz -O stanford_background.tar.gz\n", + "!tar xf stanford_background.tar.gz" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 377 + }, + "id": "78LIci7F9WWI", + "outputId": "c432ddac-5a50-47b1-daac-5a26b07afea2" + }, + "outputs": [], + "source": [ + "# Let's take a look at the dataset\n", + "import mmcv\n", + "import mmengine\n", + "import matplotlib.pyplot as plt\n", + "\n", + "\n", + "img = mmcv.imread('iccv09Data/images/6000124.jpg')\n", + "plt.figure(figsize=(8, 6))\n", + "plt.imshow(mmcv.bgr2rgb(img))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "L5mNQuc2GsVE" + }, + "source": [ + "We need to convert the annotation into semantic map format as an image." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WnGZfribFHCx" + }, + "outputs": [], + "source": [ + "# define dataset root and directory for images and annotations\n", + "data_root = 'iccv09Data'\n", + "img_dir = 'images'\n", + "ann_dir = 'labels'\n", + "# define class and palette for better visualization\n", + "classes = ('sky', 'tree', 'road', 'grass', 'water', 'bldg', 'mntn', 'fg obj')\n", + "palette = [[128, 128, 128], [129, 127, 38], [120, 69, 125], [53, 125, 34], \n", + " [0, 11, 123], [118, 20, 12], [122, 81, 25], [241, 134, 51]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WnGZfribFHCx" + }, + "outputs": [], + "source": [ + "import os.path as osp\n", + "import numpy as np\n", + "from PIL import Image\n", + "\n", + "# convert dataset annotation to semantic segmentation map\n", + "for file in mmengine.scandir(osp.join(data_root, ann_dir), suffix='.regions.txt'):\n", + " seg_map = np.loadtxt(osp.join(data_root, ann_dir, file)).astype(np.uint8)\n", + " seg_img = Image.fromarray(seg_map).convert('P')\n", + " seg_img.putpalette(np.array(palette, dtype=np.uint8))\n", + " seg_img.save(osp.join(data_root, ann_dir, file.replace('.regions.txt', \n", + " '.png')))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 377 + }, + "id": "5MCSS9ABfSks", + "outputId": "92b9bafc-589e-48fc-c9e9-476f125d6522" + }, + "outputs": [], + "source": [ + "# Let's take a look at the segmentation map we got\n", + "import matplotlib.patches as mpatches\n", + "img = Image.open('iccv09Data/labels/6000124.png')\n", + "plt.figure(figsize=(8, 6))\n", + "im = plt.imshow(np.array(img.convert('RGB')))\n", + "\n", + "# create a patch (proxy artist) for every color \n", + "patches = [mpatches.Patch(color=np.array(palette[i])/255., \n", + " label=classes[i]) for i in range(8)]\n", + "# put those patched as legend-handles into the legend\n", + "plt.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0., \n", + " fontsize='large')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WbeLYCp2k5hl" + }, + "outputs": [], + "source": [ + "# split train/val set randomly\n", + "split_dir = 'splits'\n", + "mmengine.mkdir_or_exist(osp.join(data_root, split_dir))\n", + "filename_list = [osp.splitext(filename)[0] for filename in mmengine.scandir(\n", + " osp.join(data_root, ann_dir), suffix='.png')]\n", + "with open(osp.join(data_root, split_dir, 'train.txt'), 'w') as f:\n", + " # select first 4/5 as train set\n", + " train_length = int(len(filename_list)*4/5)\n", + " f.writelines(line + '\\n' for line in filename_list[:train_length])\n", + "with open(osp.join(data_root, split_dir, 'val.txt'), 'w') as f:\n", + " # select last 1/5 as train set\n", + " f.writelines(line + '\\n' for line in filename_list[train_length:])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HchvmGYB_rrO" + }, + "source": [ + "After downloading the data, we need to implement `load_annotations` function in the new dataset class `StanfordBackgroundDataset`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "LbsWOw62_o-X" + }, + "outputs": [], + "source": [ + "from mmseg.registry import DATASETS\n", + "from mmseg.datasets import BaseSegDataset\n", + "\n", + "\n", + "@DATASETS.register_module()\n", + "class StanfordBackgroundDataset(BaseSegDataset):\n", + " METAINFO = dict(classes = classes, palette = palette)\n", + " def __init__(self, **kwargs):\n", + " super().__init__(img_suffix='.jpg', seg_map_suffix='.png', **kwargs)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yUVtmn3Iq3WA" + }, + "source": [ + "### Create a config file\n", + "In the next step, we need to modify the config for the training. To accelerate the process, we finetune the model from trained weights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download config and checkpoint files\n", + "!mim download mmsegmentation --config pspnet_r50-d8_4xb2-40k_cityscapes-512x1024 --dest ." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Wwnj9tRzqX_A" + }, + "outputs": [], + "source": [ + "from mmengine import Config\n", + "cfg = Config.fromfile('configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py')\n", + "print(f'Config:\\n{cfg.pretty_text}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1y2oV5w97jQo" + }, + "source": [ + "Since the given config is used to train PSPNet on the cityscapes dataset, we need to modify it accordingly for our new dataset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "eyKnYC1Z7iCV", + "outputId": "6195217b-187f-4675-994b-ba90d8bb3078" + }, + "outputs": [], + "source": [ + "# Since we use only one GPU, BN is used instead of SyncBN\n", + "cfg.norm_cfg = dict(type='BN', requires_grad=True)\n", + "cfg.crop_size = (256, 256)\n", + "cfg.model.data_preprocessor.size = cfg.crop_size\n", + "cfg.model.backbone.norm_cfg = cfg.norm_cfg\n", + "cfg.model.decode_head.norm_cfg = cfg.norm_cfg\n", + "cfg.model.auxiliary_head.norm_cfg = cfg.norm_cfg\n", + "# modify num classes of the model in decode/auxiliary head\n", + "cfg.model.decode_head.num_classes = 8\n", + "cfg.model.auxiliary_head.num_classes = 8\n", + "\n", + "# Modify dataset type and path\n", + "cfg.dataset_type = 'StanfordBackgroundDataset'\n", + "cfg.data_root = data_root\n", + "\n", + "cfg.train_dataloader.batch_size = 8\n", + "\n", + "cfg.train_pipeline = [\n", + " dict(type='LoadImageFromFile'),\n", + " dict(type='LoadAnnotations'),\n", + " dict(type='RandomResize', scale=(320, 240), ratio_range=(0.5, 2.0), keep_ratio=True),\n", + " dict(type='RandomCrop', crop_size=cfg.crop_size, cat_max_ratio=0.75),\n", + " dict(type='RandomFlip', prob=0.5),\n", + " dict(type='PackSegInputs')\n", + "]\n", + "\n", + "cfg.test_pipeline = [\n", + " dict(type='LoadImageFromFile'),\n", + " dict(type='Resize', scale=(320, 240), keep_ratio=True),\n", + " # add loading annotation after ``Resize`` because ground truth\n", + " # does not need to do resize data transform\n", + " dict(type='LoadAnnotations'),\n", + " dict(type='PackSegInputs')\n", + "]\n", + "\n", + "\n", + "cfg.train_dataloader.dataset.type = cfg.dataset_type\n", + "cfg.train_dataloader.dataset.data_root = cfg.data_root\n", + "cfg.train_dataloader.dataset.data_prefix = dict(img_path=img_dir, seg_map_path=ann_dir)\n", + "cfg.train_dataloader.dataset.pipeline = cfg.train_pipeline\n", + "cfg.train_dataloader.dataset.ann_file = 'splits/train.txt'\n", + "\n", + "cfg.val_dataloader.dataset.type = cfg.dataset_type\n", + "cfg.val_dataloader.dataset.data_root = cfg.data_root\n", + "cfg.val_dataloader.dataset.data_prefix = dict(img_path=img_dir, seg_map_path=ann_dir)\n", + "cfg.val_dataloader.dataset.pipeline = cfg.test_pipeline\n", + "cfg.val_dataloader.dataset.ann_file = 'splits/val.txt'\n", + "\n", + "cfg.test_dataloader = cfg.val_dataloader\n", + "\n", + "\n", + "# Load the pretrained weights\n", + "cfg.load_from = 'pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth'\n", + "\n", + "# Set up working dir to save files and logs.\n", + "cfg.work_dir = './work_dirs/tutorial'\n", + "\n", + "cfg.train_cfg.max_iters = 200\n", + "cfg.train_cfg.val_interval = 200\n", + "cfg.default_hooks.logger.interval = 10\n", + "cfg.default_hooks.checkpoint.interval = 200\n", + "\n", + "# Set seed to facilitate reproducing the result\n", + "cfg['randomness'] = dict(seed=0)\n", + "\n", + "# Let's have a look at the final config used for training\n", + "print(f'Config:\\n{cfg.pretty_text}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QWuH14LYF2gQ" + }, + "source": [ + "### Train and Evaluation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "jYKoSfdMF12B", + "outputId": "422219ca-d7a5-4890-f09f-88c959942e64" + }, + "outputs": [], + "source": [ + "from mmengine.runner import Runner\n", + "\n", + "runner = Runner.from_cfg(cfg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# start training\n", + "runner.train()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DEkWOP-NMbc_" + }, + "source": [ + "Inference with trained model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 645 + }, + "id": "ekG__UfaH_OU", + "outputId": "1437419c-869a-4902-df86-d4f6f8b2597a" + }, + "outputs": [], + "source": [ + "from mmseg.apis import init_model, inference_model, show_result_pyplot\n", + "\n", + "# Init the model from the config and the checkpoint\n", + "checkpoint_path = './work_dirs/tutorial/iter_200.pth'\n", + "model = init_model(cfg, checkpoint_path, 'cuda:0')\n", + "\n", + "img = mmcv.imread('iccv09Data/images/6000124.jpg')\n", + "result = inference_model(model, img)\n", + "plt.figure(figsize=(8, 6))\n", + "vis_result = show_result_pyplot(model, img, result)\n", + "plt.imshow(mmcv.bgr2rgb(vis_result))\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [], + "include_colab_link": true, + "name": "MMSegmentation Tutorial.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3.10.6 ('pt1.12')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + }, + "pycharm": { + "stem_cell": { + "cell_type": "raw", + "metadata": { + "collapsed": false + }, + "source": [] + } + }, + "vscode": { + "interpreter": { + "hash": "0442e67aee3d9cbb788fa6e86d60c4ffa94ad7f1943c65abfecb99a6f4696c58" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Seg_All_In_One_MMSeg/demo/classroom__rgb_00283.jpg b/Seg_All_In_One_MMSeg/demo/classroom__rgb_00283.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1df37e9248390cadd0b94d180ac3d4527b1b69c7 GIT binary patch literal 50269 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

CeI$Uf8Z)!yVnLuX0)p^){@cOks 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%0DAaIhctdwc#J^2Kd$x9(%c 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 0 else input_fps + output_height = args.output_height if args.output_height > 0 else int( + input_height) + output_width = args.output_width if args.output_width > 0 else int( + input_width) + writer = cv2.VideoWriter(args.output_file, fourcc, output_fps, + (output_width, output_height), True) + + # start looping + try: + while True: + flag, frame = cap.read() + if not flag: + break + + # test a single image + result = inference_model(model, frame) + + # blend raw image and prediction + draw_img = show_result_pyplot(model, frame, result) + + if args.show: + cv2.imshow('video_demo', draw_img) + cv2.waitKey(args.show_wait_time) + if writer: + if draw_img.shape[0] != output_height or draw_img.shape[ + 1] != output_width: + draw_img = cv2.resize(draw_img, + (output_width, output_height)) + writer.write(draw_img) + finally: + if writer: + writer.release() + cap.release() + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/docker/Dockerfile b/Seg_All_In_One_MMSeg/docker/Dockerfile new file mode 100644 index 0000000..26420dd --- /dev/null +++ b/Seg_All_In_One_MMSeg/docker/Dockerfile @@ -0,0 +1,35 @@ +ARG PYTORCH="1.11.0" +ARG CUDA="11.3" +ARG CUDNN="8" +ARG MMCV="2.0.1" + +FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel + +ENV TORCH_CUDA_ARCH_LIST="6.0 6.1 7.0+PTX" +ENV TORCH_NVCC_FLAGS="-Xfatbin -compress-all" +ENV CMAKE_PREFIX_PATH="$(dirname $(which conda))/../" + +# To fix GPG key error when running apt-get update +RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/3bf863cc.pub +RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/7fa2af80.pub + +RUN apt-get update && apt-get install -y git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 libgl1-mesa-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN conda clean --all + +# Install MMCV +ARG PYTORCH +ARG CUDA +ARG MMCV +RUN ["/bin/bash", "-c", "pip install openmim"] +RUN ["/bin/bash", "-c", "mim install mmengine"] +RUN ["/bin/bash", "-c", "mim install mmcv==${MMCV}"] + +# Install MMSegmentation +RUN git clone -b main https://github.com/open-mmlab/mmsegmentation.git /mmsegmentation +WORKDIR /mmsegmentation +ENV FORCE_CUDA="1" +RUN pip install -r requirements.txt +RUN pip install --no-cache-dir -e . diff --git a/Seg_All_In_One_MMSeg/docker/serve/Dockerfile b/Seg_All_In_One_MMSeg/docker/serve/Dockerfile new file mode 100644 index 0000000..38f91ba --- /dev/null +++ b/Seg_All_In_One_MMSeg/docker/serve/Dockerfile @@ -0,0 +1,51 @@ +ARG PYTORCH="1.11.0" +ARG CUDA="11.3" +ARG CUDNN="8" +FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel + +ARG MMCV="2.0.1" +ARG MMSEG="1.2.2" + +ENV PYTHONUNBUFFERED TRUE + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + ca-certificates \ + g++ \ + openjdk-11-jre-headless \ + # MMDet Requirements + ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \ + && rm -rf /var/lib/apt/lists/* + +ENV PATH="/opt/conda/bin:$PATH" +RUN export FORCE_CUDA=1 + +# TORCHSEVER +RUN pip install torchserve torch-model-archiver + +# MMLAB +ARG PYTORCH +ARG CUDA +RUN ["/bin/bash", "-c", "pip install openmim"] +RUN ["/bin/bash", "-c", "mim install mmengine"] +RUN ["/bin/bash", "-c", "mim install mmcv==${MMCV}"] +RUN pip install mmsegmentation==${MMSEG} + +RUN useradd -m model-server \ + && mkdir -p /home/model-server/tmp + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh + +RUN chmod +x /usr/local/bin/entrypoint.sh \ + && chown -R model-server /home/model-server + +COPY config.properties /home/model-server/config.properties +RUN mkdir /home/model-server/model-store && chown -R model-server /home/model-server/model-store + +EXPOSE 8080 8081 8082 + +USER model-server +WORKDIR /home/model-server +ENV TEMP=/home/model-server/tmp +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +CMD ["serve"] diff --git a/Seg_All_In_One_MMSeg/docker/serve/config.properties b/Seg_All_In_One_MMSeg/docker/serve/config.properties new file mode 100644 index 0000000..efb9c47 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docker/serve/config.properties @@ -0,0 +1,5 @@ +inference_address=http://0.0.0.0:8080 +management_address=http://0.0.0.0:8081 +metrics_address=http://0.0.0.0:8082 +model_store=/home/model-server/model-store +load_models=all diff --git a/Seg_All_In_One_MMSeg/docker/serve/entrypoint.sh b/Seg_All_In_One_MMSeg/docker/serve/entrypoint.sh new file mode 100644 index 0000000..41ba00b --- /dev/null +++ b/Seg_All_In_One_MMSeg/docker/serve/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +if [[ "$1" = "serve" ]]; then + shift 1 + torchserve --start --ts-config /home/model-server/config.properties +else + eval "$@" +fi + +# prevent docker exit +tail -f /dev/null diff --git a/Seg_All_In_One_MMSeg/docs/en/.readthedocs.yaml b/Seg_All_In_One_MMSeg/docs/en/.readthedocs.yaml new file mode 100644 index 0000000..9df9fdb --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +formats: + - epub + +sphinx: + configuration: docs/en/conf.py + +python: + install: + - requirements: requirements/docs.txt + - requirements: requirements/readthedocs.txt diff --git a/Seg_All_In_One_MMSeg/docs/en/Makefile b/Seg_All_In_One_MMSeg/docs/en/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/Seg_All_In_One_MMSeg/docs/en/_static/css/readthedocs.css b/Seg_All_In_One_MMSeg/docs/en/_static/css/readthedocs.css new file mode 100644 index 0000000..2e38d08 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/_static/css/readthedocs.css @@ -0,0 +1,6 @@ +.header-logo { + background-image: url("../images/mmsegmentation.png"); + background-size: 201px 40px; + height: 40px; + width: 201px; +} diff --git a/Seg_All_In_One_MMSeg/docs/en/_static/images/mmsegmentation.png b/Seg_All_In_One_MMSeg/docs/en/_static/images/mmsegmentation.png new file mode 100644 index 0000000000000000000000000000000000000000..009083a9e80599a1893591ff362da377c94a0975 GIT binary patch literal 44728 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 literal 0 HcmV?d00001 diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_datasets.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_datasets.md new file mode 100644 index 0000000..a8e3503 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_datasets.md @@ -0,0 +1,199 @@ +# Add New Datasets + +## Add new custom dataset + +Here we show how to develop a new custom dataset. + +1. Create a new file `mmseg/datasets/example.py` + + ```python + from mmseg.registry import DATASETS + from .basesegdataset import BaseSegDataset + + + @DATASETS.register_module() + class ExampleDataset(BaseSegDataset): + + METAINFO = dict( + classes=('xxx', 'xxx', ...), + palette=[[x, x, x], [x, x, x], ...]) + + def __init__(self, arg1, arg2): + pass + ``` + +2. Import the module in `mmseg/datasets/__init__.py` + + ```python + from .example import ExampleDataset + ``` + +3. Use it by creating a new new dataset config file `configs/_base_/datasets/example_dataset.py` + + ```python + dataset_type = 'ExampleDataset' + data_root = 'data/example/' + ... + ``` + +4. Add dataset meta information in `mmseg/utils/class_names.py` + + ```python + def example_classes(): + return [ + 'xxx', 'xxx', + ... + ] + + def example_palette(): + return [ + [x, x, x], [x, x, x], + ... + ] + dataset_aliases ={ + 'example': ['example', ...], + ... + } + ``` + +**Note:** If the new dataset does not satisfy the mmseg requirements, a data preprocessing script needs to be prepared in `tools/dataset_converters/` + +## Customize datasets by reorganizing data + +The simplest way is to convert your dataset to organize your data into folders. + +An example of file structure is as followed. + +```none +├── data +│ ├── my_dataset +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ │ ├── xxx{img_suffix} +│ │ │ │ ├── yyy{img_suffix} +│ │ │ │ ├── zzz{img_suffix} +│ │ │ ├── val +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ │ ├── xxx{seg_map_suffix} +│ │ │ │ ├── yyy{seg_map_suffix} +│ │ │ │ ├── zzz{seg_map_suffix} +│ │ │ ├── val + +``` + +A training pair will consist of the files with same suffix in img_dir/ann_dir. + +Some datasets don't release the test set or don't release the ground truth of the test set, and we cannot evaluate models locally without the ground truth of the test set, so we set the validation set as the default test set in config files. + +About how to build your own datasets or implement a new dataset class please refer to the [datasets guide](./datasets.md) for more detailed information. + +**Note:** The annotations are images of shape (H, W), the value pixel should fall in range `[0, num_classes - 1]`. +You may use `'P'` mode of [pillow](https://pillow.readthedocs.io/en/stable/handbook/concepts.html#palette) to create your annotation image with color. + +## Customize datasets by mixing dataset + +MMSegmentation also supports to mix dataset for training. +Currently it supports to concat, repeat and multi-image mix datasets. + +### Repeat dataset + +We use `RepeatDataset` as wrapper to repeat the dataset. +For example, suppose the original dataset is `Dataset_A`, to repeat it, the config looks like the following + +```python +dataset_A_train = dict( + type='RepeatDataset', + times=N, + dataset=dict( # This is the original config of Dataset_A + type='Dataset_A', + ... + pipeline=train_pipeline + ) +) +``` + +### Concatenate dataset + +In case the dataset you want to concatenate is different, you can concatenate the dataset configs like the following. + +```python +dataset_A_train = dict() +dataset_B_train = dict() +concatenate_dataset = dict( + type='ConcatDataset', + datasets=[dataset_A_train, dataset_B_train]) +``` + +A more complex example that repeats `Dataset_A` and `Dataset_B` by N and M times, respectively, and then concatenates the repeated datasets is as the following. + +```python +dataset_A_train = dict( + type='RepeatDataset', + times=N, + dataset=dict( + type='Dataset_A', + ... + pipeline=train_pipeline + ) +) +dataset_A_val = dict( + ... + pipeline=test_pipeline +) +dataset_A_test = dict( + ... + pipeline=test_pipeline +) +dataset_B_train = dict( + type='RepeatDataset', + times=M, + dataset=dict( + type='Dataset_B', + ... + pipeline=train_pipeline + ) +) +train_dataloader = dict( + dataset=dict( + type='ConcatDataset', + datasets=[dataset_A_train, dataset_B_train])) + +val_dataloader = dict(dataset=dataset_A_val) +test_dataloader = dict(dataset=dataset_A_test) + +``` + +You can refer base dataset [tutorial](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/basedataset.html) from mmengine for more details + +### Multi-image Mix Dataset + +We use `MultiImageMixDataset` as a wrapper to mix images from multiple datasets. +`MultiImageMixDataset` can be used by multiple images mixed data augmentation like mosaic and mixup. + +An example of using `MultiImageMixDataset` with `Mosaic` data augmentation: + +```python +train_pipeline = [ + dict(type='RandomMosaic', prob=1), + dict(type='Resize', img_scale=(1024, 512), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackSegInputs') +] + +train_dataset = dict( + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + reduce_zero_label=False, + img_dir=data_root + "images/train", + ann_dir=data_root + "annotations/train", + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + ] + ), + pipeline=train_pipeline +) + +``` diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_metrics.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_metrics.md new file mode 100644 index 0000000..0298826 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_metrics.md @@ -0,0 +1,81 @@ +# Add New Metrics + +## Develop with the source code of MMSegmentation + +Here we show how to develop a new metric with an example of `CustomMetric` as the following. + +1. Create a new file `mmseg/evaluation/metrics/custom_metric.py`. + + ```python + from typing import List, Sequence + + from mmengine.evaluator import BaseMetric + + from mmseg.registry import METRICS + + + @METRICS.register_module() + class CustomMetric(BaseMetric): + + def __init__(self, arg1, arg2): + """ + The metric first processes each batch of data_samples and predictions, + and appends the processed results to the results list. Then it + collects all results together from all ranks if distributed training + is used. Finally, it computes the metrics of the entire dataset. + """ + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + pass + + def compute_metrics(self, results: list) -> dict: + pass + + def evaluate(self, size: int) -> dict: + pass + ``` + + In the above example, `CustomMetric` is a subclass of `BaseMetric`. It has three methods: `process`, `compute_metrics` and `evaluate`. + + - `process()` process one batch of data samples and predictions. The processed results are stored in `self.results` which will be used to compute the metrics after all the data samples are processed. Please refer to [MMEngine documentation](https://github.com/open-mmlab/mmengine/blob/main/docs/en/design/evaluation.md) for more details. + + - `compute_metrics()` is used to compute the metrics from the processed results. + + - `evaluate()` is an interface to compute the metrics and return the results. It will be called by `ValLoop` or `TestLoop` in the `Runner`. In most cases, you don't need to override this method, but you can override it if you want to do some extra work. + + **Note:** You might find the details of calling `evaluate()` method in the `Runner` [here](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L366). The `Runner` is the executor of the training and testing process, you can find more details about it at the [engine document](./engine.md). + +2. Import the new metric in `mmseg/evaluation/metrics/__init__.py`. + + ```python + from .custom_metric import CustomMetric + __all__ = ['CustomMetric', ...] + ``` + +3. Add the new metric to the config file. + + ```python + val_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx) + test_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx) + ``` + +## Develop with the released version of MMSegmentation + +The above example shows how to develop a new metric with the source code of MMSegmentation. If you want to develop a new metric with the released version of MMSegmentation, you can follow the following steps. + +1. Create a new file `/Path/to/metrics/custom_metric.py`, implement the `process`, `compute_metrics` and `evaluate` methods, `evaluate` method is optional. + +2. Import the new metric in your code or config file. + + ```python + from path.to.metrics import CustomMetric + ``` + + or + + ```python + custom_imports = dict(imports=['/Path/to/metrics'], allow_failed_imports=False) + + val_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx) + test_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx) + ``` diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_models.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_models.md new file mode 100644 index 0000000..ed5c9ce --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_models.md @@ -0,0 +1,260 @@ +# Add New Modules + +## Develop new components + +We can customize all the components introduced at [the model documentation](./models.md), such as **backbone**, **head**, **loss function** and **data preprocessor**. + +### Add new backbones + +Here we show how to develop a new backbone with an example of MobileNet. + +1. Create a new file `mmseg/models/backbones/mobilenet.py`. + + ```python + import torch.nn as nn + + from mmseg.registry import MODELS + + + @MODELS.register_module() + class MobileNet(nn.Module): + + def __init__(self, arg1, arg2): + pass + + def forward(self, x): # should return a tuple + pass + + def init_weights(self, pretrained=None): + pass + ``` + +2. Import the module in `mmseg/models/backbones/__init__.py`. + + ```python + from .mobilenet import MobileNet + ``` + +3. Use it in your config file. + + ```python + model = dict( + ... + backbone=dict( + type='MobileNet', + arg1=xxx, + arg2=xxx), + ... + ``` + +### Add new heads + +In MMSegmentation, we provide a [BaseDecodeHead](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/decode_heads/decode_head.py#L17) for developing all segmentation heads. +All newly implemented decode heads should be derived from it. +Here we show how to develop a new head with the example of [PSPNet](https://arxiv.org/abs/1612.01105) as the following. + +First, add a new decode head in `mmseg/models/decode_heads/psp_head.py`. +PSPNet implements a decode head for segmentation decode. +To implement a decode head, we need to implement three functions of the new module as the following. + +```python +from mmseg.registry import MODELS + +@MODELS.register_module() +class PSPHead(BaseDecodeHead): + + def __init__(self, pool_scales=(1, 2, 3, 6), **kwargs): + super(PSPHead, self).__init__(**kwargs) + + def init_weights(self): + pass + + def forward(self, inputs): + pass +``` + +Next, the users need to add the module in the `mmseg/models/decode_heads/__init__.py`, thus the corresponding registry could find and load them. + +To config file of PSPNet is as the following + +```python +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + pretrained='pretrain_model/resnet50_v1c_trick-2cccc1ad.pth', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=3, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))) + +``` + +### Add new loss + +Assume you want to add a new loss as `MyLoss` for segmentation decode. +To add a new loss function, the users need to implement it in `mmseg/models/losses/my_loss.py`. +The decorator `weighted_loss` enables the loss to be weighted for each element. + +```python +import torch +import torch.nn as nn + +from mmseg.registry import MODELS +from .utils import weighted_loss + +@weighted_loss +def my_loss(pred, target): + assert pred.size() == target.size() and target.numel() > 0 + loss = torch.abs(pred - target) + return loss + +@MODELS.register_module() +class MyLoss(nn.Module): + + def __init__(self, reduction='mean', loss_weight=1.0): + super(MyLoss, self).__init__() + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None): + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss = self.loss_weight * my_loss( + pred, target, weight, reduction=reduction, avg_factor=avg_factor) + return loss +``` + +Then the users need to add it in the `mmseg/models/losses/__init__.py`. + +```python +from .my_loss import MyLoss, my_loss + +``` + +To use it, modify the `loss_xxx` field. +Then you need to modify the `loss_decode` field in the head. +`loss_weight` could be used to balance multiple losses. + +```python +loss_decode=dict(type='MyLoss', loss_weight=1.0)) +``` + +### Add new data preprocessor + +In MMSegmentation 1.x versions, we use [SegDataPreProcessor](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/data_preprocessor.py#L13) to copy data to the target device and preprocess the data into the model input format as default. Here we show how to develop a new data preprocessor. + +1. Create a new file `mmseg/models/my_datapreprocessor.py`. + + ```python + from mmengine.model import BaseDataPreprocessor + + from mmseg.registry import MODELS + + @MODELS.register_module() + class MyDataPreProcessor(BaseDataPreprocessor): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def forward(self, data: dict, training: bool=False) -> Dict[str, Any]: + # TODO Define the logic for data pre-processing in the forward method + pass + ``` + +2. Import your data preprocessor in `mmseg/models/__init__.py` + + ```python + from .my_datapreprocessor import MyDataPreProcessor + ``` + +3. Use it in your config file. + + ```python + model = dict( + data_preprocessor=dict(type='MyDataPreProcessor) + ... + ) + ``` + +## Develop new segmentors + +The segmentor is an algorithmic architecture in which users can customize their algorithms by adding customized components and defining the logic of algorithm execution. Please refer to [the model document](./models.md) for more details. + +Since the [BaseSegmentor](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/segmentors/base.py#L15) in MMSegmentation unifies three modes for a forward process, to develop a new segmentor, users need to overwrite `loss`, `predict` and `_forward` methods corresponding to the `loss`, `predict` and `tensor` modes. + +Here we show how to develop a new segmentor. + +1. Create a new file `mmseg/models/segmentors/my_segmentor.py`. + + ```python + from typing import Dict, Optional, Union + + import torch + + from mmseg.registry import MODELS + from mmseg.models import BaseSegmentor + + @MODELS.register_module() + class MySegmentor(BaseSegmentor): + def __init__(self, **kwargs): + super().__init__(**kwargs) + # TODO users should build components of the network here + + def loss(self, inputs: Tensor, data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples.""" + pass + + def predict(self, inputs: Tensor, data_samples: OptSampleList=None) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing.""" + pass + + def _forward(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> Tuple[List[Tensor]]: + """Network forward process. + + Usually includes backbone, neck and head forward without any post- + processing. + """ + pass + ``` + +2. Import your segmentor in `mmseg/models/segmentors/__init__.py`. + + ```python + from .my_segmentor import MySegmentor + ``` + +3. Use it in your config file. + + ```python + model = dict( + type='MySegmentor' + ... + ) + ``` diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_transforms.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_transforms.md new file mode 100644 index 0000000..ca336ce --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/add_transforms.md @@ -0,0 +1,52 @@ +# Adding New Data Transforms + +## Customization data transformation + +The customized data transformation must inherited from `BaseTransform` and implement `transform` function. +Here we use a simple flipping transformation as example: + +```python +import random +import mmcv +from mmcv.transforms import BaseTransform, TRANSFORMS + +@TRANSFORMS.register_module() +class MyFlip(BaseTransform): + def __init__(self, direction: str): + super().__init__() + self.direction = direction + + def transform(self, results: dict) -> dict: + img = results['img'] + results['img'] = mmcv.imflip(img, direction=self.direction) + return results +``` + +Moreover, import the new class. + +```python +from .my_pipeline import MyFlip +``` + +Thus, we can instantiate a `MyFlip` object and use it to process the data dict. + +```python +import numpy as np + +transform = MyFlip(direction='horizontal') +data_dict = {'img': np.random.rand(224, 224, 3)} +data_dict = transform(data_dict) +processed_img = data_dict['img'] +``` + +Or, we can use `MyFlip` transformation in data pipeline in our config file. + +```python +pipeline = [ + ... + dict(type='MyFlip', direction='horizontal'), + ... +] +``` + +Note that if you want to use `MyFlip` in config, you must ensure the file containing `MyFlip` is imported during runtime. diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/customize_runtime.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/customize_runtime.md new file mode 100644 index 0000000..33281bf --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/customize_runtime.md @@ -0,0 +1,168 @@ +# Customize Runtime Settings + +## Customize hooks + +### Step 1: Implement a new hook + +MMEngine has implemented commonly used [hooks](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/hook.md) for training and test, +When users have requirements for customization, they can follow examples below. +For example, if some hyper-parameter of the model needs to be changed when model training, we can implement a new hook for it: + +```python +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence + +from mmengine.hooks import Hook +from mmengine.model import is_model_wrapper + +from mmseg.registry import HOOKS + + +@HOOKS.register_module() +class NewHook(Hook): + """Docstring for NewHook. + """ + + def __init__(self, a: int, b: int) -> None: + self.a = a + self.b = b + + def before_train_iter(self, + runner, + batch_idx: int, + data_batch: Optional[Sequence[dict]] = None) -> None: + cur_iter = runner.iter + # acquire this model when it is in a wrapper + if is_model_wrapper(runner.model): + model = runner.model.module + model.hyper_parameter = self.a * cur_iter + self.b +``` + +### Step 2: Import a new hook + +The module which is defined above needs to be imported into main namespace first to ensure being registered. +We assume `NewHook` is implemented in `mmseg/engine/hooks/new_hook.py`, there are two ways to import it: + +- Import it by modifying `mmseg/engine/hooks/__init__.py`. + Modules should be imported in `mmseg/engine/hooks/__init__.py` thus these new modules can be found and added by registry. + +```python +from .new_hook import NewHook + +__all__ = [..., NewHook] +``` + +- Import it manually by `custom_imports` in config file. + +```python +custom_imports = dict(imports=['mmseg.engine.hooks.new_hook'], allow_failed_imports=False) +``` + +### Step 3: Modify config file + +Users can set and use customized hooks in training and test followed methods below. +The execution priority of hooks at the same place of `Runner` can be referred [here](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/hook.md#built-in-hooks), +Default priority of customized hook is `NORMAL`. + +```python +custom_hooks = [ + dict(type='NewHook', a=a_value, b=b_value, priority='ABOVE_NORMAL') +] +``` + +## Customize optimizer + +### Step 1: Implement a new optimizer + +We recommend the customized optimizer implemented in `mmseg/engine/optimizers/my_optimizer.py`. Here is an example of a new optimizer `MyOptimizer` which has parameters `a`, `b` and `c`: + +```python +from mmseg.registry import OPTIMIZERS +from torch.optim import Optimizer + + +@OPTIMIZERS.register_module() +class MyOptimizer(Optimizer): + + def __init__(self, a, b, c) +``` + +### Step 2: Import a new optimizer + +The module which is defined above needs to be imported into main namespace first to ensure being registered. +We assume `MyOptimizer` is implemented in `mmseg/engine/optimizers/my_optimizer.py`, there are two ways to import it: + +- Import it by modifying `mmseg/engine/optimizers/__init__.py`. + Modules should be imported in `mmseg/engine/optimizers/__init__.py` thus these new modules can be found and added by registry. + +```python +from .my_optimizer import MyOptimizer +``` + +- Import it manually by `custom_imports` in config file. + +```python +custom_imports = dict(imports=['mmseg.engine.optimizers.my_optimizer'], allow_failed_imports=False) +``` + +### Step 3: Modify config file + +Then it needs to modify `optimizer` in `optim_wrapper` of config file, if users want to use customized `MyOptimizer`, it can be modified as: + +```python +optim_wrapper = dict(type='OptimWrapper', + optimizer=dict(type='MyOptimizer', + a=a_value, b=b_value, c=c_value), + clip_grad=None) +``` + +## Customize optimizer constructor + +### Step 1: Implement a new optimizer constructor + +Optimizer constructor is used to create optimizer and optimizer wrapper for model training, which has powerful functions like specifying learning rate and weight decay for different model layers. +Here is an example for a customized optimizer constructor. + +```python +from mmengine.optim import DefaultOptimWrapperConstructor +from mmseg.registry import OPTIM_WRAPPER_CONSTRUCTORS + +@OPTIM_WRAPPER_CONSTRUCTORS.register_module() +class LearningRateDecayOptimizerConstructor(DefaultOptimWrapperConstructor): + def __init__(self, optim_wrapper_cfg, paramwise_cfg=None): + + def __call__(self, model): + + return my_optimizer +``` + +Default optimizer constructor is implemented [here](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L19). +It can also be used as base class of new optimizer constructor. + +### Step 2: Import a new optimizer constructor + +The module which is defined above needs to be imported into main namespace first to ensure being registered. +We assume `MyOptimizerConstructor` is implemented in `mmseg/engine/optimizers/my_optimizer_constructor.py`, there are two ways to import it: + +- Import it by modifying `mmseg/engine/optimizers/__init__.py`. + Modules should be imported in `mmseg/engine/optimizers/__init__.py` thus these new modules can be found and added by registry. + +```python +from .my_optimizer_constructor import MyOptimizerConstructor +``` + +- Import it manually by `custom_imports` in config file. + +```python +custom_imports = dict(imports=['mmseg.engine.optimizers.my_optimizer_constructor'], allow_failed_imports=False) +``` + +### Step 3: Modify config file + +Then it needs to modify `constructor` in `optim_wrapper` of config file, if users want to use customized `MyOptimizerConstructor`, it can be modified as: + +```python +optim_wrapper = dict(type='OptimWrapper', + constructor='MyOptimizerConstructor', + clip_grad=None) +``` diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/data_flow.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/data_flow.md new file mode 100644 index 0000000..404035a --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/data_flow.md @@ -0,0 +1,87 @@ +# Dataflow + +In this chapter, we will introduce the dataflow and data format convention between the internal modules managed by the [Runner](https://mmengine.readthedocs.io/en/latest/tutorials/runner.html). + +## Overview of dataflow + +The [Runner](https://github.com/open-mmlab/mmengine/blob/main/docs/en/design/runner.md) is an "integrator" in MMEngine. It covers all aspects of the framework and shoulders the responsibility of organizing and scheduling nearly all modules, that means the dataflow between all modules also controlled by the `Runner`. As illustrated in the [Runner document of MMEngine](https://mmengine.readthedocs.io/en/latest/tutorials/runner.html), the following diagram shows the basic dataflow. + +![Basic dataflow](https://user-images.githubusercontent.com/112053249/199228350-5f80699e-7fd2-4b4c-ac32-0b16b1922c2e.png) + +The dashed border, gray filled shapes represent different data formats, while solid boxes represent modules/methods. Due to the great flexibility and extensibility of MMEngine, some critical base classes can be inherited and their methods can be overridden. The diagram above only holds when users are not customizing `TrainLoop`, `ValLoop`, and `TestLoop` in `Runner`, and are not overriding `train_step`, `val_step` and `test_step` method in their custom model. The default setting of loops in MMSegmentation is as follows, it uses `IterBasedTrainLoop` to train models with 20000 iterations in total and do evaluation each 2000 iterations. + +```python +train_cfg = dict(type='IterBasedTrainLoop', max_iters=20000, val_interval=2000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +``` + +In the above diagram, the red line indicates the [train_step](./models.md#train_step). At each training iteration, dataloader loads images from storage and transfer to data preprocessor, data preprocessor would put images to the specific device and stack data to batch, then model accepts the batch data as inputs, finally the outputs of the model would be sent to optimizer. The blue line indicates [val_step](./models.md#val_step) and [test_step](./models.md#test_step). The dataflow of these two process is similar to the `train_step` except the outputs of model, since model parameters are freezed when doing evaluation, the model output would be transferred to [Evaluator](./evaluation.md#ioumetric) to compute metrics. + +## Dataflow convention in MMSegmentation + +From the diagram above, we could see the basic dataflow. In this section, we would introduce format convention of data involved in this dataflow, respectively. + +### DataLoader to Data Preprocessor + +DataLoader is an essential component in training and testing pipelines of MMEngine. Conceptually, it is derived from and consistent with [PyTorch](https://pytorch.org/). DataLoader loads data from filesystem and the original data passes through data preparation pipeline, then it would be sent to Data Preprocessor. + +MMSegmentation defines the default data format at [PackSegInputs](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/transforms/formatting.py#L12), it's the last component of `train_pipeline` and `test_pipeline`. Please refer to [data transform documentation](./transforms.md) for more information about data transform `pipeline`. + +Without any modifications, the return value of PackSegInputs is usually a `dict` and has only two keys, `inputs` and `data_samples`. The following pseudo-code shows the data types of the data loader output in mmseg, which is a batch of fetched data samples from the dataset, and data loader packs them into a dictionary of the list. `inputs` is the list of input tensors to the model and `data_samples` contains a list of input images' meta information and corresponding ground truth. + +```python +dict( + inputs=List[torch.Tensor], + data_samples=List[SegDataSample] +) +``` + +**Note:** [SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py) is a data structure interface of MMSegmentation, it is used as an interface between different components. `SegDataSample` implements the abstract data element `mmengine.structures.BaseDataElement`, please refer to [the SegDataSample documentation](./structures.md) and [data element documentation](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/data_element.html) in [MMEngine](https://github.com/open-mmlab/mmengine) for more information. + +### Data Preprocessor to Model + +Though drawn separately in the diagram [above](#overview-of-dataflow), data_preprocessor is a part of the model and thus can be found in [Model tutorial](./models.md) at data preprocessor chapter. + +The return value of data preprocessor is a dictionary, containing `inputs` and `data_samples`, `inputs` is batched images, a 4D tensor, and some additional meta info used in data preprocesses would be added to the `data_samples`. When transferred to the network, the dictionary would be unpacked to two values. The following pseudo-codes show the return value of the data preprocessor and the input values of model. + +```python +dict( + inputs=torch.Tensor, + data_samples=List[SegDataSample] +) +``` + +```python +class Network(BaseSegmentor): + + def forward(self, inputs: torch.Tensor, data_samples: List[SegDataSample], mode: str): + pass +``` + +**Note:** Model forward has 3 kinds of mode, which is controlled by input argumentmode, please refer [model tutorial](./models.md) for more details. + +### Model output + +As [model tutorial](./models.md#forward) mentioned 3 kinds of mode forward with 3 kinds of output. `train_step`and `test_step`(or `val_step`) correspond to `'loss'` and `'predict'` respectively. + +In `test_step` or `val_step`, the inference results would be transferred to `Evaluator`. You might read the [evaluation document](./evaluation.md) for more information about `Evaluator`. + +After inference, the [BaseSegmentor](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/segmentors/base.py#L15) in MMSegmentation would do a simple post process to pack inference results, the segmentation logits produced by the neural network, segmentation mask after the `argmax` operation and ground truth(if exists) would be packed into a similar `SegDataSample` instance. The return value of [postprocess_result](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/segmentors/base.py#L132) is a **`List` of `SegDataSample`**. Following diagram shows the key properties of these `SegDataSample` instances. + +![SegDataSample](https://user-images.githubusercontent.com/15952744/209912225-ab46a8d9-904a-43cb-8bf1-8bec4938ed29.png) + +The same as Data Preprocessor, loss function is also a part of the model, it's a property of [decode head](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/decode_heads/decode_head.py#L142). + +In MMSegmentation, the method [loss_by_feat](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/decode_heads/decode_head.py#L291) of `decode_head` is an unified interface used to compute loss. + +Parameters: + +- seg_logits (Tensor): The output from decode head forward function. +- batch_data_samples (List\[:obj:`SegDataSample`\]): The seg data samples. It usually includes information such as `metainfo` and `gt_sem_seg`. + +Returns: + +- dict\[str, Tensor\]: a dictionary of loss components + +**Note:** The `train_step` transfers the loss into OptimWrapper to update the weights in model, please refer [train_step](./models.md#train_step) for more details. diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/datasets.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/datasets.md new file mode 100644 index 0000000..1efc334 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/datasets.md @@ -0,0 +1,386 @@ +# Dataset + +Dataset classes in MMSegmentation have two functions: (1) load data information after [data preparation](../user_guides/2_dataset_prepare.md) +and (2) send data into [dataset transform pipeline](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/basesegdataset.py#L141) to do [data augmentation](./transforms.md). +There are 2 kinds of loaded information: (1) meta information which is original dataset information such as categories (classes) of dataset and their corresponding palette information, (2) data information which includes +the path of dataset images and labels. +The tutorial includes some main interfaces in MMSegmentation 1.x dataset class: methods of loading data information and modifying dataset classes in base dataset class, and the relationship between dataset and the data transform pipeline. + +## Main Interfaces + +Take Cityscapes as an example, if you want to run the example, please download and [preprocess](../user_guides/2_dataset_prepare.md#cityscapes) +Cityscapes dataset in `data` directory, before running the demo code: + +Instantiate Cityscapes training dataset: + +```python +from mmseg.datasets import CityscapesDataset +from mmengine.registry import init_default_scope +init_default_scope('mmseg') + +data_root = 'data/cityscapes/' +data_prefix=dict(img_path='leftImg8bit/train', seg_map_path='gtFine/train') +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='RandomCrop', crop_size=(512, 1024), cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PackSegInputs') +] + +dataset = CityscapesDataset(data_root=data_root, data_prefix=data_prefix, test_mode=False, pipeline=train_pipeline) +``` + +Get the length of training set: + +```python +print(len(dataset)) + +2975 +``` + +Get data information: The type of data information is `dict` which includes several keys: + +- `'img_path'`: path of images +- `'seg_map_path'`: path of segmentation labels +- `'seg_fields'`: saving label fields +- `'sample_idx'`: the index of the current sample + +There are also `'label_map'` and `'reduce_zero_label'` whose functions would be introduced in the next section. + +```python +# Acquire data information of first sample in dataset +print(dataset.get_data_info(0)) + +{'img_path': 'data/cityscapes/leftImg8bit/train/aachen/aachen_000000_000019_leftImg8bit.png', + 'seg_map_path': 'data/cityscapes/gtFine/train/aachen/aachen_000000_000019_gtFine_labelTrainIds.png', + 'label_map': None, + 'reduce_zero_label': False, + 'seg_fields': [], + 'sample_idx': 0} +``` + +Get dataset meta information: the type of MMSegmentation meta information is also `dict`, which includes `'classes'` field for dataset classes and `'palette'` field for corresponding colors in visualization, and has `'label_map'` field and `'reduce_zero_label'` filed. + +```python +print(dataset.metainfo) + +{'classes': ('road', + 'sidewalk', + 'building', + 'wall', + 'fence', + 'pole', + 'traffic light', + 'traffic sign', + 'vegetation', + 'terrain', + 'sky', + 'person', + 'rider', + 'car', + 'truck', + 'bus', + 'train', + 'motorcycle', + 'bicycle'), + 'palette': [[128, 64, 128], + [244, 35, 232], + [70, 70, 70], + [102, 102, 156], + [190, 153, 153], + [153, 153, 153], + [250, 170, 30], + [220, 220, 0], + [107, 142, 35], + [152, 251, 152], + [70, 130, 180], + [220, 20, 60], + [255, 0, 0], + [0, 0, 142], + [0, 0, 70], + [0, 60, 100], + [0, 80, 100], + [0, 0, 230], + [119, 11, 32]], + 'label_map': None, + 'reduce_zero_label': False} +``` + +The return value of dataset `__getitem__` method is the output of data samples after data augmentation, whose type is also `dict`. It has two fields: `'inputs'` corresponding to images after data augmentation, +and `'data_samples'` corresponding to [`SegDataSample`](./structures.md) which is new data structures in MMSegmentation 1.x, +and `gt_sem_seg` of `SegDataSample` has labels after data augmentation operations. + +```python +print(dataset[0]) + +{'inputs': tensor([[[131, 130, 130, ..., 23, 23, 23], + [132, 132, 132, ..., 23, 22, 23], + [134, 133, 133, ..., 23, 23, 23], + ..., + [ 66, 67, 67, ..., 71, 71, 71], + [ 66, 67, 66, ..., 68, 68, 68], + [ 67, 67, 66, ..., 70, 70, 70]], + + [[143, 143, 142, ..., 28, 28, 29], + [145, 145, 145, ..., 28, 28, 29], + [145, 145, 145, ..., 27, 28, 29], + ..., + [ 75, 75, 76, ..., 80, 81, 81], + [ 75, 76, 75, ..., 80, 80, 80], + [ 77, 76, 76, ..., 82, 82, 82]], + + [[126, 125, 126, ..., 21, 21, 22], + [127, 127, 128, ..., 21, 21, 22], + [127, 127, 126, ..., 21, 21, 22], + ..., + [ 63, 63, 64, ..., 69, 69, 70], + [ 64, 65, 64, ..., 69, 69, 69], + [ 65, 66, 66, ..., 72, 71, 71]]], dtype=torch.uint8), + 'data_samples': + _gt_sem_seg: + )} +``` + +## BaseSegDataset + +As mentioned above, dataset classes have the same functions, we implemented [`BaseSegDataset`](https://mmsegmentation.readthedocs.io/en/latest/api.html?highlight=BaseSegDataset#mmseg.datasets.BaseSegDataset) to reues the common functions. +It inherits [`BaseDataset` of MMEngine](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/basedataset.md) and follows unified initialization process of OpenMMLab. It supports the highly effective interior storing format, some functions like +dataset concatenation and repeatedly sampling. In MMSegmentation `BaseSegDataset`, the **method of loading data information** (`load_data_list`) is redefined and adds new `get_label_map` method to **modify dataset classes information**. + +### Loading Dataset Information + +The loaded data information includes the path of images samples and annotations samples, the detailed implementation could be found in +[`load_data_list`](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/basesegdataset.py#L231) of `BaseSegDataset` in MMSegmentation. +There are two main methods to acquire the path of images and labels: + +1. Load file paths according to the dirictory and suffix of input images and annotations + +If the dataset directory structure is organized as below, the [`load_data_list`](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/basesegdataset.py#L231) can parse dataset directory Structure: + +``` +├── data +│ ├── my_dataset +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ │ ├── xxx{img_suffix} +│ │ │ │ ├── yyy{img_suffix} +│ │ │ ├── val +│ │ │ │ ├── zzz{img_suffix} +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ │ ├── xxx{seg_map_suffix} +│ │ │ │ ├── yyy{seg_map_suffix} +│ │ │ ├── val +│ │ │ │ ├── zzz{seg_map_suffix} +``` + +Here is an example pf ADE20K, and below the directory structure of the dataset: + +``` +├── ade +│ ├── ADEChallengeData2016 +│ │ ├── annotations +│ │ │ ├── training +│ │ │ │ ├── ADE_train_00000001.png +│ │ │ │ ├── ... +│ │ │ │── validation +│ │ │ │ ├── ADE_val_00000001.png +│ │ │ │ ├── ... +│ │ ├── images +│ │ │ ├── training +│ │ │ │ ├── ADE_train_00000001.jpg +│ │ │ │ ├── ... +│ │ │ ├── validation +│ │ │ │ ├── ADE_val_00000001.jpg +│ │ │ │ ├── ... +``` + +```python +from mmseg.datasets import ADE20KDataset + +ADE20KDataset(data_root = 'data/ade/ADEChallengeData2016', + data_prefix=dict(img_path='images/training', seg_map_path='annotations/training'), + img_suffix='.jpg', + seg_map_suffix='.png', + reduce_zero_label=True) +``` + +2. Load file paths from annotation file + +Dataset also can load an annotation file which includes the data sample paths of dataset. +Take PascalContext dataset instance as an example, its input annotation file is: + +```python +2008_000008 +... +``` + +It needs to define `ann_file` when instantiation: + +```python +PascalContextDataset(data_root='data/VOCdevkit/VOC2010/', + data_prefix=dict(img_path='JPEGImages', seg_map_path='SegmentationClassContext'), + ann_file='ImageSets/SegmentationContext/train.txt') +``` + +### Modification of Dataset Classes + +- Use `metainfo` input argument + +Meta information is defined as class variables, such as `METAINFO` variable of Cityscapes: + +```python +class CityscapesDataset(BaseSegDataset): + """Cityscapes dataset. + + The ``img_suffix`` is fixed to '_leftImg8bit.png' and ``seg_map_suffix`` is + fixed to '_gtFine_labelTrainIds.png' for Cityscapes dataset. + """ + METAINFO = dict( + classes=('road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', + 'sky', 'person', 'rider', 'car', 'truck', 'bus', 'train', + 'motorcycle', 'bicycle'), + palette=[[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, + 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], + [220, 20, 60], [255, 0, 0], [0, 0, 142], [0, 0, 70], + [0, 60, 100], [0, 80, 100], [0, 0, 230], [119, 11, 32]]) + +``` + +Here `'classes'` defines class names of Cityscapes dataset annotations, if users only concern some classes about vehicles and **ignore other classes**, +the meta information of dataset could be modified by defined input argument `metainfo` when instantiating Cityscapes dataset: + +```python +from mmseg.datasets import CityscapesDataset + +data_root = 'data/cityscapes/' +data_prefix=dict(img_path='leftImg8bit/train', seg_map_path='gtFine/train') +# metainfo only keep classes below: +metainfo=dict(classes=( 'car', 'truck', 'bus', 'train', 'motorcycle', 'bicycle')) +dataset = CityscapesDataset(data_root=data_root, data_prefix=data_prefix, metainfo=metainfo) + +print(dataset.metainfo) + +{'classes': ('car', 'truck', 'bus', 'train', 'motorcycle', 'bicycle'), + 'palette': [[0, 0, 142], + [0, 0, 70], + [0, 60, 100], + [0, 80, 100], + [0, 0, 230], + [119, 11, 32], + [128, 64, 128], + [244, 35, 232], + [70, 70, 70], + [102, 102, 156], + [190, 153, 153], + [153, 153, 153], + [250, 170, 30], + [220, 220, 0], + [107, 142, 35], + [152, 251, 152], + [70, 130, 180], + [220, 20, 60], + [255, 0, 0]], + # pixels whose label index are 255 would be ignored when calculating loss + 'label_map': {0: 255, + 1: 255, + 2: 255, + 3: 255, + 4: 255, + 5: 255, + 6: 255, + 7: 255, + 8: 255, + 9: 255, + 10: 255, + 11: 255, + 12: 255, + 13: 0, + 14: 1, + 15: 2, + 16: 3, + 17: 4, + 18: 5}, + 'reduce_zero_label': False} +``` + +Meta information is different from default setting of Cityscapes dataset. Moreover, `label_map` field is also defined, which is used for modifying label index of each pixel on segmentation mask. +The segmentation label would re-map class information by `label_map`, [here](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/basesegdataset.py#L151) is detailed implementation: + +```python +gt_semantic_seg_copy = gt_semantic_seg.copy() +for old_id, new_id in results['label_map'].items(): + gt_semantic_seg[gt_semantic_seg_copy == old_id] = new_id +``` + +- Using `reduce_zero_label` input argument + +To ignore label 0 (such as ADE20K dataset), we can use `reduce_zero_label` (default to `False`) argument of BaseSegDataset and its subclasses. +When `reduce_zero_label` is `True`, label 0 in segmentation annotations would be set as 255 (models of MMSegmentation would ignore label 255 in calculating loss) and indices of other labels will minus 1: + +```python +gt_semantic_seg[gt_semantic_seg == 0] = 255 +gt_semantic_seg = gt_semantic_seg - 1 +gt_semantic_seg[gt_semantic_seg == 254] = 255 +``` + +## Dataset and Data Transform Pipeline + +If the argument `pipeline` is defined, the return value of `__getitem__` method is after data argument. +If dataset input argument does not define pipeline, it is the same as return value of `get_data_info` method. + +```python +from mmseg.datasets import CityscapesDataset + +data_root = 'data/cityscapes/' +data_prefix=dict(img_path='leftImg8bit/train', seg_map_path='gtFine/train') +dataset = CityscapesDataset(data_root=data_root, data_prefix=data_prefix, test_mode=False) + +print(dataset[0]) + +{'img_path': 'data/cityscapes/leftImg8bit/train/aachen/aachen_000000_000019_leftImg8bit.png', + 'seg_map_path': 'data/cityscapes/gtFine/train/aachen/aachen_000000_000019_gtFine_labelTrainIds.png', + 'label_map': None, + 'reduce_zero_label': False, + 'seg_fields': [], + 'sample_idx': 0} +``` diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/engine.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/engine.md new file mode 100644 index 0000000..7acfe5a --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/engine.md @@ -0,0 +1,279 @@ +# Training Engine + +MMEngine defined some [basic loop controllers](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py) such as epoch-based training loop (`EpochBasedTrainLoop`), iteration-based training loop (`IterBasedTrainLoop`), standard validation loop (`ValLoop`), and standard testing loop (`TestLoop`). + +OpenMMLab's algorithm libraries like MMSegmentation abstract model training, testing, and inference as `Runner` to handle. Users can use the default `Runner` in MMEngine directly or modify the `Runner` to meet customized needs. This document mainly introduces how users can configure existing running settings, hooks, and optimizers' basic concepts and usage methods. + +## Configuring Runtime Settings + +### Configuring Training Iterations + +Loop controllers refer to the execution process during training, validation, and testing. `train_cfg`, `val_cfg`, and `test_cfg` are used to build these processes in the configuration file. MMSegmentation sets commonly used training iterations in `train_cfg` under the `configs/_base_/schedules` folder. +For example, to train for 80,000 iterations using the iteration-based training loop (`IterBasedTrainLoop`) and perform validation every 8,000 iterations, you can set it as follows: + +```python +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=8000) +``` + +### Configuring Training Optimizers + +Here's an example of a SGD optimizer: + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005), + clip_grad=None) +``` + +OpenMMLab supports all optimizers in PyTorch. For more details, please refer to the [MMEngine optimizer documentation](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/optim_wrapper.md). + +It is worth emphasizing that `optim_wrapper` is a variable of `runner`, so when configuring the optimizer, the field to configure is the `optim_wrapper` field. For more information on using optimizers, see the [Optimizer](#Optimizer) section below. + +### Configuring Training Parameter Schedulers + +Before configuring the training parameter scheduler, it is recommended to first understand the basic concepts of parameter schedulers in the [MMEngine documentation](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/param_scheduler.md). + +Here's an example of a parameter scheduler. During training, a linearly changing learning rate strategy is used for warm-up in the first 1,000 iterations. After the first 1,000 iterations until the 16,000 iterations in the end, the default polynomial learning rate decay is used: + +```python +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +``` + +Note: When modifying the `max_iters` in `train_cfg`, make sure the parameters in the parameter scheduler `param_scheduler` are also modified accordingly. + +## Hook + +### Introduction + +OpenMMLab abstracts the model training and testing process as `Runner`. Inserting hooks can implement the corresponding functionality needed at different training and testing stages (such as "before and after each training iter", "before and after each validation iter", etc.) in `Runner`. For more introduction on hook mechanisms, please refer to [here](https://www.calltutors.com/blog/what-is-hook). + +Hooks used in `Runner` are divided into two categories: + +- Default hooks: + +They implement essential functions during training and are defined in the configuration file by `default_hooks` and passed to `Runner`. `Runner` registers them through the [`register_default_hooks`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py#L1780) method. + +Hooks have corresponding priorities; the higher the priority, the earlier the runner calls them. If the priorities are the same, the calling order is consistent with the hook registration order. + +It is not recommended for users to modify the default hook priorities. Please refer to the [MMEngine hooks documentation](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/hook.md) to understand the hook priority definitions. + +The following are the default hooks used in MMSegmentation: + +| Hook | Function | Priority | +| :--------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: | :---------------: | +| [IterTimerHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/iter_timer_hook.py) | Record the time spent on each iteration. | NORMAL (50) | +| [LoggerHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/logger_hook.py) | Collect log records from different components in `Runner` and output them to terminal, JSON file, tensorboard, wandb, etc. | BELOW_NORMAL (60) | +| [ParamSchedulerHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/param_scheduler_hook.py) | Update some hyperparameters in the optimizer, such as learning rate momentum. | LOW (70) | +| [CheckpointHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py) | Regularly save checkpoint files. | VERY_LOW (90) | +| [DistSamplerSeedHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/sampler_seed_hook.py) | Ensure the distributed sampler shuffle is enabled. | NORMAL (50) | +| [SegVisualizationHook](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/visualization/local_visualizer.py) | Visualize prediction results during validation and testing. | NORMAL (50) | + +MMSegmentation registers some hooks with essential training functions in `default_hooks`: + +```python +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=32000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) +``` + +All the default hooks mentioned above, except for `SegVisualizationHook`, are implemented in MMEngine. The `SegVisualizationHook` is a hook implemented in MMSegmentation, which will be introduced later. + +- Modifying default hooks + +We will use the `logger` and `checkpoint` in `default_hooks` as examples to demonstrate how to modify the default hooks in `default_hooks`. + +(1) Model saving configuration + +`default_hooks` uses the `checkpoint` field to initialize the [model saving hook (CheckpointHook)](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L19). + +```python +checkpoint = dict(type='CheckpointHook', interval=1) +``` + +Users can set `max_keep_ckpts` to save only a small number of checkpoints or use `save_optimizer` to determine whether to save optimizer information. More details on related parameters can be found [here](https://mmengine.readthedocs.io/en/latest/api/generated/mmengine.hooks.CheckpointHook.html#checkpointhook). + +(2) Logging configuration + +The `LoggerHook` is used to collect log information from different components in `Runner` and write it to terminal, JSON files, tensorboard, wandb, etc. + +```python +logger=dict(type='LoggerHook', interval=10) +``` + +In the latest 1.x version of MMSegmentation, some logger hooks (LoggerHook) such as `TextLoggerHook`, `WandbLoggerHook`, and `TensorboardLoggerHook` will no longer be used. Instead, MMEngine uses `LogProcessor` to handle the information processed by the aforementioned hooks, which are now in [`MessageHub`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/logging/message_hub.py#L17), [`WandbVisBackend`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/visualization/vis_backend.py#L324), and [`TensorboardVisBackend`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/visualization/vis_backend.py#L472). + +Detailed usage is as follows, configuring the visualizer and specifying the visualization backend at the same time, here using Tensorboard as the visualizer's backend: + +```python +# TensorboardVisBackend +visualizer = dict( + type='SegLocalVisualizer', vis_backends=[dict(type='TensorboardVisBackend')], name='visualizer') +``` + +For more related usage, please refer to [MMEngine Visualization Backend User Tutorial](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/visualization.md). + +- Custom hooks + +Custom hooks are defined in the configuration through `custom_hooks`, and `Runner` registers them using the [`register_custom_hooks`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py#L1820) method. + +The priority of custom hooks needs to be set in the configuration file; if not, it will be set to `NORMAL` by default. The following are some custom hooks implemented in MMEngine: + +| Hook | Usage | +| :----------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | +| [EMAHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/ema_hook.py) | Use Exponential Moving Average (EMA) during model training. | +| [EmptyCacheHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/empty_cache_hook.py) | Release all GPU memory not occupied by the cache during training | +| [SyncBuffersHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/sync_buffer_hook.py) | Synchronize the parameters in the model buffer, such as `running_mean` and `running_var` in BN, at the end of each training epoch. | + +The following is a use case for `EMAHook`, where the config file includes the configuration of the implemented custom hooks as members of the `custom_hooks` list. + +```python +custom_hooks = [ + dict(type='EMAHook', start_iters=500, priority='NORMAL') +] +``` + +### SegVisualizationHook + +MMSegmentation implemented [`SegVisualizationHook`](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/engine/hooks/visualization_hook.py#L17), which is used to visualize prediction results during validation and testing. +`SegVisualizationHook` overrides the `_after_iter` method in the base class `Hook`. During validation or testing, it calls the `add_datasample` method of `visualizer` to draw semantic segmentation results according to the specified iteration interval. The specific implementation is as follows: + +```python +... +@HOOKS.register_module() +class SegVisualizationHook(Hook): +... + def _after_iter(self, + runner: Runner, + batch_idx: int, + data_batch: dict, + outputs: Sequence[SegDataSample], + mode: str = 'val') -> None: +... + # If it's a training phase or self.draw is False, then skip it + if self.draw is False or mode == 'train': + return +... + if self.every_n_inner_iters(batch_idx, self.interval): + for output in outputs: + img_path = output.img_path + img_bytes = self.file_client.get(img_path) + img = mmcv.imfrombytes(img_bytes, channel_order='rgb') + window_name = f'{mode}_{osp.basename(img_path)}' + + self._visualizer.add_datasample( + window_name, + img, + data_sample=output, + show=self.show, + wait_time=self.wait_time, + step=runner.iter) + +``` + +For more details about visualization, you can check [here](../user_guides/visualization.md). + +## Optimizer + +In the previous configuration and runtime settings, we provided a simple example of configuring the training optimizer. This section will further detailly introduce how to configure optimizers in MMSegmentation. + +## Optimizer Wrapper + +OpenMMLab 2.0 introduces an optimizer wrapper that supports different training strategies, including mixed-precision training, gradient accumulation, and gradient clipping. Users can choose the appropriate training strategy according to their needs. The optimizer wrapper also defines a standard parameter update process, allowing users to switch between different training strategies within the same code. For more information, please refer to the [MMEngine optimizer wrapper documentation](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/optim_wrapper.md). + +Here are some common usage methods in MMSegmentation: + +#### Configuring PyTorch Supported Optimizers + +OpenMMLab 2.0 supports all native PyTorch optimizers, as referenced [here](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/optim_wrapper.md). + +To set the optimizer used by the `Runner` during training in the configuration file, you need to define `optim_wrapper` instead of `optimizer`. Below is an example of configuring an optimizer during training: + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005), + clip_grad=None) +``` + +#### Configuring Gradient Clipping + +When the model training requires gradient clipping, you can configure it as shown in the following example: + +```python +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2)) +``` + +Here, `max_norm` refers to the maximum value of the gradient after clipping, and `norm_type` refers to the norm used when clipping the gradient. Related methods can be found in [torch.nn.utils.clip_grad_norm\_](https://pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_norm_.html). + +#### Configuring Mixed Precision Training + +When mixed precision training is needed to reduce memory usage, you can use `AmpOptimWrapper`. The specific configuration is as follows: + +```python +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='AmpOptimWrapper', optimizer=optimizer) +``` + +The default setting for `loss_scale` in [`AmpOptimWrapper`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/amp_optimizer_wrapper.py#L20) is `dynamic`. + +#### Configuring Hyperparameters for Different Layers of the Model Network + +In model training, if you want to set different optimization strategies for different parameters in the optimizer, such as setting different learning rates, weight decay, and other hyperparameters, you can achieve this by setting `paramwise_cfg` in the `optim_wrapper` of the configuration file. + +The following config file uses the [ViT `optim_wrapper`](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py#L15-L27) as an example to introduce the use of `paramwise_cfg` parameters. During training, the weight decay parameter coefficients for the `pos_embed`, `mask_token`, and `norm` modules are set to 0. That is, during training, the weight decay for these modules will be changed to `weight_decay * decay_mult`=0. + +```python +optimizer = dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + paramwise_cfg=dict( + custom_keys={ + 'pos_embed': dict(decay_mult=0.), + 'cls_token': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) +``` + +Here, `decay_mult` refers to the weight decay coefficient for the corresponding parameters. For more information on the usage of `paramwise_cfg`, please refer to the [MMEngine optimizer wrapper documentation](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/optim_wrapper.md). + +### Optimizer Wrapper Constructor + +The default optimizer wrapper constructor [`DefaultOptimWrapperConstructor`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L19) builds the optimizer used in training based on the input `optim_wrapper` and `paramwise_cfg` defined in the `optim_wrapper`. When the functionality of [`DefaultOptimWrapperConstructor`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L19) does not meet the requirements, you can customize the optimizer wrapper constructor to implement the configuration of hyperparameters. + +MMSegmentation has implemented the [`LearningRateDecayOptimizerConstructor`](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/engine/optimizers/layer_decay_optimizer_constructor.py#L104), which can decay the learning rate of model parameters in the backbone networks of ConvNeXt, BEiT, and MAE models during training according to the defined decay ratio (`decay_rate`). The configuration in the configuration file is as follows: + +```python +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg={ + 'decay_rate': 0.9, + 'decay_type': 'stage_wise', + 'num_layers': 12 + }, + constructor='LearningRateDecayOptimizerConstructor', + loss_scale='dynamic') +``` + +The purpose of `_delete_=True` is to ignore the inherited configuration in the OpenMMLab Config. In this code snippet, the inherited `optim_wrapper` configuration is ignored. For more information on `_delete_` fields, please refer to the [MMEngine documentation](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/config.md#delete-key-in-dict). diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/evaluation.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/evaluation.md new file mode 100644 index 0000000..ca0beee --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/evaluation.md @@ -0,0 +1,155 @@ +# Evaluation + +The evaluation procedure would be executed at [ValLoop](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L300) and [TestLoop](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L373), users can evaluate model performance during training or using the test script with simple settings in the configuration file. The `ValLoop` and `TestLoop` are properties of [Runner](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py#L59), they will be built the first time they are called. To build the `ValLoop` successfully, the `val_dataloader` and `val_evaluator` must be set when building `Runner` since `dataloader` and `evaluator` are required parameters, and the same goes for `TestLoop`. For more information about the Runner's design, please refer to the [documentation](https://github.com/open-mmlab/mmengine/blob/main/docs/en/design/runner.md) of [MMEngine](https://github.com/open-mmlab/mmengine). + +![test_step/val_step dataflow](https://user-images.githubusercontent.com/15952744/228828179-3269baa3-bebd-4c9a-9787-59e7d785fbcf.png) + +In MMSegmentation, we write the settings of dataloader and metrics in the config files of datasets and the configuration of the evaluation loop in the `schedule_x` config files by default. + +For example, in the ADE20K config file `configs/_base_/dataset/ade20k.py`, on lines 37 to 48, we configured the `val_dataloader`, on line 51, we select `IoUMetric` as the evaluator and set `mIoU` as the metric: + +```python +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +``` + +To be able to evaluate the model during training, for example, we add the evaluation configuration to the file `configs/schedules/schedule_40k.py` on lines 15 to 16: + +```python +train_cfg = dict(type='IterBasedTrainLoop', max_iters=40000, val_interval=4000) +val_cfg = dict(type='ValLoop') +``` + +With the above two settings, MMSegmentation evaluates the **mIoU** metric of the model once every 4000 iterations during the training of 40K iterations. + +If we would like to test the model after training, we need to add the `test_dataloader`, `test_evaluator` and `test_cfg` configs to the config file. + +```python +test_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) + +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_cfg = dict(type='TestLoop') +``` + +In MMSegmentation, the settings of `test_dataloader` and `test_evaluator` are the same as the `ValLoop`'s dataloader and evaluator by default, we can modify these settings to meet our needs. + +## IoUMetric + +MMSegmentation implements [IoUMetric](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/evaluation/metrics/iou_metric.py) and [CityscapesMetric](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/evaluation/metrics/citys_metric.py) for evaluating the performance of models, based on the [BaseMetric](https://github.com/open-mmlab/mmengine/blob/main/mmengine/evaluator/metric.py) provided by [MMEngine](https://github.com/open-mmlab/mmengine). Please refer to [the documentation](https://mmengine.readthedocs.io/en/latest/tutorials/evaluation.html) for more details about the unified evaluation interface. + +Here we briefly describe the arguments and the two main methods of `IoUMetric`. + +The constructor of `IoUMetric` has some additional parameters besides the base `collect_device` and `prefix`. + +The arguments of the constructor: + +- ignore_index (int) - Index that will be ignored in evaluation. Default: 255. +- iou_metrics (list\[str\] | str) - Metrics to be calculated, the options includes 'mIoU', 'mDice' and 'mFscore'. +- nan_to_num (int, optional) - If specified, NaN values will be replaced by the numbers defined by the user. Default: None. +- beta (int) - Determines the weight of recall in the combined score. Default: 1. +- collect_device (str) - Device name used for collecting results from different ranks during distributed training. Must be 'cpu' or 'gpu'. Defaults to 'cpu'. +- prefix (str, optional) - The prefix that will be added in the metric names to disambiguate homonymous metrics of different evaluators. If the prefix is not provided in the argument, self.default_prefix will be used instead. Defaults to None. + +`IoUMetric` implements the IoU metric calculation, the core two methods of `IoUMetric` are `process` and `compute_metrics`. + +- `process` method processes one batch of data and data_samples. +- `compute_metrics` method computes the metrics from processed results. + +### IoUMetric.process + +Parameters: + +- data_batch (Any) - A batch of data from the dataloader. +- data_samples (Sequence\[dict\]) - A batch of outputs from the model. + +Returns: + +This method doesn't have returns since the processed results would be stored in `self.results`, which will be used to compute the metrics when all batches have been processed. + +### IoUMetric.compute_metrics + +Parameters: + +- results (list) - The processed results of each batch. + +Returns: + +- Dict\[str, float\] - The computed metrics. The keys are the names of the metrics, and the values are corresponding results. The key mainly includes **aAcc**, **mIoU**, **mAcc**, **mDice**, **mFscore**, **mPrecision**, **mRecall**. + +## CityscapesMetric + +`CityscapesMetric` uses the official [CityscapesScripts](https://github.com/mcordts/cityscapesScripts) provided by Cityscapes to evaluate model performance. + +### Usage + +Before using it, please install the `cityscapesscripts` package first: + +```shell +pip install cityscapesscripts +``` + +Since the `IoUMetric` is used as the default evaluator in MMSegmentation, if you would like to use `CityscapesMetric`, customizing the config file is required. In your customized config file, you should overwrite the default evaluator as follows. + +```python +val_evaluator = dict(type='CityscapesMetric', output_dir='tmp') +test_evaluator = val_evaluator +``` + +### Interface + +The arguments of the constructor: + +- output_dir (str) - The directory for output prediction +- ignore_index (int) - Index that will be ignored in evaluation. Default: 255. +- format_only (bool) - Only format result for results commit without perform evaluation. It is useful when you want to format the result to a specific format and submit it to the test server. Defaults to False. +- keep_results (bool) - Whether to keep the results. When `format_only` is True, `keep_results` must be True. Defaults to False. +- collect_device (str) - Device name used for collecting results from different ranks during distributed training. Must be 'cpu' or 'gpu'. Defaults to 'cpu'. +- prefix (str, optional) - The prefix that will be added in the metric names to disambiguate homonymous metrics of different evaluators. If prefix is not provided in the argument, self.default_prefix will be used instead. Defaults to None. + +#### CityscapesMetric.process + +This method would draw the masks on images and save the painted images to `work_dir`. + +Parameters: + +- data_batch (dict) - A batch of data from the dataloader. +- data_samples (Sequence\[dict\]) - A batch of outputs from the model. + +Returns: + +This method doesn't have returns, the annotations' path would be stored in `self.results`, which will be used to compute the metrics when all batches have been processed. + +#### CityscapesMetric.compute_metrics + +This method would call `cityscapesscripts.evaluation.evalPixelLevelSemanticLabeling` tool to calculate metrics. + +Parameters: + +- results (list) - Testing results of the dataset. + +Returns: + +- dict\[str: float\] - Cityscapes evaluation results. diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/index.rst b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/index.rst new file mode 100644 index 0000000..53ef8c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/index.rst @@ -0,0 +1,26 @@ +Basic Concepts +*************** + +.. toctree:: + :maxdepth: 1 + + data_flow.md + structures.md + models.md + datasets.md + transforms.md + evaluation.md + engine.md + training_tricks.md + +Component Customization +************************ + +.. toctree:: + :maxdepth: 1 + + add_models.md + add_datasets.md + add_transforms.md + add_metrics.md + customize_runtime.md diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/models.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/models.md new file mode 100644 index 0000000..b008986 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/models.md @@ -0,0 +1,164 @@ +# Models + +We usually define a neural network in a deep learning task as a model, and this model is the core of an algorithm. [MMEngine](https://github.com/open-mmlab/mmengine) abstracts a unified model [BaseModel](https://github.com/open-mmlab/mmengine/blob/main/mmengine/model/base_model/base_model.py#L16) to standardize the interfaces for training, testing and other processes. All models implemented by MMSegmentation inherit from `BaseModel`, and in MMSegmentation we implemented forward and added some functions for the semantic segmentation algorithm. + +## Common components + +### Segmentor + +In MMSegmentation, we abstract the network architecture as a **Segmentor**, it is a model that contains all components of a network. We have already implemented **EncoderDecoder** and **CascadeEncoderDecoder**, which typically consist of **Data preprocessor**, **Backbone**, **Decode head** and **Auxiliary head**. + +### Data preprocessor + +**Data preprocessor** is the part that copies data to the target device and preprocesses the data into the model input format. + +### Backbone + +**Backbone** is the part that transforms an image to feature maps, such as a **ResNet-50** without the last fully connected layer. + +### Neck + +**Neck** is the part that connects the backbone and heads. It performs some refinements or reconfigurations on the raw feature maps produced by the backbone. An example is **Feature Pyramid Network (FPN)**. + +### Decode head + +**Decode head** is the part that transforms the feature maps into a segmentation mask, such as **PSPNet**. + +### Auxiliary head + +**Auxiliary head** is an optional component that transforms the feature maps into segmentation masks which only used for computing auxiliary losses. + +## Basic interfaces + +MMSegmentation wraps `BaseModel` and implements the [BaseSegmentor](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/models/segmentors/base.py#L15) class, which mainly provides the interfaces `forward`, `train_step`, `val_step` and `test_step`. The following will introduce these interfaces in detail. + +### forward + +![EncoderDecoder dataflow](https://user-images.githubusercontent.com/15952744/228827860-c0e34875-d370-4736-84f0-9560c26c9576.png) +![CascadeEncoderDecoder dataflow](https://user-images.githubusercontent.com/15952744/228827987-aa214507-0c6d-4a08-8ce4-679b2b200b79.png) + +The `forward` method returns losses or predictions of training, validation, testing, and a simple inference process. + +The method should accept three modes: "tensor", "predict" and "loss": + +- "tensor": Forward the whole network and return the tensor or tuple of tensor without any post-processing, same as a common `nn.Module`. +- "predict": Forward and return the predictions, which are fully processed to a list of `SegDataSample`. +- "loss": Forward and return a `dict` of losses according to the given inputs and data samples. + +**Note:** [SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py) is a data structure interface of MMSegmentation, it is used as an interface between different components. `SegDataSample` implements the abstract data element `mmengine.structures.BaseDataElement`, please refer to [the SegDataSample documentation](https://mmsegmentation.readthedocs.io/en/1.x/advanced_guides/structures.html) and [data element documentation](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/data_element.html) in [MMEngine](https://github.com/open-mmlab/mmengine) for more information. + +Note that this method doesn't handle either backpropagation or optimizer updating, which are done in the method `train_step`. + +Parameters: + +- inputs (torch.Tensor) - The input tensor with shape (N, C, ...) in general. +- data_sample (list\[[SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py)\]) - The seg data samples. It usually includes information such as `metainfo` and `gt_sem_seg`. Default to None. +- mode (str) - Return what kind of value. Defaults to 'tensor'. + +Returns: + +- `dict` or `list`: + - If `mode == "loss"`, return a `dict` of loss tensor used for backward and logging. + - If `mode == "predict"`, return a `list` of `SegDataSample`, the inference results will be incrementally added to the `data_sample` parameter passed to the forward method, each `SegDataSample` contains the following keys: + - pred_sem_seg (`PixelData`): Prediction of semantic segmentation. + - seg_logits (`PixelData`): Predicted logits of semantic segmentation before normalization. + - If `mode == "tensor"`, return a `tensor` or `tuple of tensor` or `dict` of `tensor` for custom use. + +### prediction modes + +We briefly describe the fields of the model's configuration in [the config documentation](../user_guides/1_config.md), here we elaborate on the `model.test_cfg` field. `model.test_cfg` is used to control forward behavior, the `forward` method in `"predict"` mode can run in two modes: + +- `whole_inference`: If `cfg.model.test_cfg.mode == 'whole'`, model will inference with full images. + + An `whole_inference` mode example config: + + ```python + model = dict( + type='EncoderDecoder' + ... + test_cfg=dict(mode='whole') + ) + ``` + +- `slide_inference`: If `cfg.model.test_cfg.mode == 'slide'`, model will inference by sliding-window. **Note:** if you select the `slide` mode, `cfg.model.test_cfg.stride` and `cfg.model.test_cfg.crop_size` should also be specified. + + An `slide_inference` mode example config: + + ```python + model = dict( + type='EncoderDecoder' + ... + test_cfg=dict(mode='slide', crop_size=256, stride=170) + ) + ``` + +### train_step + +The `train_step` method calls the forward interface of the `loss` mode to get the loss `dict`. The `BaseModel` class implements the default model training process including preprocessing, model forward propagation, loss calculation, optimization, and back-propagation. + +Parameters: + +- data (dict or tuple or list) - Data sampled from the dataset. In MMSegmentation, the data dict contains `inputs` and `data_samples` two fields. +- optim_wrapper (OptimWrapper) - OptimWrapper instance used to update model parameters. + +**Note:** [OptimWrapper](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/optimizer_wrapper.py#L17) provides a common interface for updating parameters, please refer to optimizer wrapper [documentation](https://mmengine.readthedocs.io/en/latest/tutorials/optim_wrapper.html) in [MMEngine](https://github.com/open-mmlab/mmengine) for more information. + +Returns: + +- Dict\[str, `torch.Tensor`\]: A `dict` of tensor for logging. + +![train_step dataflow](https://user-images.githubusercontent.com/15952744/228828089-a9ae1225-958d-4cf7-99af-9af8576f7ef7.png) + +### val_step + +The `val_step` method calls the forward interface of the `predict` mode and returns the prediction result, which is further passed to the process interface of the evaluator and the `after_val_iter` interface of the Hook. + +Parameters: + +- data (`dict` or `tuple` or `list`) - Data sampled from the dataset. In MMSegmentation, the data dict contains `inputs` and `data_samples` two fields. + +Returns: + +- `list` - The predictions of given data. + +![test_step/val_step dataflow](https://user-images.githubusercontent.com/15952744/228828179-3269baa3-bebd-4c9a-9787-59e7d785fbcf.png) + +### test_step + +The `BaseModel` implements `test_step` the same as `val_step`. + +## Data Preprocessor + +The [SegDataPreProcessor](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/models/data_preprocessor.py#L13) implemented by MMSegmentation inherits from the [BaseDataPreprocessor](https://github.com/open-mmlab/mmengine/blob/main/mmengine/model/base_model/data_preprocessor.py#L18) implemented by [MMEngine](https://github.com/open-mmlab/mmengine) and provides the functions of data preprocessing and copying data to the target device. + +The runner carries the model to the specified device during the construction stage, while the data is carried to the specified device by the [SegDataPreProcessor](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/models/data_preprocessor.py#L13) in `train_step`, `val_step`, and `test_step`, and the processed data is further passed to the model. + +The parameters of the `SegDataPreProcessor` constructor: + +- mean (Sequence\[Number\], optional) - The pixel mean of R, G, B channels. Defaults to None. +- std (Sequence\[Number\], optional) - The pixel standard deviation of R, G, B channels. Defaults to None. +- size (tuple, optional) - Fixed padding size. +- size_divisor (int, optional) - The divisor of padded size. +- pad_val (float, optional) - Padding value. Default: 0. +- seg_pad_val (float, optional) - Padding value of segmentation map. Default: 255. +- bgr_to_rgb (bool) - whether to convert image from BGR to RGB. Defaults to False. +- rgb_to_bgr (bool) - whether to convert image from RGB to BGR. Defaults to False. +- batch_augments (list\[dict\], optional) - Batch-level augmentations. Default to None. + +The data will be processed as follows: + +- Collate and move data to the target device. +- Pad inputs to the input size with defined `pad_val`, and pad seg map with defined `seg_pad_val`. +- Stack inputs to batch_inputs. +- Convert inputs from bgr to rgb if the shape of input is (3, H, W). +- Normalize image with defined std and mean. +- Do batch augmentations like Mixup and Cutmix during training. + +The parameters of the `forward` method: + +- data (dict) - data sampled from dataloader. +- training (bool) - Whether to enable training time augmentation. + +The returns of the `forward` method: + +- Dict: Data in the same format as the model input. diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/structures.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/structures.md new file mode 100644 index 0000000..2607242 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/structures.md @@ -0,0 +1,104 @@ +# Structures + +To unify input and output interfaces between different models and modules, OpenMMLab 2.0 MMEngine defines an abstract data structure, +it has implemented basic functions of `Create`, `Read`, `Update`, `Delete`, supported data transferring among different types of devices +and tensor-like or dictionary-like operations such as `.cpu()`, `.cuda()`, `.get()` and `.detach()`. +More details can be found [here](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/data_element.md). + +MMSegmentation also follows this interface protocol and defines `SegDataSample` which is used to encapsulate the data of semantic segmentation task. + +## Semantic Segmentation Data SegDataSample + +[SegDataSample](mmseg.structures.SegDataSample) includes three main fields `gt_sem_seg`, `pred_sem_seg` and `seg_logits`, which are used to store the annotation information and prediction results respectively. + +| Field | Type | Description | +| -------------- | ------------------------- | ------------------------------------------ | +| gt_sem_seg | [`PixelData`](#pixeldata) | Annotation information. | +| pred_instances | [`PixelData`](#pixeldata) | The predicted result. | +| seg_logits | [`PixelData`](#pixeldata) | The raw (non-normalized) predicted result. | + +The following sample code demonstrates the use of `SegDataSample`. + +```python +import torch +from mmengine.structures import PixelData +from mmseg.structures import SegDataSample + +img_meta = dict(img_shape=(4, 4, 3), + pad_shape=(4, 4, 3)) +data_sample = SegDataSample() +# defining gt_segmentations for encapsulate the ground truth data +gt_segmentations = PixelData(metainfo=img_meta) +gt_segmentations.data = torch.randint(0, 2, (1, 4, 4)) + +# add and process property in SegDataSample +data_sample.gt_sem_seg = gt_segmentations +assert 'gt_sem_seg' in data_sample +assert 'sem_seg' in data_sample.gt_sem_seg +assert 'img_shape' in data_sample.gt_sem_seg.metainfo_keys() +print(data_sample.gt_sem_seg.shape) +''' +(4, 4) +''' +print(data_sample) +''' + +) at 0x1c2aae44d60> +''' + +# delete and change property in SegDataSample +data_sample = SegDataSample() +gt_segmentations = PixelData(metainfo=img_meta) +gt_segmentations.data = torch.randint(0, 2, (1, 4, 4)) +data_sample.gt_sem_seg = gt_segmentations +data_sample.gt_sem_seg.set_metainfo(dict(img_shape=(4,4,9), pad_shape=(4,4,9))) +del data_sample.gt_sem_seg.img_shape + +# Tensor-like operations +data_sample = SegDataSample() +gt_segmentations = PixelData(metainfo=img_meta) +gt_segmentations.data = torch.randint(0, 2, (1, 4, 4)) +cuda_gt_segmentations = gt_segmentations.cuda() +cuda_gt_segmentations = gt_segmentations.to('cuda:0') +cpu_gt_segmentations = cuda_gt_segmentations.cpu() +cpu_gt_segmentations = cuda_gt_segmentations.to('cpu') +``` + +## Customize New Property in SegDataSample + +If you want to customize new property in `SegDataSample`, you may follow [SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py) below: + +```python +class SegDataSample(BaseDataElement): + ... + + @property + def xxx_property(self) -> xxxData: + return self._xxx_property + + @xxx_property.setter + def xxx_property(self, value: xxxData) -> None: + self.set_field(value, '_xxx_property', dtype=xxxData) + + @xxx_property.deleter + def xxx_property(self) -> None: + del self._xxx_property +``` + +Then a new property would be added to `SegDataSample`. diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/training_tricks.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/training_tricks.md new file mode 100644 index 0000000..bc4f722 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/training_tricks.md @@ -0,0 +1,75 @@ +# Training Tricks + +MMSegmentation support following training tricks out of box. + +## Different Learning Rate(LR) for Backbone and Heads + +In semantic segmentation, some methods make the LR of heads larger than backbone to achieve better performance or faster convergence. + +In MMSegmentation, you may add following lines to config to make the LR of heads 10 times of backbone. + +```python +optim_wrapper=dict( + paramwise_cfg = dict( + custom_keys={ + 'head': dict(lr_mult=10.)})) +``` + +With this modification, the LR of any parameter group with `'head'` in name will be multiplied by 10. +You may refer to [MMEngine documentation](https://mmengine.readthedocs.io/en/latest/tutorials/optim_wrapper.html#advanced-usages) for further details. + +## Online Hard Example Mining (OHEM) + +We implement pixel sampler for training sampling, like OHEM (Online Hard Example Mining), +which is used for remove the "easy" examples for model training. +Here is an example config of training PSPNet with OHEM enabled. + +```python +_base_ = './pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model=dict( + decode_head=dict( + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=100000)) ) +``` + +In this way, only pixels with confidence score under 0.7 are used to train. And we keep at least 100000 pixels during training. If `thresh` is not specified, pixels of top `min_kept` loss will be selected. + +## Class Balanced Loss + +For dataset that is not balanced in classes distribution, you may change the loss weight of each class. +Here is an example for cityscapes dataset. + +```python +_base_ = './pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +model=dict( + decode_head=dict( + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0, + # DeepLab used this class weight for cityscapes + class_weight=[0.8373, 0.9180, 0.8660, 1.0345, 1.0166, 0.9969, 0.9754, + 1.0489, 0.8786, 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, + 1.0865, 1.0955, 1.0865, 1.1529, 1.0507]))) +``` + +`class_weight` will be passed into `CrossEntropyLoss` as `weight` argument. Please refer to [PyTorch Doc](https://pytorch.org/docs/stable/nn.html?highlight=crossentropy#torch.nn.CrossEntropyLoss) for details. + +## Multiple Losses + +For loss calculation, we support multiple losses training concurrently. Here is an example config of training `unet` on `DRIVE` dataset, whose loss function is `1:3` weighted sum of `CrossEntropyLoss` and `DiceLoss`: + +```python +_base_ = './fcn_unet_s5-d16_64x64_40k_drive.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ]), + auxiliary_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ]), +) +``` + +In this way, `loss_weight` and `loss_name` will be weight and name in training log of corresponding loss, respectively. + +Note: If you want this loss item to be included into the backward graph, `loss_` must be the prefix of the name. diff --git a/Seg_All_In_One_MMSeg/docs/en/advanced_guides/transforms.md b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/transforms.md new file mode 100644 index 0000000..68b1f44 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/advanced_guides/transforms.md @@ -0,0 +1,119 @@ +# Data Transforms + +In this tutorial, we introduce the design of transforms pipeline in MMSegmentation. + +The structure of this guide is as follows: + +- [Data Transforms](#data-transforms) + - [Design of Data pipelines](#design-of-data-pipelines) + - [Data loading](#data-loading) + - [Pre-processing](#pre-processing) + - [Formatting](#formatting) + +## Design of Data pipelines + +Following typical conventions, we use `Dataset` and `DataLoader` for data loading with multiple workers. `Dataset` returns a dict of data items corresponding the arguments of models' forward method. Since the data in semantic segmentation may not be the same size, we introduce a new `DataContainer` type in MMCV to help collect and distribute data of different size. See [here](https://github.com/open-mmlab/mmcv/blob/master/mmcv/parallel/data_container.py) for more details. + +In 1.x version of MMSegmentation, all data transformations are inherited from [`BaseTransform`](https://github.com/open-mmlab/mmcv/blob/2.x/mmcv/transforms/base.py#L6). + +The input and output types of transformations are both dict. A simple example is as follows: + +```python +>>> from mmseg.datasets.transforms import LoadAnnotations +>>> transforms = LoadAnnotations() +>>> img_path = './data/cityscapes/leftImg8bit/train/aachen/aachen_000000_000019_leftImg8bit.png.png' +>>> gt_path = './data/cityscapes/gtFine/train/aachen/aachen_000015_000019_gtFine_instanceTrainIds.png' +>>> results = dict( +>>> img_path=img_path, +>>> seg_map_path=gt_path, +>>> reduce_zero_label=False, +>>> seg_fields=[]) +>>> data_dict = transforms(results) +>>> print(data_dict.keys()) +dict_keys(['img_path', 'seg_map_path', 'reduce_zero_label', 'seg_fields', 'gt_seg_map']) +``` + +The data preparation pipeline and the dataset are decomposed. Usually a dataset defines how to process the annotations and a data pipeline defines all the steps to prepare a data dict. A pipeline consists of a sequence of operations. Each operation takes a dict as input and also outputs a dict for the next transform. + +The operations are categorized into data loading, pre-processing, formatting and test-time augmentation. + +Here is a pipeline example for PSPNet: + +```python +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +``` + +For each operation, we list the related dict fields that are `added`/`updated`/`removed`. Before pipelines, the information we can directly obtain from the datasets are `img_path` and `seg_map_path`. + +### Data loading + +`LoadImageFromFile`: Load an image from file. + +- add: `img`, `img_shape`, `ori_shape` + +`LoadAnnotations`: Load semantic segmentation maps provided by dataset. + +- add: `seg_fields`, `gt_seg_map` + +### Pre-processing + +`RandomResize`: Random resize image & segmentation map. + +- add: `scale`, `scale_factor`, `keep_ratio` +- update: `img`, `img_shape`, `gt_seg_map` + +`Resize`: Resize image & segmentation map. + +- add: `scale`, `scale_factor`, `keep_ratio` +- update: `img`, `gt_seg_map`, `img_shape` + +`RandomCrop`: Random crop image & segmentation map. + +- update: `img`, `gt_seg_map`, `img_shape` + +`RandomFlip`: Flip the image & segmentation map. + +- add: `flip`, `flip_direction` +- update: `img`, `gt_seg_map` + +`PhotoMetricDistortion`: Apply photometric distortion to image sequentially, every transformation is applied with a probability of 0.5. The position of random contrast is in second or second to last(mode 0 or 1 below, respectively). + +``` +1. random brightness +2. random contrast (mode 0) +3. convert color from BGR to HSV +4. random saturation +5. random hue +6. convert color from HSV to BGR +7. random contrast (mode 1) +``` + +- update: `img` + +### Formatting + +`PackSegInputs`: Pack the inputs data for the semantic segmentation. + +- add: `inputs`, `data_sample` +- remove: keys specified by `meta_keys` (merged into the metainfo of data_sample), all other keys diff --git a/Seg_All_In_One_MMSeg/docs/en/api.rst b/Seg_All_In_One_MMSeg/docs/en/api.rst new file mode 100644 index 0000000..2f1a25e --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/api.rst @@ -0,0 +1,95 @@ +mmseg.apis +-------------- +.. automodule:: mmseg.apis + :members: + +mmseg.datasets +-------------- + +datasets +^^^^^^^^^^ +.. automodule:: mmseg.datasets + :members: + +transforms +^^^^^^^^^^^^ +.. automodule:: mmseg.datasets.transforms + :members: + +mmseg.engine +-------------- + +hooks +^^^^^^^^^^ +.. automodule:: mmseg.engine.hooks + :members: + +optimizers +^^^^^^^^^^^^^^^ +.. automodule:: mmseg.engine.optimizers + :members: + +mmseg.evaluation +----------------- + +metrics +^^^^^^^^^^ +.. automodule:: mmseg.evaluation.metrics + :members: + +mmseg.models +-------------- + +backbones +^^^^^^^^^^^^^^^^^^ +.. automodule:: mmseg.models.backbones + :members: + +decode_heads +^^^^^^^^^^^^^^^ +.. automodule:: mmseg.models.decode_heads + :members: + +segmentors +^^^^^^^^^^ +.. automodule:: mmseg.models.segmentors + :members: + +losses +^^^^^^^^^^ +.. automodule:: mmseg.models.losses + :members: + +necks +^^^^^^^^^^^^ +.. automodule:: mmseg.models.necks + :members: + +utils +^^^^^^^^^^ +.. automodule:: mmseg.models.utils + :members: + + +mmseg.structures +-------------------- + +structures +^^^^^^^^^^^^^^^^^ +.. automodule:: mmseg.structures + :members: + +sampler +^^^^^^^^^^ +.. automodule:: mmseg.structures.sampler + :members: + +mmseg.visualization +-------------------- +.. automodule:: mmseg.visualization + :members: + +mmseg.utils +-------------- +.. automodule:: mmseg.utils + :members: diff --git a/Seg_All_In_One_MMSeg/docs/en/conf.py b/Seg_All_In_One_MMSeg/docs/en/conf.py new file mode 100644 index 0000000..e20aab1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/conf.py @@ -0,0 +1,133 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import subprocess +import sys + +import pytorch_sphinx_theme + +sys.path.insert(0, os.path.abspath('../../')) + +# -- Project information ----------------------------------------------------- + +project = 'MMSegmentation' +copyright = '2020-2021, OpenMMLab' +author = 'MMSegmentation Authors' +version_file = '../../mmseg/version.py' + + +def get_version(): + with open(version_file) as f: + exec(compile(f.read(), version_file, 'exec')) + return locals()['__version__'] + + +# The full version, including alpha/beta/rc tags +release = get_version() + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.viewcode', + 'sphinx_markdown_tables', 'sphinx_copybutton', 'myst_parser' +] + +autodoc_mock_imports = [ + 'matplotlib', 'pycocotools', 'mmseg.version', 'mmcv.ops' +] + +# Ignore >>> when copying code +copybutton_prompt_text = r'>>> |\.\.\. ' +copybutton_prompt_is_regexp = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +# The master toctree document. +master_doc = 'index' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# html_theme = 'sphinx_rtd_theme' +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] +html_theme_options = { + 'logo_url': + 'https://mmsegmentation.readthedocs.io/en/latest/', + 'menu': [ + { + 'name': + 'Tutorial', + 'url': + 'https://github.com/open-mmlab/mmsegmentation/blob/master/' + 'demo/MMSegmentation_Tutorial.ipynb' + }, + { + 'name': 'GitHub', + 'url': 'https://github.com/open-mmlab/mmsegmentation' + }, + { + 'name': + 'Upstream', + 'children': [ + { + 'name': 'MMCV', + 'url': 'https://github.com/open-mmlab/mmcv', + 'description': 'Foundational library for computer vision' + }, + ] + }, + ], + # Specify the language of shared menu + 'menu_lang': + 'en' +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = ['css/readthedocs.css'] + +# Enable ::: for my_st +myst_enable_extensions = ['colon_fence'] + +language = 'en' + + +def builder_inited_handler(app): + subprocess.run(['./stat.py']) + + +def setup(app): + app.connect('builder-inited', builder_inited_handler) diff --git a/Seg_All_In_One_MMSeg/docs/en/device/npu.md b/Seg_All_In_One_MMSeg/docs/en/device/npu.md new file mode 100644 index 0000000..a90d6ac --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/device/npu.md @@ -0,0 +1,39 @@ +# NPU (HUAWEI Ascend) + +## Usage + +Please refer to the [building documentation of MMCV](https://mmcv.readthedocs.io/en/latest/get_started/build.html#build-mmcv-full-on-ascend-npu-machine) to install MMCV on NPU devices + +Here we use 4 NPUs on your computer to train the model with the following command: + +```shell +bash tools/dist_train.sh configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py 4 +``` + +Also, you can use only one NPU to train the model with the following command: + +```shell +python tools/train.py configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py +``` + +## Models Results + +| Model | mIoU | Config | Download | +| :-----------------: | :---: | :----------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | +| [deeplabv3](<>) | 78.85 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024_20230115_205626.json) | +| [deeplabv3plus](<>) | 79.23 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024_20230116_043450.json) | +| [hrnet](<>) | 78.1 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/hrnet/fcn_hr18_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/fcn_hr18_4xb2-40k_cityscapes-512x1024_20230116_215821.json) | +| [fcn](<>) | 74.15 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/fcn_r50-d8_4xb2-40k_cityscapes-512x1024_20230111_083014.json) | +| [icnet](<>) | 69.25 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/icnet/icnet_r50-d8_4xb2-80k_cityscapes-832x832.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/icnet_r50-d8_4xb2-80k_cityscapes-832x832_20230119_002929.json) | +| [pspnet](<>) | 77.21 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024_20230114_042721.json) | +| [unet](<>) | 68.86 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024_20230129_224750.json) | +| [upernet](<>) | 77.81 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/upernet_r50_4xb2-40k_cityscapes-512x1024_20230129_014634.json) | +| [apcnet](<>) | 78.02 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024_20230209_212545.json) | +| [bisenetv1](<>) | 76.04 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024_20230201_023946.json) | +| [bisenetv2](<>) | 72.44 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/bisenetv2/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024_20230205_215606.json) | + +**Notes:** + +- If not specially marked, the results on NPU with amp are the basically same as those on the GPU with FP32. + +**All above models are provided by Huawei Ascend group.** diff --git a/Seg_All_In_One_MMSeg/docs/en/get_started.md b/Seg_All_In_One_MMSeg/docs/en/get_started.md new file mode 100644 index 0000000..3f957eb --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/get_started.md @@ -0,0 +1,211 @@ +# Get started: Install and Run MMSeg + +## Prerequisites + +In this section we demonstrate how to prepare an environment with PyTorch. + +MMSegmentation works on Linux, Windows and macOS. It requires Python 3.7+, CUDA 10.2+ and PyTorch 1.8+. + +**Note:** +If you are experienced with PyTorch and have already installed it, just skip this part and jump to the [next section](##installation). Otherwise, you can follow these steps for the preparation. + +**Step 0.** Download and install Miniconda from the [official website](https://docs.conda.io/en/latest/miniconda.html). + +**Step 1.** Create a conda environment and activate it. + +```shell +conda create --name openmmlab python=3.8 -y +conda activate openmmlab +``` + +**Step 2.** Install PyTorch following [official instructions](https://pytorch.org/get-started/locally/), e.g. + +On GPU platforms: + +```shell +conda install pytorch torchvision -c pytorch +``` + +On CPU platforms: + +```shell +conda install pytorch torchvision cpuonly -c pytorch +``` + +## Installation + +We recommend that users follow our best practices to install MMSegmentation. However, the whole process is highly customizable. See [Customize Installation](#customize-installation) section for more information. + +### Best Practices + +**Step 0.** Install [MMCV](https://github.com/open-mmlab/mmcv) using [MIM](https://github.com/open-mmlab/mim). + +```shell +pip install -U openmim +mim install mmengine +mim install "mmcv>=2.0.0" +``` + +**Step 1.** Install MMSegmentation. + +Case a: If you develop and run mmseg directly, install it from source: + +```shell +git clone -b main https://github.com/open-mmlab/mmsegmentation.git +cd mmsegmentation +pip install -v -e . +# '-v' means verbose, or more output +# '-e' means installing a project in editable mode, +# thus any local modifications made to the code will take effect without reinstallation. +``` + +Case b: If you use mmsegmentation as a dependency or third-party package, install it with pip: + +```shell +pip install "mmsegmentation>=1.0.0" +``` + +### Verify the installation + +To verify whether MMSegmentation is installed correctly, we provide some sample codes to run an inference demo. + +**Step 1.** We need to download config and checkpoint files. + +```shell +mim download mmsegmentation --config pspnet_r50-d8_4xb2-40k_cityscapes-512x1024 --dest . +``` + +The downloading will take several seconds or more, depending on your network environment. When it is done, you will find two files `pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py` and `pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth` in your current folder. + +**Step 2.** Verify the inference demo. + +Option (a). If you install mmsegmentation from source, just run the following command. + +```shell +python demo/image_demo.py demo/demo.png configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth --device cuda:0 --out-file result.jpg +``` + +You will see a new image `result.jpg` on your current folder, where segmentation masks are covered on all objects. + +Option (b). If you install mmsegmentation with pip, open you python interpreter and copy&paste the following codes. + +```python +from mmseg.apis import inference_model, init_model, show_result_pyplot +import mmcv + +config_file = 'pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +checkpoint_file = 'pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth' + +# build the model from a config file and a checkpoint file +model = init_model(config_file, checkpoint_file, device='cuda:0') + +# test a single image and show the results +img = 'demo/demo.png' # or img = mmcv.imread(img), which will only load it once +result = inference_model(model, img) +# visualize the results in a new window +show_result_pyplot(model, img, result, show=True) +# or save the visualization results to image files +# you can change the opacity of the painted segmentation map in (0, 1]. +show_result_pyplot(model, img, result, show=True, out_file='result.jpg', opacity=0.5) +# test a video and show the results +video = mmcv.VideoReader('video.mp4') +for frame in video: + result = inference_model(model, frame) + show_result_pyplot(model, frame, result, wait_time=1) +``` + +You can modify the code above to test a single image or a video, both of these options can verify that the installation was successful. + +### Customize Installation + +#### CUDA versions + +When installing PyTorch, you need to specify the version of CUDA. If you are not clear on which to choose, follow our recommendations: + +- For Ampere-based NVIDIA GPUs, such as GeForce 30 series and NVIDIA A100, CUDA 11 is a must. +- For older NVIDIA GPUs, CUDA 11 is backward compatible, but CUDA 10.2 offers better compatibility and is more lightweight. + +Please make sure the GPU driver satisfies the minimum version requirements. See [this table](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions) for more information. + +**Note:** +Installing CUDA runtime libraries is enough if you follow our best practices, because no CUDA code will be compiled locally. However if you hope to compile MMCV from source or develop other CUDA operators, you need to install the complete CUDA toolkit from NVIDIA's [website](https://developer.nvidia.com/cuda-downloads), and its version should match the CUDA version of PyTorch. i.e., the specified version of cudatoolkit in `conda install` command. + +#### Install MMCV without MIM + +MMCV contains C++ and CUDA extensions, thus depending on PyTorch in a complex way. MIM solves such dependencies automatically and makes the installation easier. However, it is not a must. + +To install MMCV with pip instead of MIM, please follow [MMCV installation guides](https://mmcv.readthedocs.io/en/latest/get_started/installation.html). This requires manually specifying a find-url based on PyTorch version and its CUDA version. + +For example, the following command install mmcv==2.0.0 built for PyTorch 1.10.x and CUDA 11.3. + +```shell +pip install mmcv==2.0.0 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html +``` + +#### Install on CPU-only platforms + +MMSegmentation can be built for CPU only environment. In CPU mode you can train (requires MMCV version >= 2.0.0), test or inference a model. + +#### Install on Google Colab + +[Google Colab](https://research.google.com/) usually has PyTorch installed, +thus we only need to install MMCV and MMSegmentation with the following commands. + +**Step 1.** Install [MMCV](https://github.com/open-mmlab/mmcv) using [MIM](https://github.com/open-mmlab/mim). + +```shell +!pip3 install openmim +!mim install mmengine +!mim install "mmcv>=2.0.0" +``` + +**Step 2.** Install MMSegmentation from the source. + +```shell +!git clone https://github.com/open-mmlab/mmsegmentation.git +%cd mmsegmentation +!git checkout main +!pip install -e . +``` + +**Step 3.** Verification. + +```python +import mmseg +print(mmseg.__version__) +# Example output: 1.0.0 +``` + +**Note:** +Within Jupyter, the exclamation mark `!` is used to call external executables and `%cd` is a [magic command](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-cd) to change the current working directory of Python. + +### Using MMSegmentation with Docker + +We provide a [Dockerfile](https://github.com/open-mmlab/mmsegmentation/blob/main/docker/Dockerfile) to build an image. Ensure that your [docker version](https://docs.docker.com/engine/install/) >=19.03. + +```shell +# build an image with PyTorch 1.11, CUDA 11.3 +# If you prefer other versions, just modified the Dockerfile +docker build -t mmsegmentation docker/ +``` + +Run it with + +```shell +docker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmsegmentation/data mmsegmentation +``` + +### Optional Dependencies + +#### Install GDAL + +[GDAL](https://gdal.org/) is a translator library for raster and vector geospatial data formats. Install GDAL to read complex formats and extremely large remote sensing images. + +```shell +conda install GDAL +``` + +## Trouble shooting + +If you have some issues during the installation, please first view the [FAQ](notes/faq.md) page. +You may [open an issue](https://github.com/open-mmlab/mmsegmentation/issues/new/choose) on GitHub if no solution is found. diff --git a/Seg_All_In_One_MMSeg/docs/en/index.rst b/Seg_All_In_One_MMSeg/docs/en/index.rst new file mode 100644 index 0000000..cdf8622 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/index.rst @@ -0,0 +1,63 @@ +Welcome to MMSegmentation's documentation! +=========================================== + +.. toctree:: + :maxdepth: 1 + :caption: Get Started + + overview.md + get_started.md + +.. toctree:: + :maxdepth: 2 + :caption: User Guides + + user_guides/index.rst + +.. toctree:: + :maxdepth: 2 + :caption: Advanced Guides + + advanced_guides/index.rst + +.. toctree:: + :maxdepth: 1 + :caption: Migration + + migration/index.rst + +.. toctree:: + :caption: API Reference + + api.rst + +.. toctree:: + :maxdepth: 1 + :caption: Model Zoo + + model_zoo.md + modelzoo_statistics.md + +.. toctree:: + :maxdepth: 1 + :caption: Notes + + notes/changelog.md + notes/faq.md + +.. toctree:: + :caption: Device Support + + device/npu.md + +.. toctree:: + :caption: Switch Language + + switch_language.md + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/Seg_All_In_One_MMSeg/docs/en/make.bat b/Seg_All_In_One_MMSeg/docs/en/make.bat new file mode 100644 index 0000000..922152e --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/Seg_All_In_One_MMSeg/docs/en/migration/index.rst b/Seg_All_In_One_MMSeg/docs/en/migration/index.rst new file mode 100644 index 0000000..2843bdb --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/migration/index.rst @@ -0,0 +1,8 @@ +Migration +*************** + +.. toctree:: + :maxdepth: 1 + + interface.md + package.md diff --git a/Seg_All_In_One_MMSeg/docs/en/migration/interface.md b/Seg_All_In_One_MMSeg/docs/en/migration/interface.md new file mode 100644 index 0000000..b83eeb9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/migration/interface.md @@ -0,0 +1,525 @@ +# Migration from MMSegmentation 0.x + +## Introduction + +This guide describes the fundamental differences between MMSegmentation 0.x and MMSegmentation 1.x in terms of behaviors and the APIs, and how these all relate to your migration journey. + +## New dependencies + +MMSegmentation 1.x depends on some new packages, you can prepare a new clean environment and install again according to the [installation tutorial](../get_started.md). + +Or install the below packages manually. + +1. [MMEngine](https://github.com/open-mmlab/mmengine): MMEngine is the core the OpenMMLab 2.0 architecture, and we splited many compentents unrelated to computer vision from MMCV to MMEngine. + +2. [MMCV](https://github.com/open-mmlab/mmcv): The computer vision package of OpenMMLab. This is not a new dependency, but you need to upgrade it to **2.0.0** version or above. + +3. [MMClassification](https://github.com/open-mmlab/mmclassification)(Optional): The image classification toolbox and benchmark of OpenMMLab. This is not a new dependency, but you need to upgrade it to **1.0.0rc6** version. + +4. [MMDetection](https://github.com/open-mmlab/mmdetection)(Optional): The object detection toolbox and benchmark of OpenMMLab. This is not a new dependency, but you need to upgrade it to **3.0.0** version or above. + +## Train launch + +The main improvement of OpenMMLab 2.0 is releasing MMEngine which provides universal and powerful runner for unified interfaces to launch training jobs. + +Compared with MMSeg0.x, MMSeg1.x provides fewer command line arguments in `tools/train.py` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FunctionOriginalNew
Loading pre-trained checkpoint--load_from=$CHECKPOINT--cfg-options load_from=$CHECKPOINT
Resuming Train from specific checkpoint--resume-from=$CHECKPOINT--resume=$CHECKPOINT
Resuming Train from the latest checkpoint--auto-resume--resume='auto'
Whether not to evaluate the checkpoint during training--no-validate--cfg-options val_cfg=None val_dataloader=None val_evaluator=None
Training device assignment--gpu-id=$DEVICE_ID-
Whether or not set different seeds for different ranks--diff-seed--cfg-options randomness.diff_rank_seed=True
Whether to set deterministic options for CUDNN backend--deterministic--cfg-options randomness.deterministic=True
+ +## Test launch + +Similar to training launch, there are only common arguments in tools/test.py of MMSegmentation 1.x. +Below is the difference in test scripts, +please refer to [this documentation](../user_guides/4_train_test.md) for more details about test launch. + + + + + + + + + + + + + + + + + + + + + + +
Function0.x1.x
Evaluation metrics--eval mIoU--cfg-options test_evaluator.type=IoUMetric
Whether to use test time augmentation--aug-test--tta
Whether save the output results without perform evaluation--format-only--cfg-options test_evaluator.format_only=True
+ +## Configuration file + +### Model settings + +No changes in `model.backbone`, `model.neck`, `model.decode_head` and `model.losses` fields. + +Add `model.data_preprocessor` field to configure the `DataPreProcessor`, including: + +- `mean` (Sequence, optional): The pixel mean of R, G, B channels. Defaults to None. + +- `std` (Sequence, optional): The pixel standard deviation of R, G, B channels. Defaults to None. + +- `size` (Sequence, optional): Fixed padding size. + +- `size_divisor` (int, optional): The divisor of padded size. + +- `seg_pad_val` (float, optional): Padding value of segmentation map. Default: 255. + +- `padding_mode` (str): Type of padding. Default: 'constant'. + + - constant: pads with a constant value, this value is specified with pad_val. + +- `bgr_to_rgb` (bool): whether to convert image from BGR to RGB.Defaults to False. + +- `rgb_to_bgr` (bool): whether to convert image from RGB to BGR. Defaults to False. + +**Note:** +Please refer [models documentation](../advanced_guides/models.md) for more details. + +### Dataset settings + +Changes in **data**: + +The original `data` field is split to `train_dataloader`, `val_dataloader` and `test_dataloader`. This allows us to configure them in fine-grained. For example, you can specify different sampler and batch size during training and test. +The `samples_per_gpu` is renamed to `batch_size`. +The `workers_per_gpu` is renamed to `num_workers`. + + + + + + + + + +
Original + +```python +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict(...), + val=dict(...), + test=dict(...), +) +``` + +
New + +```python +train_dataloader = dict( + batch_size=4, + num_workers=4, + dataset=dict(...), + sampler=dict(type='DefaultSampler', shuffle=True) # necessary +) + +val_dataloader = dict( + batch_size=4, + num_workers=4, + dataset=dict(...), + sampler=dict(type='DefaultSampler', shuffle=False) # necessary +) + +test_dataloader = val_dataloader +``` + +
+ +Changes in **pipeline** + +- The original formatting transforms **`ToTensor`**、**`ImageToTensor`**、**`Collect`** are combined as [`PackSegInputs`](mmseg.datasets.transforms.PackSegInputs) +- We don't recommend to do **`Normalize`** and **Pad** in the dataset pipeline. Please remove it from pipelines and set it in the `data_preprocessor` field. +- The original **`Resize`** in MMSeg 1.x has been changed to **`RandomResize`** and the input arguments `img_scale` is renamed to `scale`, and the default value of `keep_ratio` is modified to False. +- The original `test_pipeline` combines single-scale test and multi-scale test together, in MMSeg 1.x we separate it into `test_pipeline` and `tta_pipeline`. + +**Note:** +We move some work of data transforms to the data preprocessor, like normalization, see [the documentation](package.md) for more details. + +train_pipeline + + + + + + + + + +
Original + +```python +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='Resize', img_scale=(2560, 640), ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255), + dict(type='DefaultFormatBundle'), + dict(type='Collect', keys=['img', 'gt_semantic_seg']), +] +``` + +
New + +```python +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2560, 640), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +``` + +
+ +test_pipeline + + + + + + + + + +
Original + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(2560, 640), + # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75], + flip=False, + transforms=[ + dict(type='Resize', keep_ratio=True), + dict(type='RandomFlip'), + dict(type='Normalize', **img_norm_cfg), + dict(type='ImageToTensor', keys=['img']), + dict(type='Collect', keys=['img']), + ]) +] +``` + +
New + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2560, 640), keep_ratio=True), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +``` + +
+ +Changes in **`evaluation`**: + +- The **`evaluation`** field is split to `val_evaluator` and `test_evaluator`. And it won't support `interval` and `save_best` arguments. + The `interval` is moved to `train_cfg.val_interval`, and the `save_best` is moved to `default_hooks.checkpoint.save_best`. `pre_eval` has been removed. +- `'mIoU'` has been changed to `'IoUMetric'`. + + + + + + + + + +
Original + +```python +evaluation = dict(interval=2000, metric='mIoU', pre_eval=True) +``` + +
New + +```python +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator +``` + +
+ +### Optimizer and Schedule settings + +Changes in **`optimizer`** and **`optimizer_config`**: + +- Now we use `optim_wrapper` field to specify all configuration about the optimization process. And the `optimizer` is a sub field of `optim_wrapper` now. +- `paramwise_cfg` is also a sub field of `optim_wrapper`, instead of `optimizer`. +- `optimizer_config` is removed now, and all configurations of it are moved to `optim_wrapper`. +- `grad_clip` is renamed to `clip_grad`. + + + + + + + + + +
Original + +```python +optimizer = dict(type='AdamW', lr=0.0001, weight_decay=0.0005) +optimizer_config = dict(grad_clip=dict(max_norm=1, norm_type=2)) +``` + +
New + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0005), + clip_grad=dict(max_norm=1, norm_type=2)) +``` + +
+ +Changes in **`lr_config`**: + +- The `lr_config` field is removed and we use new `param_scheduler` to replace it. +- The `warmup` related arguments are removed, since we use schedulers combination to implement this functionality. + +The new schedulers combination mechanism is very flexible, and you can use it to design many kinds of learning rate / momentum curves. See [the tutorial](TODO) for more details. + + + + + + + + + +
Original + +```python +lr_config = dict( + policy='poly', + warmup='linear', + warmup_iters=1500, + warmup_ratio=1e-6, + power=1.0, + min_lr=0.0, + by_epoch=False) +``` + +
New + +```python +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] +``` + +
+ +Changes in **`runner`**: + +Most configuration in the original `runner` field is moved to `train_cfg`, `val_cfg` and `test_cfg`, which configure the loop in training, validation and test. + + + + + + + + + +
Original + +```python +runner = dict(type='IterBasedRunner', max_iters=20000) +``` + +
New + +```python +# The `val_interval` is the original `evaluation.interval`. +train_cfg = dict(type='IterBasedTrainLoop', max_iters=20000, val_interval=2000) +val_cfg = dict(type='ValLoop') # Use the default validation loop. +test_cfg = dict(type='TestLoop') # Use the default test loop. +``` + +
+ +In fact, in OpenMMLab 2.0, we introduced `Loop` to control the behaviors in training, validation and test. The functionalities of `Runner` are also changed. You can find more details of [runner tutorial](https://github.com/open-mmlab/mmengine/blob/main/docs/en/design/runner.md) in [MMEngine](https://github.com/open-mmlab/mmengine/). + +### Runtime settings + +Changes in **`checkpoint_config`** and **`log_config`**: + +The `checkpoint_config` are moved to `default_hooks.checkpoint` and the `log_config` are moved to `default_hooks.logger`. +And we move many hooks settings from the script code to the `default_hooks` field in the runtime configuration. + +```python +default_hooks = dict( + # record the time of every iterations. + timer=dict(type='IterTimerHook'), + + # print log every 50 iterations. + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + + # enable the parameter scheduler. + param_scheduler=dict(type='ParamSchedulerHook'), + + # save checkpoint every 2000 iterations. + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=2000), + + # set sampler seed in distributed environment. + sampler_seed=dict(type='DistSamplerSeedHook'), + + # validation results visualization. + visualization=dict(type='SegVisualizationHook')) +``` + +In addition, we split the original logger to logger and visualizer. The logger is used to record information and the visualizer is used to show the logger in different backends, like terminal and TensorBoard. + + + + + + + + + +
Original + +```python +log_config = dict( + interval=100, + hooks=[ + dict(type='TextLoggerHook'), + dict(type='TensorboardLoggerHook'), + ]) +``` + +
New + +```python +default_hooks = dict( + ... + logger=dict(type='LoggerHook', interval=100), +) +vis_backends = [dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +``` + +
+ +Changes in **`load_from`** and **`resume_from`**: + +- The `resume_from` is removed. And we use `resume` and `load_from` to replace it. + - If `resume=True` and `load_from` is **not None**, resume training from the checkpoint in `load_from`. + - If `resume=True` and `load_from` is **None**, try to resume from the latest checkpoint in the work directory. + - If `resume=False` and `load_from` is **not None**, only load the checkpoint, not resume training. + - If `resume=False` and `load_from` is **None**, do not load nor resume. + +Changes in **`dist_params`**: The `dist_params` field is a sub field of `env_cfg` now. And there are some new configurations in the `env_cfg`. + +```python +env_cfg = dict( + # whether to enable cudnn benchmark + cudnn_benchmark=False, + + # set multi process parameters + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + + # set distributed parameters + dist_cfg=dict(backend='nccl'), +) +``` + +Changes in **`workflow`**: `workflow` related functionalities are removed. + +New field **`visualizer`**: The visualizer is a new design in OpenMMLab 2.0 architecture. We use a visualizer instance in the runner to handle results & log visualization and save to different backends. See the [visualization tutorial](../user_guides/visualization.md) for more details. + +New field **`default_scope`**: The start point to search module for all registries. The `default_scope` in MMSegmentation is `mmseg`. See [the registry tutorial](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/registry.md) for more details. diff --git a/Seg_All_In_One_MMSeg/docs/en/migration/package.md b/Seg_All_In_One_MMSeg/docs/en/migration/package.md new file mode 100644 index 0000000..728e9a9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/migration/package.md @@ -0,0 +1,113 @@ +# Package structures changes + +This section is included if you are curious about what has changed between MMSeg 0.x and 1.x. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MMSegmentation 0.xMMSegmentation 1.x
mmseg.apimmseg.api
- mmseg.core+ mmseg.engine
mmseg.datasetsmmseg.datasets
mmseg.modelsmmseg.models
- mmseg.ops+ mmseg.structure
mmseg.utilsmmseg.utils
+ mmseg.evaluation
+ mmseg.registry
+ +## Removed packages + +### `mmseg.core` + +In OpenMMLab 2.0, `core` package has been removed. `hooks` and `optimizers` of `core` are moved in `mmseg.engine`, and `evaluation` in `core` is mmseg.evaluation currently. + +## `mmseg.ops` + +`ops` package included `encoding` and `wrappers`, which are moved in `mmseg.models.utils`. + +## Added packages + +### `mmseg.engine` + +OpenMMLab 2.0 adds a new foundational library for training deep learning, MMEngine. It servers as the training engine of all OpenMMLab codebases. +`engine` package of mmseg is some customized modules for semantic segmentation task, like `SegVisualizationHook` which works for visualizing segmentation mask. + +### `mmseg.structure` + +In OpenMMLab 2.0, we designed data structure for computer vision task, and in mmseg, we implements `SegDataSample` in `structure` package. + +### `mmseg.evaluation` + +We move all evaluation metric in `mmseg.evaluation`. + +### `mmseg.registry` + +We moved registry implementations for all kinds of modules in MMSegmentation in `mmseg.registry`. + +## Modified packages + +### `mmseg.apis` + +OpenMMLab 2.0 tries to support unified interface for multitasking of Computer Vision, and releases much stronger [`Runner`](https://github.com/open-mmlab/mmengine/blob/main/docs/en/design/runner.md), so MMSeg 1.x removed modules in `train.py` and `test.py` renamed `init_segmentor` to `init_model` and `inference_segmentor` to `inference_model`. + +Here is the changes of `mmseg.apis`: + +| Function | Changes | +| :-------------------: | :---------------------------------------------- | +| `init_segmentor` | Renamed to `init_model` | +| `inference_segmentor` | Rename to `inference_model` | +| `show_result_pyplot` | Implemented based on `SegLocalVisualizer` | +| `train_model` | Removed, use `runner.train` to train. | +| `multi_gpu_test` | Removed, use `runner.test` to test. | +| `single_gpu_test` | Removed, use `runner.test` to test. | +| `set_random_seed` | Removed, use `mmengine.runner.set_random_seed`. | +| `init_random_seed` | Removed, use `mmengine.dist.sync_random_seed`. | + +### `mmseg.datasets` + +OpenMMLab 2.0 defines the `BaseDataset` to function and interface of dataset, and MMSegmentation 1.x also follow this protocol and defines the `BaseSegDataset` inherited from `BaseDataset`. MMCV 2.x collects general data transforms for multiple tasks e.g. classification, detection, segmentation, so MMSegmentation 1.x uses these data transforms and removes them from mmseg.datasets. + +| Packages/Modules | Changes | +| :-------------------: | :------------------------------------------------------------------------------------------ | +| `mmseg.pipelines` | Moved in `mmcv.transforms` | +| `mmseg.sampler` | Moved in `mmengine.dataset.sampler` | +| `CustomDataset` | Renamed to `BaseSegDataset` and inherited from `BaseDataset` in MMEngine | +| `DefaultFormatBundle` | Replaced with `PackSegInputs` | +| `LoadImageFromFile` | Moved in `mmcv.transforms.LoadImageFromFile` | +| `LoadAnnotations` | Moved in `mmcv.transforms.LoadAnnotations` | +| `Resize` | Moved in `mmcv.transforms` and split into `Resize`, `RandomResize` and `RandomChoiceResize` | +| `RandomFlip` | Moved in `mmcv.transforms.RandomFlip` | +| `Pad` | Moved in `mmcv.transforms.Pad` | +| `Normalize` | Moved in `mmcv.transforms.Normalize` | +| `Compose` | Moved in `mmcv.transforms.Compose` | +| `ImageToTensor` | Moved in `mmcv.transforms.ImageToTensor` | + +### `mmseg.models` + +`models` has not changed a lot, just added the `encoding` and `wrappers` from previous `mmseg.ops` diff --git a/Seg_All_In_One_MMSeg/docs/en/model_zoo.md b/Seg_All_In_One_MMSeg/docs/en/model_zoo.md new file mode 100644 index 0000000..6717df6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/model_zoo.md @@ -0,0 +1,186 @@ +# Benchmark and Model Zoo + +## Common settings + +- We use distributed training with 4 GPUs by default. + +- All pytorch-style pretrained backbones on ImageNet are train by ourselves, with the same procedure in the [paper](https://arxiv.org/pdf/1812.01187.pdf). + Our ResNet style backbone are based on ResNetV1c variant, where the 7x7 conv in the input stem is replaced with three 3x3 convs. + +- For the consistency across different hardwares, we report the GPU memory as the maximum value of `torch.cuda.max_memory_allocated()` for all 4 GPUs with `torch.backends.cudnn.benchmark=False`. + Note that this value is usually less than what `nvidia-smi` shows. + +- We report the inference time as the total time of network forwarding and post-processing, excluding the data loading time. + Results are obtained with the script `tools/benchmark.py` which computes the average time on 200 images with `torch.backends.cudnn.benchmark=False`. + +- There are two inference modes in this framework. + + - `slide` mode: The `test_cfg` will be like `dict(mode='slide', crop_size=(769, 769), stride=(513, 513))`. + + In this mode, multiple patches will be cropped from input image, passed into network individually. + The crop size and stride between patches are specified by `crop_size` and `stride`. + The overlapping area will be merged by average + + - `whole` mode: The `test_cfg` will be like `dict(mode='whole')`. + + In this mode, the whole imaged will be passed into network directly. + + By default, we use `slide` inference for 769x769 trained model, `whole` inference for the rest. + +- For input size of 8x+1 (e.g. 769), `align_corner=True` is adopted as a traditional practice. + Otherwise, for input size of 8x (e.g. 512, 1024), `align_corner=False` is adopted. + +## Baselines + +### FCN + +Please refer to [FCN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn) for details. + +### PSPNet + +Please refer to [PSPNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet) for details. + +### DeepLabV3 + +Please refer to [DeepLabV3](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3) for details. + +### PSANet + +Please refer to [PSANet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet) for details. + +### DeepLabV3+ + +Please refer to [DeepLabV3+](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus) for details. + +### UPerNet + +Please refer to [UPerNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet) for details. + +### NonLocal Net + +Please refer to [NonLocal Net](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net) for details. + +### EncNet + +Please refer to [EncNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet) for details. + +### CCNet + +Please refer to [CCNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet) for details. + +### DANet + +Please refer to [DANet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet) for details. + +### APCNet + +Please refer to [APCNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet) for details. + +### HRNet + +Please refer to [HRNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet) for details. + +### GCNet + +Please refer to [GCNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet) for details. + +### DMNet + +Please refer to [DMNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet) for details. + +### ANN + +Please refer to [ANN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann) for details. + +### OCRNet + +Please refer to [OCRNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet) for details. + +### Fast-SCNN + +Please refer to [Fast-SCNN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastscnn) for details. + +### ResNeSt + +Please refer to [ResNeSt](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest) for details. + +### Semantic FPN + +Please refer to [Semantic FPN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/sem_fpn) for details. + +### PointRend + +Please refer to [PointRend](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/point_rend) for details. + +### MobileNetV2 + +Please refer to [MobileNetV2](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2) for details. + +### MobileNetV3 + +Please refer to [MobileNetV3](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v3) for details. + +### EMANet + +Please refer to [EMANet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/emanet) for details. + +### DNLNet + +Please refer to [DNLNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet) for details. + +### CGNet + +Please refer to [CGNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/cgnet) for details. + +### Mixed Precision (FP16) Training + +Please refer [Mixed Precision (FP16) Training on BiSeNetV2](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv2/bisenetv2_fcn_4xb4-160k_cityscapes-1024x1024.py) for details. + +### U-Net + +Please refer to [U-Net](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet/README.md) for details. + +### ViT + +Please refer to [ViT](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/README.md) for details. + +### Swin + +Please refer to [Swin](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/swin/README.md) for details. + +### SETR + +Please refer to [SETR](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr/README.md) for details. + +## Speed benchmark + +### Hardware + +- 8 NVIDIA Tesla V100 (32G) GPUs +- Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz + +### Software environment + +- Python 3.7 +- PyTorch 1.5 +- CUDA 10.1 +- CUDNN 7.6.03 +- NCCL 2.4.08 + +### Training speed + +For fair comparison, we benchmark all implementations with ResNet-101V1c. +The input size is fixed to 1024x512 with batch size 2. + +The training speed is reported as followed, in terms of second per iter (s/iter). The lower, the better. + +| Implementation | PSPNet (s/iter) | DeepLabV3+ (s/iter) | +| --------------------------------------------------------------------------- | --------------- | ------------------- | +| [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) | **0.83** | **0.85** | +| [SegmenTron](https://github.com/LikeLy-Journey/SegmenTron) | 0.84 | 0.85 | +| [CASILVision](https://github.com/CSAILVision/semantic-segmentation-pytorch) | 1.15 | N/A | +| [vedaseg](https://github.com/Media-Smart/vedaseg) | 0.95 | 1.25 | + +:::{note} +The output stride of DeepLabV3+ is 8. +::: diff --git a/Seg_All_In_One_MMSeg/docs/en/modelzoo_statistics.md b/Seg_All_In_One_MMSeg/docs/en/modelzoo_statistics.md new file mode 100644 index 0000000..e5e21a1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/modelzoo_statistics.md @@ -0,0 +1,102 @@ +# Model Zoo Statistics + +- Number of papers: 47 + + - ALGORITHM: 36 + - BACKBONE: 11 + +- Number of checkpoints: 612 + + - \[ALGORITHM\] [ANN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ann) (16 ckpts) + + - \[ALGORITHM\] [APCNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/apcnet) (12 ckpts) + + - \[BACKBONE\] [BEiT](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/beit) (2 ckpts) + + - \[ALGORITHM\] [BiSeNetV1](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv1) (11 ckpts) + + - \[ALGORITHM\] [BiSeNetV2](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/bisenetv2) (4 ckpts) + + - \[ALGORITHM\] [CCNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ccnet) (16 ckpts) + + - \[ALGORITHM\] [CGNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/cgnet) (2 ckpts) + + - \[BACKBONE\] [ConvNeXt](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/convnext) (6 ckpts) + + - \[ALGORITHM\] [DANet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/danet) (16 ckpts) + + - \[ALGORITHM\] [DeepLabV3](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3) (41 ckpts) + + - \[ALGORITHM\] [DeepLabV3+](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/deeplabv3plus) (42 ckpts) + + - \[ALGORITHM\] [DMNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dmnet) (12 ckpts) + + - \[ALGORITHM\] [DNLNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dnlnet) (12 ckpts) + + - \[ALGORITHM\] [DPT](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/dpt) (1 ckpts) + + - \[ALGORITHM\] [EMANet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/emanet) (4 ckpts) + + - \[ALGORITHM\] [EncNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/encnet) (12 ckpts) + + - \[ALGORITHM\] [ERFNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/erfnet) (1 ckpts) + + - \[ALGORITHM\] [FastFCN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastfcn) (12 ckpts) + + - \[ALGORITHM\] [Fast-SCNN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fastscnn) (1 ckpts) + + - \[ALGORITHM\] [FCN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn) (41 ckpts) + + - \[ALGORITHM\] [GCNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/gcnet) (16 ckpts) + + - \[BACKBONE\] [HRNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/hrnet) (37 ckpts) + + - \[ALGORITHM\] [ICNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/icnet) (12 ckpts) + + - \[ALGORITHM\] [ISANet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/isanet) (16 ckpts) + + - \[ALGORITHM\] [K-Net](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/knet) (7 ckpts) + + - \[BACKBONE\] [MAE](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mae) (1 ckpts) + + - \[ALGORITHM\] [Mask2Former](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mask2former) (13 ckpts) + + - \[ALGORITHM\] [MaskFormer](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/maskformer) (4 ckpts) + + - \[BACKBONE\] [MobileNetV2](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v2) (8 ckpts) + + - \[BACKBONE\] [MobileNetV3](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/mobilenet_v3) (4 ckpts) + + - \[ALGORITHM\] [NonLocal Net](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/nonlocal_net) (16 ckpts) + + - \[ALGORITHM\] [OCRNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/ocrnet) (24 ckpts) + + - \[ALGORITHM\] [PointRend](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/point_rend) (4 ckpts) + + - \[BACKBONE\] [PoolFormer](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/poolformer) (5 ckpts) + + - \[ALGORITHM\] [PSANet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/psanet) (16 ckpts) + + - \[ALGORITHM\] [PSPNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/pspnet) (54 ckpts) + + - \[BACKBONE\] [ResNeSt](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/resnest) (8 ckpts) + + - \[ALGORITHM\] [SegFormer](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segformer) (13 ckpts) + + - \[ALGORITHM\] [Segmenter](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/segmenter) (5 ckpts) + + - \[ALGORITHM\] [Semantic FPN](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/sem_fpn) (4 ckpts) + + - \[ALGORITHM\] [SETR](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/setr) (7 ckpts) + + - \[ALGORITHM\] [STDC](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/stdc) (4 ckpts) + + - \[BACKBONE\] [Swin Transformer](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/swin) (6 ckpts) + + - \[BACKBONE\] [Twins](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/twins) (12 ckpts) + + - \[ALGORITHM\] [UNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/unet) (25 ckpts) + + - \[ALGORITHM\] [UPerNet](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/upernet) (16 ckpts) + + - \[BACKBONE\] [Vision Transformer](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit) (11 ckpts) diff --git a/Seg_All_In_One_MMSeg/docs/en/notes/changelog.md b/Seg_All_In_One_MMSeg/docs/en/notes/changelog.md new file mode 100644 index 0000000..8eaf332 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/notes/changelog.md @@ -0,0 +1,506 @@ +# Changelog of v1.x + +## v1.2.2 (12/14/2023) + +### Bug Fixes + +- Fix bug in cross entropy loss ([#3457](https://github.com/open-mmlab/mmsegmentation/pull/3457)) +- Allow custom visualizer ([#3455](https://github.com/open-mmlab/mmsegmentation/pull/3455)) +- test resize with pad_shape ([#3421](https://github.com/open-mmlab/mmsegmentation/pull/3421)) +- add with-labels args to inferencer for visualization without labels ([#3466](https://github.com/open-mmlab/mmsegmentation/pull/3466)) + +### New Contributors + +- @okotaku made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3421 + +## v1.2.1 (10/17/2023) + +### Bug Fixes + +- Add bpe_simple_vocab_16e6.txt.gz to release ([#3386](https://github.com/open-mmlab/mmsegmentation/pull/3386)) +- Fix init api ([#3388](https://github.com/open-mmlab/mmsegmentation/pull/3388)) + +## v1.2.0 (10/12/2023) + +### Features + +- Support Side Adapter Network ([#3232](https://github.com/open-mmlab/mmsegmentation/pull/3232)) + +### Bug Fixes + +- fix wrong variables passing for `set_dataset_meta` ([#3348](https://github.com/open-mmlab/mmsegmentation/pull/3348)) + +### Documentation + +- add documentation of Finetune ONNX Models (MMSegemetation) Inference for NVIDIA Jetson ([#3372](https://github.com/open-mmlab/mmsegmentation/pull/3372)) + +## v1.1.2(09/20/2023) + +### Features + +- Add semantic label to the segmentation visualization results ([#3229](https://github.com/open-mmlab/mmsegmentation/pull/3229)) +- Support NYU depth estimation dataset ([#3269](https://github.com/open-mmlab/mmsegmentation/pull/3269)) +- Support Kullback-Leibler divergence Loss ([#3242](https://github.com/open-mmlab/mmsegmentation/pull/3242)) +- Support depth metrics ([#3297](https://github.com/open-mmlab/mmsegmentation/pull/3297)) +- Support Remote sensing inferencer ([#3131](https://github.com/open-mmlab/mmsegmentation/pull/3131)) +- Support VPD Depth Estimator ((#3321)(https://github.com/open-mmlab/mmsegmentation/pull/3321)) +- Support inference and visualization of VPD ([#3331](https://github.com/open-mmlab/mmsegmentation/pull/3331)) +- Support using the pytorch-grad-cam tool to visualize Class Activation Maps (CAM) ([#3324](https://github.com/open-mmlab/mmsegmentation/pull/3324)) + +### New projects + +- Support PP-Mobileseg ([#3239](https://github.com/open-mmlab/mmsegmentation/pull/3239)) +- Support CAT-Seg (CVPR'2023) ([#3098](https://github.com/open-mmlab/mmsegmentation/pull/3098)) +- Support Adabins ([#3257](https://github.com/open-mmlab/mmsegmentation/pull/3257)) +- Add pp_mobileseg onnx inference script ([#3268](https://github.com/open-mmlab/mmsegmentation/pull/3268)) + +### Bug Fixes + +- Fix module PascalContextDataset ([#3235](https://github.com/open-mmlab/mmsegmentation/pull/3235)) +- Fix one hot encoding for dice loss ([#3237](https://github.com/open-mmlab/mmsegmentation/pull/3237)) +- Fix confusion_matrix.py ([#3291](https://github.com/open-mmlab/mmsegmentation/pull/3291)) +- Fix inferencer visualization ([#3333](https://github.com/open-mmlab/mmsegmentation/pull/3333)) + +### Documentation + +- Translate doc for docs/zh_cn/user_guides/5_deployment.md ([#3281](https://github.com/open-mmlab/mmsegmentation/pull/3281)) + +### New Contributors + +- @angiecao made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3235 +- @yeedrag made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3237 +- @Yang-Changhui made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3239 +- @ooooo-create made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3261 +- @Ben-Louis made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3269 +- @crazysteeaam made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3284 +- @zen0no made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3242 +- @XiandongWang made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3291 +- @ZhaoQiiii made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3332 +- @zhen6618 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3324 + +## v1.1.1(07/24/2023) + +### Features + +- Add bdd100K datasets ([#3158](https://github.com/open-mmlab/mmsegmentation/pull/3158)) +- Remove batch inference assertion ([#3210](https://github.com/open-mmlab/mmsegmentation/pull/3210)) + +### Bug Fixes + +- Fix train map path for coco-stuff164k.py ([#3187](https://github.com/open-mmlab/mmsegmentation/pull/3187)) +- Fix mim search error ([#3194](https://github.com/open-mmlab/mmsegmentation/pull/3194)) +- Fix SegTTAModel with no attribute '\_gt_sem_seg' error ([#3152](https://github.com/open-mmlab/mmsegmentation/pull/3152)) +- Fix Albumentations default key mapping mismatch ([#3195](https://github.com/open-mmlab/mmsegmentation/pull/3195)) + +### New Contributors + +- @OliverGrace made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3187 +- @ZiAn-Su made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3152 +- @CastleDream made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3158 +- @coding-famer made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3174 +- @Alias-z made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3195 + +## v1.1.0(06/28/2023) + +## What's Changed + +### Features + +- Support albu transform ([#2943](https://github.com/open-mmlab/mmsegmentation/pull/2943)) +- Support DDRNet ([#2855](https://github.com/open-mmlab/mmsegmentation/pull/2855)) +- Add GDAL backend and Support LEVIR-CD Dataset ([#2903](https://github.com/open-mmlab/mmsegmentation/pull/2903)) +- Support DSDL Dataset ([#2925](https://github.com/open-mmlab/mmsegmentation/pull/2925)) +- huasdorff distance loss ([#2820](https://github.com/open-mmlab/mmsegmentation/pull/2820)) + +### New Projects + +- Support SAM inferencer ([#2897](https://github.com/open-mmlab/mmsegmentation/pull/2897)) +- Added a supported for Visual Attention Network (VAN) ([#2987](https://github.com/open-mmlab/mmsegmentation/pull/2987)) +- add GID dataset ([#3038](https://github.com/open-mmlab/mmsegmentation/pull/3038)) +- add Medical semantic seg dataset: Bactteria ([#2568](https://github.com/open-mmlab/mmsegmentation/pull/2568)) +- add Medical semantic seg dataset: Vampire ([#2633](https://github.com/open-mmlab/mmsegmentation/pull/2633)) +- add Medical semantic seg dataset: Ravir ([#2635](https://github.com/open-mmlab/mmsegmentation/pull/2635)) +- add Medical semantic seg dataset: Cranium ([#2675](https://github.com/open-mmlab/mmsegmentation/pull/2675)) +- add Medical semantic seg dataset: bccs ([#2861](https://github.com/open-mmlab/mmsegmentation/pull/2861)) +- add Medical semantic seg dataset: Gamma Task3 dataset ([#2695](https://github.com/open-mmlab/mmsegmentation/pull/2695)) +- add Medical semantic seg dataset: consep ([#2724](https://github.com/open-mmlab/mmsegmentation/pull/2724)) +- add Medical semantic seg dataset: breast_cancer_cell_seg dataset ([#2726](https://github.com/open-mmlab/mmsegmentation/pull/2726)) +- add Medical semantic seg dataset: chest_image_pneum dataset ([#2727](https://github.com/open-mmlab/mmsegmentation/pull/2727)) +- add Medical semantic seg dataset: conic2022 ([#2725](https://github.com/open-mmlab/mmsegmentation/pull/2725)) +- add Medical semantic seg dataset: dr_hagis ([#2729](https://github.com/open-mmlab/mmsegmentation/pull/2729)) +- add Medical semantic seg dataset: orvs ([#2728](https://github.com/open-mmlab/mmsegmentation/pull/2728)) +- add Medical semantic seg dataset: ISIC-2016 Task1 ([#2708](https://github.com/open-mmlab/mmsegmentation/pull/2708)) +- add Medical semantic seg dataset: ISIC-2017 Task1 ([#2709](https://github.com/open-mmlab/mmsegmentation/pull/2709)) +- add Medical semantic seg dataset: Kvasir seg ([#2677](https://github.com/open-mmlab/mmsegmentation/pull/2677)) +- add Medical semantic seg dataset: Kvasir seg aliyun ([#2678](https://github.com/open-mmlab/mmsegmentation/pull/2678)) +- add Medical semantic seg dataset: Rite ([#2680](https://github.com/open-mmlab/mmsegmentation/pull/2680)) +- add Medical semantic seg dataset: Fusc2021 ([#2682](https://github.com/open-mmlab/mmsegmentation/pull/2682)) +- add Medical semantic seg dataset: 2pm vessel ([#2685](https://github.com/open-mmlab/mmsegmentation/pull/2685)) +- add Medical semantic seg dataset: Pcam ([#2684](https://github.com/open-mmlab/mmsegmentation/pull/2684)) +- add Medical semantic seg dataset: Pannuke ([#2683](https://github.com/open-mmlab/mmsegmentation/pull/2683)) +- add Medical semantic seg dataset: Covid 19 ct cxr ([#2688](https://github.com/open-mmlab/mmsegmentation/pull/2688)) +- add Medical semantic seg dataset: Crass ([#2690](https://github.com/open-mmlab/mmsegmentation/pull/2690)) +- add Medical semantic seg dataset: Chest x ray images with pneumothorax masks ([#2687](https://github.com/open-mmlab/mmsegmentation/pull/2687)) + +### Enhancement + +- Robust mapping from image path to seg map path ([#3091](https://github.com/open-mmlab/mmsegmentation/pull/3091)) +- Change assertion logic inference cfg.model.test_cfg ([#3012](https://github.com/open-mmlab/mmsegmentation/pull/3012)) +- Refactor dice loss ([#3002](https://github.com/open-mmlab/mmsegmentation/pull/3002)) +- Update Dockerfile libgl1-mesa-dev ([#3095](https://github.com/open-mmlab/mmsegmentation/pull/3095)) +- Prevent passed `ann_file` from silently failing to load ([#2966](https://github.com/open-mmlab/mmsegmentation/pull/2966)) +- Update the translation of models documentation ([#2833](https://github.com/open-mmlab/mmsegmentation/pull/2833)) +- Add docs contents at README.md ([#3083](https://github.com/open-mmlab/mmsegmentation/pull/3083)) +- Enhance swin pretrained model loading ([#3097](https://github.com/open-mmlab/mmsegmentation/pull/3097)) + +### Bug Fixes + +- Handle case where device is neither CPU nor CUDA in HamHead ([#2868](https://github.com/open-mmlab/mmsegmentation/pull/2868)) +- Fix bugs when out_channels==1 ([#2911](https://github.com/open-mmlab/mmsegmentation/pull/2911)) +- Fix binary C=1 focal loss & dataset fileio ([#2935](https://github.com/open-mmlab/mmsegmentation/pull/2935)) +- Fix isaid dataset pre-processing tool ([#3010](https://github.com/open-mmlab/mmsegmentation/pull/3010)) +- Fix bug cannot use both '--tta' and '--out' while testing ([#3067](https://github.com/open-mmlab/mmsegmentation/pull/3067)) +- Fix inferencer ut ([#3117](https://github.com/open-mmlab/mmsegmentation/pull/3117)) +- Fix document ([#2863](https://github.com/open-mmlab/mmsegmentation/pull/2863), [#2896](https://github.com/open-mmlab/mmsegmentation/pull/2896), [#2919](https://github.com/open-mmlab/mmsegmentation/pull/2919), [#2951](https://github.com/open-mmlab/mmsegmentation/pull/2951), [#2970](https://github.com/open-mmlab/mmsegmentation/pull/2970), [#2961](https://github.com/open-mmlab/mmsegmentation/pull/2961), [#3042](https://github.com/open-mmlab/mmsegmentation/pull/3042), ) +- Fix squeeze error when N=1 and C=1 ([#2933](https://github.com/open-mmlab/mmsegmentation/pull/2933)) + +### New Contributors + +- @liu-mengyang made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2896 +- @likyoo made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2911 +- @1qh made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2902 +- @JoshuaChou2018 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2951 +- @jts250 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2833 +- @MGAMZ made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2970 +- @tianbinli made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2568 +- @Provable0816 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2633 +- @Zoulinx made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2903 +- @wufan-tb made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2925 +- @haruishi43 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2966 +- @Masaaki-75 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2675 +- @tang576225574 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2987 +- @Kedreamix made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3010 +- @nightrain01 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3067 +- @shigengtian made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3095 +- @SheffieldCao made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3097 +- @wangruohui made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3091 +- @LHamnett made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/3012 + +## v1.0.0(04/06/2023) + +### Highlights + +- Add Mapillary Vistas Datasets support to MMSegmentation Core Package ([#2576](https://github.com/open-mmlab/mmsegmentation/pull/2576)) +- Support PIDNet ([#2609](https://github.com/open-mmlab/mmsegmentation/pull/2609)) +- Support SegNeXt ([#2654](https://github.com/open-mmlab/mmsegmentation/pull/2654)) + +### Features + +- Support calculating FLOPs of segmentors ([#2706](https://github.com/open-mmlab/mmsegmentation/pull/2706)) +- Support multi-band image for Mosaic ([#2748](https://github.com/open-mmlab/mmsegmentation/pull/2748)) +- Support dump segment prediction ([#2712](https://github.com/open-mmlab/mmsegmentation/pull/2712)) + +### Bug fix + +- Fix format_result and fix prefix param in cityscape metric, and rename CitysMetric to CityscapesMetric ([#2660](https://github.com/open-mmlab/mmsegmentation/pull/2660)) +- Support input gt seg map is not 2D ([#2739](https://github.com/open-mmlab/mmsegmentation/pull/2739)) +- Fix accepting an unexpected argument `local-rank` in PyTorch 2.0 ([#2812](https://github.com/open-mmlab/mmsegmentation/pull/2812)) + +### Documentation + +- Add Chinese version of various documentation ([#2673](https://github.com/open-mmlab/mmsegmentation/pull/2673), [#2702](https://github.com/open-mmlab/mmsegmentation/pull/2702), [#2703](https://github.com/open-mmlab/mmsegmentation/pull/2703), [#2701](https://github.com/open-mmlab/mmsegmentation/pull/2701), [#2722](https://github.com/open-mmlab/mmsegmentation/pull/2722), [#2733](https://github.com/open-mmlab/mmsegmentation/pull/2733), [#2769](https://github.com/open-mmlab/mmsegmentation/pull/2769), [#2790](https://github.com/open-mmlab/mmsegmentation/pull/2790), [#2798](https://github.com/open-mmlab/mmsegmentation/pull/2798)) +- Update and refine various English documentation ([#2715](https://github.com/open-mmlab/mmsegmentation/pull/2715), [#2755](https://github.com/open-mmlab/mmsegmentation/pull/2755), [#2745](https://github.com/open-mmlab/mmsegmentation/pull/2745), [#2797](https://github.com/open-mmlab/mmsegmentation/pull/2797), [#2799](https://github.com/open-mmlab/mmsegmentation/pull/2799), [#2821](https://github.com/open-mmlab/mmsegmentation/pull/2821), [#2827](https://github.com/open-mmlab/mmsegmentation/pull/2827), [#2831](https://github.com/open-mmlab/mmsegmentation/pull/2831)) +- Add deeplabv3 model structure documentation ([#2426](https://github.com/open-mmlab/mmsegmentation/pull/2426)) +- Add custom metrics documentation ([#2799](https://github.com/open-mmlab/mmsegmentation/pull/2799)) +- Add faq in dev-1.x branch ([#2765](https://github.com/open-mmlab/mmsegmentation/pull/2765)) + +### New Contributors + +- @liuruiqiang made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2554 +- @wangjiangben-hw made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2569 +- @jinxianwei made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2557 +- @KKIEEK made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2747 +- @Renzhihan made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2765 + +## v1.0.0rc6(03/03/2023) + +### Highlights + +- Support MMSegInferencer ([#2413](https://github.com/open-mmlab/mmsegmentation/pull/2413), [#2658](https://github.com/open-mmlab/mmsegmentation/pull/2658)) +- Support REFUGE dataset ([#2554](https://github.com/open-mmlab/mmsegmentation/pull/2554)) + +### Features + +- Support auto import modules from registry ([#2481](https://github.com/open-mmlab/mmsegmentation/pull/2481)) +- Replace numpy ascontiguousarray with torch contiguous to speed-up ([#2604](https://github.com/open-mmlab/mmsegmentation/pull/2604)) +- Add browse_dataset.py tool ([#2649](https://github.com/open-mmlab/mmsegmentation/pull/2649)) + +### Bug fix + +- Rename and Fix bug of projects HieraSeg ([#2565](https://github.com/open-mmlab/mmsegmentation/pull/2565)) +- Add out_channels in `CascadeEncoderDecoder` and update OCRNet and MobileNet v2 results ([#2656](https://github.com/open-mmlab/mmsegmentation/pull/2656)) + +### Documentation + +- Add dataflow documentation of Chinese version ([#2652](https://github.com/open-mmlab/mmsegmentation/pull/2652)) +- Add custmized runtime documentation of English version ([#2533](https://github.com/open-mmlab/mmsegmentation/pull/2533)) +- Add documentation for visualizing feature map using wandb backend ([#2557](https://github.com/open-mmlab/mmsegmentation/pull/2557)) +- Add documentation for benchmark results on NPU (HUAWEI Ascend) ([#2569](https://github.com/open-mmlab/mmsegmentation/pull/2569), [#2596](https://github.com/open-mmlab/mmsegmentation/pull/2596), [#2610](https://github.com/open-mmlab/mmsegmentation/pull/2610)) +- Fix api name error in the migration doc ([#2601](https://github.com/open-mmlab/mmsegmentation/pull/2601)) +- Refine projects documentation ([#2586](https://github.com/open-mmlab/mmsegmentation/pull/2586)) +- Refine MMSegmentation documentation ([#2668](https://github.com/open-mmlab/mmsegmentation/pull/2668), [#2659](https://github.com/open-mmlab/mmsegmentation/pull/2659)) + +### New Contributors + +- @zccjjj made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2548 +- @liuruiqiang made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2554 +- @wangjiangben-hw made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2569 +- @jinxianwei made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2557 + +## v1.0.0rc5(02/01/2023) + +### Bug fix + +- Fix MaskFormer and Mask2Former when install mmdet from source ([#2532](https://github.com/open-mmlab/mmsegmentation/pull/2532)) +- Support new fileio interface in `MMCV>=2.0.0rc4` ([#2543](https://github.com/open-mmlab/mmsegmentation/pull/2543)) +- Fix ERFNet URL in dev-1.x branch ([#2537](https://github.com/open-mmlab/mmsegmentation/pull/2537)) +- Fix misleading `List[Tensor]` types ([#2546](https://github.com/open-mmlab/mmsegmentation/pull/2546)) +- Rename typing.py to typing_utils.py ([#2548](https://github.com/open-mmlab/mmsegmentation/pull/2548)) + +## v1.0.0rc4(01/30/2023) + +### Highlights + +- Support ISNet (ICCV'2021) in projects ([#2400](https://github.com/open-mmlab/mmsegmentation/pull/2400)) +- Support HSSN (CVPR'2022) in projects ([#2444](https://github.com/open-mmlab/mmsegmentation/pull/2444)) + +### Features + +- Add Gaussian Noise and Blur for biomedical data ([#2373](https://github.com/open-mmlab/mmsegmentation/pull/2373)) +- Add BioMedicalRandomGamma ([#2406](https://github.com/open-mmlab/mmsegmentation/pull/2406)) +- Add BioMedical3DPad ([#2383](https://github.com/open-mmlab/mmsegmentation/pull/2383)) +- Add BioMedical3DRandomFlip ([#2404](https://github.com/open-mmlab/mmsegmentation/pull/2404)) +- Add `gt_edge_map` field to SegDataSample ([#2466](https://github.com/open-mmlab/mmsegmentation/pull/2466)) +- Support synapse dataset ([#2432](https://github.com/open-mmlab/mmsegmentation/pull/2432), [#2465](https://github.com/open-mmlab/mmsegmentation/pull/2465)) +- Support Mapillary Vistas Dataset in projects ([#2484](https://github.com/open-mmlab/mmsegmentation/pull/2484)) +- Switch order of `reduce_zero_label` and applying `label_map` ([#2517](https://github.com/open-mmlab/mmsegmentation/pull/2517)) + +### Documentation + +- Add ZN Customized_runtime Doc ([#2502](https://github.com/open-mmlab/mmsegmentation/pull/2502)) +- Add EN datasets.md ([#2464](https://github.com/open-mmlab/mmsegmentation/pull/2464)) +- Fix minor typo in migration `package.md` ([#2518](https://github.com/open-mmlab/mmsegmentation/pull/2518)) + +### Bug fix + +- Fix incorrect `img_shape` value assignment in RandomCrop ([#2469](https://github.com/open-mmlab/mmsegmentation/pull/2469)) +- Fix inference api and support setting palette to SegLocalVisualizer ([#2475](https://github.com/open-mmlab/mmsegmentation/pull/2475)) +- Unfinished label conversion from `-1` to `255` ([#2516](https://github.com/open-mmlab/mmsegmentation/pull/2516)) + +### New Contributors + +- @blueyo0 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2373 +- @Fivethousand5k made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2406 +- @suyanzhou626 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2383 +- @unrealMJ made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2400 +- @Dominic23331 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2432 +- @AI-Tianlong made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2444 +- @morkovka1337 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2492 +- @Leeinsn made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2404 +- @siddancha made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/2516 + +## v1.0.0rc3(31/12/2022) + +### Highlights + +- Support test time augmentation ([#2184](https://github.com/open-mmlab/mmsegmentation/pull/2184)) +- Add 'Projects/' folder and the first example project ([#2412](https://github.com/open-mmlab/mmsegmentation/pull/2412)) + +### Features + +- Add Biomedical 3D array random crop transform ([#2378](https://github.com/open-mmlab/mmsegmentation/pull/2378)) + +### Documentation + +- Add Chinese version of config tutorial ([#2371](https://github.com/open-mmlab/mmsegmentation/pull/2371)) +- Add Chinese version of train & test tutorial ([#2355](https://github.com/open-mmlab/mmsegmentation/pull/2355)) +- Add Chinese version of overview ([(#2397)](https://github.com/open-mmlab/mmsegmentation/pull/2397))) +- Add Chinese version of get_started ([#2417](https://github.com/open-mmlab/mmsegmentation/pull/2417)) +- Add datasets in Chinese ([#2387](https://github.com/open-mmlab/mmsegmentation/pull/2387)) +- Add dataflow document ([#2403](https://github.com/open-mmlab/mmsegmentation/pull/2403)) +- Add pspnet model structure graph ([#2437](https://github.com/open-mmlab/mmsegmentation/pull/2437)) +- Update some content of engine Chinese documentation ([#2341](https://github.com/open-mmlab/mmsegmentation/pull/2341)) +- Update TTA to migration documentation ([#2335](https://github.com/open-mmlab/mmsegmentation/pull/2335)) + +### Bug fix + +- Remove dependency mmdet when do not use MaskFormerHead and MMDET_Mask2FormerHead ([#2448](https://github.com/open-mmlab/mmsegmentation/pull/2448)) + +### Enhancement + +- Add torch1.13 checking in CI ([#2402](https://github.com/open-mmlab/mmsegmentation/pull/2402)) +- Fix pytorch version for merge stage test ([#2449](https://github.com/open-mmlab/mmsegmentation/pull/2449)) + +## v1.0.0rc2(6/12/2022) + +### Highlights + +- Support MaskFormer ([#2215](https://github.com/open-mmlab/mmsegmentation/pull/2215)) +- Support Mask2Former ([#2255](https://github.com/open-mmlab/mmsegmentation/pull/2255)) + +### Features + +- Add ResizeShortestEdge transform ([#2339](https://github.com/open-mmlab/mmsegmentation/pull/2339)) +- Support padding in data pre-processor for model testing([#2290](https://github.com/open-mmlab/mmsegmentation/pull/2290)) +- Fix the problem of post-processing not removing padding ([#2367](https://github.com/open-mmlab/mmsegmentation/pull/2367)) + +### Bug fix + +- Fix links in README ([#2024](https://github.com/open-mmlab/mmsegmentation/pull/2024)) +- Fix swin load state_dict ([#2304](https://github.com/open-mmlab/mmsegmentation/pull/2304)) +- Fix typo of BaseSegDataset docstring ([#2322](https://github.com/open-mmlab/mmsegmentation/pull/2322)) +- Fix the bug in the visualization step ([#2326](https://github.com/open-mmlab/mmsegmentation/pull/2326)) +- Fix ignore class id from -1 to 255 in BaseSegDataset ([#2332](https://github.com/open-mmlab/mmsegmentation/pull/2332)) +- Fix KNet IterativeDecodeHead bug ([#2334](https://github.com/open-mmlab/mmsegmentation/pull/2334)) +- Add input argument for datasets ([#2379](https://github.com/open-mmlab/mmsegmentation/pull/2379)) +- Fix typo in warning on binary classification ([#2382](https://github.com/open-mmlab/mmsegmentation/pull/2382)) + +### Enhancement + +- Fix ci for 1.x ([#2011](https://github.com/open-mmlab/mmsegmentation/pull/2011), [#2019](https://github.com/open-mmlab/mmsegmentation/pull/2019)) +- Fix lint and pre-commit hook ([#2308](https://github.com/open-mmlab/mmsegmentation/pull/2308)) +- Add `data` string in .gitignore file in dev-1.x branch ([#2336](https://github.com/open-mmlab/mmsegmentation/pull/2336)) +- Make scipy as a default dependency in runtime ([#2362](https://github.com/open-mmlab/mmsegmentation/pull/2362)) +- Delete mmcls in runtime.txt ([#2368](https://github.com/open-mmlab/mmsegmentation/pull/2368)) + +### Documentation + +- Update configuration documentation ([#2048](https://github.com/open-mmlab/mmsegmentation/pull/2048)) +- Update inference documentation ([#2052](https://github.com/open-mmlab/mmsegmentation/pull/2052)) +- Update train test documentation ([#2061](https://github.com/open-mmlab/mmsegmentation/pull/2061)) +- Update get started documentatin ([#2148](https://github.com/open-mmlab/mmsegmentation/pull/2148)) +- Update transforms documentation ([#2088](https://github.com/open-mmlab/mmsegmentation/pull/2088)) +- Add MMEval projects like in README ([#2259](https://github.com/open-mmlab/mmsegmentation/pull/2259)) +- Translate the visualization.md ([#2298](https://github.com/open-mmlab/mmsegmentation/pull/2298)) + +## v1.0.0rc1 (2/11/2022) + +### Highlights + +- Support PoolFormer ([#2191](https://github.com/open-mmlab/mmsegmentation/pull/2191)) +- Add Decathlon dataset ([#2227](https://github.com/open-mmlab/mmsegmentation/pull/2227)) + +### Features + +- Add BioMedical data loading ([#2176](https://github.com/open-mmlab/mmsegmentation/pull/2176)) +- Add LIP dataset ([#2251](https://github.com/open-mmlab/mmsegmentation/pull/2251)) +- Add `GenerateEdge` data transform ([#2210](https://github.com/open-mmlab/mmsegmentation/pull/2210)) + +### Bug fix + +- Fix segmenter-vit-s_fcn config ([#2037](https://github.com/open-mmlab/mmsegmentation/pull/2037)) +- Fix binary segmentation ([#2101](https://github.com/open-mmlab/mmsegmentation/pull/2101)) +- Fix MMSegmentation colab demo ([#2089](https://github.com/open-mmlab/mmsegmentation/pull/2089)) +- Fix ResizeToMultiple transform ([#2185](https://github.com/open-mmlab/mmsegmentation/pull/2185)) +- Use SyncBN in mobilenet_v2 ([#2198](https://github.com/open-mmlab/mmsegmentation/pull/2198)) +- Fix typo in installation ([#2175](https://github.com/open-mmlab/mmsegmentation/pull/2175)) +- Fix typo in visualization.md ([#2116](https://github.com/open-mmlab/mmsegmentation/pull/2116)) + +### Enhancement + +- Add mim extras_requires in setup.py ([#2012](https://github.com/open-mmlab/mmsegmentation/pull/2012)) +- Fix CI ([#2029](https://github.com/open-mmlab/mmsegmentation/pull/2029)) +- Remove ops module ([#2063](https://github.com/open-mmlab/mmsegmentation/pull/2063)) +- Add pyupgrade pre-commit hook ([#2078](https://github.com/open-mmlab/mmsegmentation/pull/2078)) +- Add `out_file` in `add_datasample` of `SegLocalVisualizer` to directly save image ([#2090](https://github.com/open-mmlab/mmsegmentation/pull/2090)) +- Upgrade pre commit hooks ([#2154](https://github.com/open-mmlab/mmsegmentation/pull/2154)) +- Ignore test timm in CI when torch\<1.7 ([#2158](https://github.com/open-mmlab/mmsegmentation/pull/2158)) +- Update requirements ([#2186](https://github.com/open-mmlab/mmsegmentation/pull/2186)) +- Fix Windows platform CI ([#2202](https://github.com/open-mmlab/mmsegmentation/pull/2202)) + +### Documentation + +- Add `Overview` documentation ([#2042](https://github.com/open-mmlab/mmsegmentation/pull/2042)) +- Add `Evaluation` documentation ([#2077](https://github.com/open-mmlab/mmsegmentation/pull/2077)) +- Add `Migration` documentation ([#2066](https://github.com/open-mmlab/mmsegmentation/pull/2066)) +- Add `Structures` documentation ([#2070](https://github.com/open-mmlab/mmsegmentation/pull/2070)) +- Add `Structures` ZN documentation ([#2129](https://github.com/open-mmlab/mmsegmentation/pull/2129)) +- Add `Engine` ZN documentation ([#2157](https://github.com/open-mmlab/mmsegmentation/pull/2157)) +- Update `Prepare datasets` and `Visualization` doc ([#2054](https://github.com/open-mmlab/mmsegmentation/pull/2054)) +- Update `Models` documentation ([#2160](https://github.com/open-mmlab/mmsegmentation/pull/2160)) +- Update `Add New Modules` documentation ([#2067](https://github.com/open-mmlab/mmsegmentation/pull/2067)) +- Fix the installation commands in get_started.md ([#2174](https://github.com/open-mmlab/mmsegmentation/pull/2174)) +- Add MMYOLO to README.md ([#2220](https://github.com/open-mmlab/mmsegmentation/pull/2220)) + +## v1.0.0rc0 (31/8/2022) + +We are excited to announce the release of MMSegmentation 1.0.0rc0. +MMSeg 1.0.0rc0 is the first version of MMSegmentation 1.x, a part of the OpenMMLab 2.0 projects. +Built upon the new [training engine](https://github.com/open-mmlab/mmengine), +MMSeg 1.x unifies the interfaces of dataset, models, evaluation, and visualization with faster training and testing speed. + +### Highlights + +1. **New engines** MMSeg 1.x is based on [MMEngine](https://github.com/open-mmlab/mmengine), which provides a general and powerful runner that allows more flexible customizations and significantly simplifies the entrypoints of high-level interfaces. + +2. **Unified interfaces** As a part of the OpenMMLab 2.0 projects, MMSeg 1.x unifies and refactors the interfaces and internal logics of train, testing, datasets, models, evaluation, and visualization. All the OpenMMLab 2.0 projects share the same design in those interfaces and logics to allow the emergence of multi-task/modality algorithms. + +3. **Faster speed** We optimize the training and inference speed for common models. + +4. **New features**: + + - Support TverskyLoss function + +5. **More documentation and tutorials**. We add a bunch of documentation and tutorials to help users get started more smoothly. Read it [here](https://mmsegmentation.readthedocs.io/en/1.x/). + +### Breaking Changes + +We briefly list the major breaking changes here. +We will update the [migration guide](../migration.md) to provide complete details and migration instructions. + +#### Training and testing + +- MMSeg 1.x runs on PyTorch>=1.6. We have deprecated the support of PyTorch 1.5 to embrace the mixed precision training and other new features since PyTorch 1.6. Some models can still run on PyTorch 1.5, but the full functionality of MMSeg 1.x is not guaranteed. + +- MMSeg 1.x uses Runner in [MMEngine](https://github.com/open-mmlab/mmengine) rather than that in MMCV. The new Runner implements and unifies the building logic of dataset, model, evaluation, and visualizer. Therefore, MMSeg 1.x no longer maintains the building logics of those modules in `mmseg.train.apis` and `tools/train.py`. Those code have been migrated into [MMEngine](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py). Please refer to the [migration guide of Runner in MMEngine](https://mmengine.readthedocs.io/en/latest/migration/runner.html) for more details. + +- The Runner in MMEngine also supports testing and validation. The testing scripts are also simplified, which has similar logic as that in training scripts to build the runner. + +- The execution points of hooks in the new Runner have been enriched to allow more flexible customization. Please refer to the [migration guide of Hook in MMEngine](https://mmengine.readthedocs.io/en/latest/migration/hook.html) for more details. + +- Learning rate and momentum scheduling has been migrated from `Hook` to `Parameter Scheduler` in MMEngine. Please refer to the [migration guide of Parameter Scheduler in MMEngine](https://mmengine.readthedocs.io/en/latest/migration/param_scheduler.html) for more details. + +#### Configs + +- The [Runner in MMEngine](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py) uses a different config structures to ease the understanding of the components in runner. Users can read the [config example of mmseg](../user_guides/config.md) or refer to the [migration guide in MMEngine](https://mmengine.readthedocs.io/en/latest/migration/runner.html) for migration details. +- The file names of configs and models are also refactored to follow the new rules unified across OpenMMLab 2.0 projects. Please refer to the [user guides of config](../user_guides/1_config.md) for more details. + +#### Components + +- Dataset +- Data Transforms +- Model +- Evaluation +- Visualization + +### Improvements + +- Support mixed precision training of all the models. However, some models may got Nan results due to some numerical issues. We will update the documentation and list their results (accuracy of failure) of mixed precision training. + +### Bug Fixes + +- Fix several config file errors [#1994](https://github.com/open-mmlab/mmsegmentation/pull/1994) + +### New Features + +1. Support data structures and encapsulating `seg_logits` in data samples, which can be return from models to support more common evaluation metrics. + +### Ongoing changes + +1. Test-time augmentation: which is supported in MMSeg 0.x is not implemented in this version due to limited time slot. We will support it in the following releases with a new and simplified design. + +2. Inference interfaces: a unified inference interfaces will be supported in the future to ease the use of released models. + +3. Interfaces of useful tools that can be used in notebook: more useful tools that implemented in the `tools` directory will have their python interfaces so that they can be used through notebook and in downstream libraries. + +4. Documentation: we will add more design docs, tutorials, and migration guidance so that the community can deep dive into our new design, participate the future development, and smoothly migrate downstream libraries to MMSeg 1.x. diff --git a/Seg_All_In_One_MMSeg/docs/en/notes/changelog_v0.x.md b/Seg_All_In_One_MMSeg/docs/en/notes/changelog_v0.x.md new file mode 100644 index 0000000..d347a44 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/notes/changelog_v0.x.md @@ -0,0 +1,720 @@ +## Changelog + +### V0.24.1 (5/1/2022) + +**Bug Fixes** + +- Fix `LayerDecayOptimizerConstructor` for MAE training ([#1539](https://github.com/open-mmlab/mmsegmentation/pull/1539), [#1540](https://github.com/open-mmlab/mmsegmentation/pull/1540)) + +### V0.24.0 (4/29/2022) + +**Highlights** + +- Support MAE: Masked Autoencoders Are Scalable Vision Learners +- Support Resnet strikes back + +**New Features** + +- Support MAE: Masked Autoencoders Are Scalable Vision Learners ([#1307](https://github.com/open-mmlab/mmsegmentation/pull/1307), [#1523](https://github.com/open-mmlab/mmsegmentation/pull/1523)) +- Support Resnet strikes back ([#1390](https://github.com/open-mmlab/mmsegmentation/pull/1390)) +- Support extra dataloader settings in configs ([#1435](https://github.com/open-mmlab/mmsegmentation/pull/1435)) + +**Bug Fixes** + +- Fix input previous results for the last cascade_decode_head ([#1450](https://github.com/open-mmlab/mmsegmentation/pull/1450)) +- Fix validation loss logging ([#1494](https://github.com/open-mmlab/mmsegmentation/pull/1494)) +- Fix the bug in binary_cross_entropy ([1527](https://github.com/open-mmlab/mmsegmentation/pull/1527)) +- Support single channel prediction for Binary Cross Entropy Loss ([#1454](https://github.com/open-mmlab/mmsegmentation/pull/1454)) +- Fix potential bugs in accuracy.py ([1496](https://github.com/open-mmlab/mmsegmentation/pull/1496)) +- Avoid converting label ids twice by label map during evaluation ([1417](https://github.com/open-mmlab/mmsegmentation/pull/1417)) +- Fix bug about label_map ([1445](https://github.com/open-mmlab/mmsegmentation/pull/1445)) +- Fix image save path bug in Windows ([1423](https://github.com/open-mmlab/mmsegmentation/pull/1423)) +- Fix MMSegmentation Colab demo ([1501](https://github.com/open-mmlab/mmsegmentation/pull/1501), [1452](https://github.com/open-mmlab/mmsegmentation/pull/1452)) +- Migrate azure blob for beit checkpoints ([1503](https://github.com/open-mmlab/mmsegmentation/pull/1503)) +- Fix bug in `tools/analyse_logs.py` caused by wrong plot_iter in some cases ([1428](https://github.com/open-mmlab/mmsegmentation/pull/1428)) + +**Improvements** + +- Merge BEiT and ConvNext's LR decay optimizer constructors ([#1438](https://github.com/open-mmlab/mmsegmentation/pull/1438)) +- Register optimizer constructor with mmseg ([#1456](https://github.com/open-mmlab/mmsegmentation/pull/1456)) +- Refactor transformer encode layer in ViT and BEiT backbone ([#1481](https://github.com/open-mmlab/mmsegmentation/pull/1481)) +- Add `build_pos_embed` and `build_layers` for BEiT ([1517](https://github.com/open-mmlab/mmsegmentation/pull/1517)) +- Add `with_cp` to mit and vit ([1431](https://github.com/open-mmlab/mmsegmentation/pull/1431)) +- Fix inconsistent dtype of `seg_label` in stdc decode ([1463](https://github.com/open-mmlab/mmsegmentation/pull/1463)) +- Delete random seed for training in `dist_train.sh` ([1519](https://github.com/open-mmlab/mmsegmentation/pull/1519)) +- Revise high `workers_per_gpus` in config file ([#1506](https://github.com/open-mmlab/mmsegmentation/pull/1506)) +- Add GPG keys and del mmcv version in Dockerfile ([1534](https://github.com/open-mmlab/mmsegmentation/pull/1534)) +- Update checkpoint for model in deeplabv3plus ([#1487](https://github.com/open-mmlab/mmsegmentation/pull/1487)) +- Add `DistSamplerSeedHook` to set epoch number to dataloader when runner is `EpochBasedRunner` ([1449](https://github.com/open-mmlab/mmsegmentation/pull/1449)) +- Provide URLs of Swin Transformer pretrained models ([1389](https://github.com/open-mmlab/mmsegmentation/pull/1389)) +- Updating Dockerfiles From Docker Directory and `get_started.md` to reach latest stable version of Python, PyTorch and MMCV ([1446](https://github.com/open-mmlab/mmsegmentation/pull/1446)) + +**Documentation** + +- Add more clearly statement of CPU training/inference ([1518](https://github.com/open-mmlab/mmsegmentation/pull/1518)) + +**Contributors** + +- @jiangyitong made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1431 +- @kahkeng made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1447 +- @Nourollah made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1446 +- @androbaza made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1452 +- @Yzichen made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1445 +- @whu-pzhang made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1423 +- @panfeng-hover made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1417 +- @Johnson-Wang made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1496 +- @jere357 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1460 +- @mfernezir made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1494 +- @donglixp made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1503 +- @YuanLiuuuuuu made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1307 +- @Dawn-bin made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1527 + +### V0.23.0 (4/1/2022) + +**Highlights** + +- Support BEiT: BERT Pre-Training of Image Transformers +- Support K-Net: Towards Unified Image Segmentation +- Add `avg_non_ignore` of CELoss to support average loss over non-ignored elements +- Support dataset initialization with file client + +**New Features** + +- Support BEiT: BERT Pre-Training of Image Transformers ([#1404](https://github.com/open-mmlab/mmsegmentation/pull/1404)) +- Support K-Net: Towards Unified Image Segmentation ([#1289](https://github.com/open-mmlab/mmsegmentation/pull/1289)) +- Support dataset initialization with file client ([#1402](https://github.com/open-mmlab/mmsegmentation/pull/1402)) +- Add class name function for STARE datasets ([#1376](https://github.com/open-mmlab/mmsegmentation/pull/1376)) +- Support different seeds on different ranks when distributed training ([#1362](https://github.com/open-mmlab/mmsegmentation/pull/1362)) +- Add `nlc2nchw2nlc` and `nchw2nlc2nchw` to simplify tensor with different dimension operation ([#1249](https://github.com/open-mmlab/mmsegmentation/pull/1249)) + +**Improvements** + +- Synchronize random seed for distributed sampler ([#1411](https://github.com/open-mmlab/mmsegmentation/pull/1411)) +- Add script and documentation for multi-machine distributed training ([#1383](https://github.com/open-mmlab/mmsegmentation/pull/1383)) + +**Bug Fixes** + +- Add `avg_non_ignore` of CELoss to support average loss over non-ignored elements ([#1409](https://github.com/open-mmlab/mmsegmentation/pull/1409)) +- Fix some wrong URLs of models or logs in `./configs` ([#1336](https://github.com/open-mmlab/mmsegmentation/pull/1433)) +- Add title and color theme arguments to plot function in `tools/confusion_matrix.py` ([#1401](https://github.com/open-mmlab/mmsegmentation/pull/1401)) +- Fix outdated link in Colab demo ([#1392](https://github.com/open-mmlab/mmsegmentation/pull/1392)) +- Fix typos ([#1424](https://github.com/open-mmlab/mmsegmentation/pull/1424), [#1405](https://github.com/open-mmlab/mmsegmentation/pull/1405), [#1371](https://github.com/open-mmlab/mmsegmentation/pull/1371), [#1366](https://github.com/open-mmlab/mmsegmentation/pull/1366), [#1363](https://github.com/open-mmlab/mmsegmentation/pull/1363)) + +**Documentation** + +- Add FAQ document ([#1420](https://github.com/open-mmlab/mmsegmentation/pull/1420)) +- Fix the config name style description in official docs([#1414](https://github.com/open-mmlab/mmsegmentation/pull/1414)) + +**Contributors** + +- @kinglintianxia made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1371 +- @CCODING04 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1376 +- @mob5566 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1401 +- @xiongnemo made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1392 +- @Xiangxu-0103 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1405 + +### V0.22.1 (3/9/2022) + +**Bug Fixes** + +- Fix the ZeroDivisionError that all pixels in one image is ignored. ([#1336](https://github.com/open-mmlab/mmsegmentation/pull/1336)) + +**Improvements** + +- Provide URLs of STDC, Segmenter and Twins pretrained models ([#1272](https://github.com/open-mmlab/mmsegmentation/pull/1357)) + +### V0.22 (3/04/2022) + +**Highlights** + +- Support ConvNeXt: A ConvNet for the 2020s. Please use the latest MMClassification (0.21.0) to try it out. +- Support iSAID aerial Dataset. +- Officially Support inference on Windows OS. + +**New Features** + +- Support ConvNeXt: A ConvNet for the 2020s. ([#1216](https://github.com/open-mmlab/mmsegmentation/pull/1216)) +- Support iSAID aerial Dataset. ([#1115](https://github.com/open-mmlab/mmsegmentation/pull/1115) +- Generating and plotting confusion matrix. ([#1301](https://github.com/open-mmlab/mmsegmentation/pull/1301)) + +**Improvements** + +- Refactor 4 decoder heads (ASPP, FCN, PSP, UPer): Split forward function into `_forward_feature` and `cls_seg`. ([#1299](https://github.com/open-mmlab/mmsegmentation/pull/1299)) +- Add `min_size` arg in `Resize` to keep the shape after resize bigger than slide window. ([#1318](https://github.com/open-mmlab/mmsegmentation/pull/1318)) +- Revise pre-commit-hooks. ([#1315](https://github.com/open-mmlab/mmsegmentation/pull/1315)) +- Add win-ci. ([#1296](https://github.com/open-mmlab/mmsegmentation/pull/1296)) + +**Bug Fixes** + +- Fix `mlp_ratio` type in Swin Transformer. ([#1274](https://github.com/open-mmlab/mmsegmentation/pull/1274)) +- Fix path errors in `./demo` . ([#1269](https://github.com/open-mmlab/mmsegmentation/pull/1269)) +- Fix bug in conversion of potsdam. ([#1279](https://github.com/open-mmlab/mmsegmentation/pull/1279)) +- Make accuracy take into account `ignore_index`. ([#1259](https://github.com/open-mmlab/mmsegmentation/pull/1259)) +- Add Pytorch HardSwish assertion in unit test. ([#1294](https://github.com/open-mmlab/mmsegmentation/pull/1294)) +- Fix wrong palette value in vaihingen. ([#1292](https://github.com/open-mmlab/mmsegmentation/pull/1292)) +- Fix the bug that SETR cannot load pretrain. ([#1293](https://github.com/open-mmlab/mmsegmentation/pull/1293)) +- Update correct `In Collection` in metafile of each configs. ([#1239](https://github.com/open-mmlab/mmsegmentation/pull/1239)) +- Upload completed STDC models. ([#1332](https://github.com/open-mmlab/mmsegmentation/pull/1332)) +- Fix `DNLHead` exports onnx inference difference type Cast error. ([#1161](https://github.com/open-mmlab/mmsegmentation/pull/1332)) + +**Contributors** + +- @JiaYanhao made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1269 +- @andife made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1281 +- @SBCV made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1279 +- @HJoonKwon made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1259 +- @Tsingularity made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1290 +- @Waterman0524 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1115 +- @MeowZheng made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1315 +- @linfangjian01 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1318 + +### V0.21.1 (2/9/2022) + +**Bug Fixes** + +- Fix typos in docs. ([#1263](https://github.com/open-mmlab/mmsegmentation/pull/1263)) +- Fix repeating log by `setup_multi_processes`. ([#1267](https://github.com/open-mmlab/mmsegmentation/pull/1267)) +- Upgrade isort in pre-commit hook. ([#1270](https://github.com/open-mmlab/mmsegmentation/pull/1270)) + +**Improvements** + +- Use MMCV load_state_dict func in ViT/Swin. ([#1272](https://github.com/open-mmlab/mmsegmentation/pull/1272)) +- Add exception for PointRend for support CPU-only. ([#1271](https://github.com/open-mmlab/mmsegmentation/pull/1270)) + +### V0.21 (1/29/2022) + +**Highlights** + +- Officially Support CPUs training and inference, please use the latest MMCV (1.4.4) to try it out. +- Support Segmenter: Transformer for Semantic Segmentation (ICCV'2021). +- Support ISPRS Potsdam and Vaihingen Dataset. +- Add Mosaic transform and `MultiImageMixDataset` class in `dataset_wrappers`. + +**New Features** + +- Support Segmenter: Transformer for Semantic Segmentation (ICCV'2021) ([#955](https://github.com/open-mmlab/mmsegmentation/pull/955)) +- Support ISPRS Potsdam and Vaihingen Dataset ([#1097](https://github.com/open-mmlab/mmsegmentation/pull/1097), [#1171](https://github.com/open-mmlab/mmsegmentation/pull/1171)) +- Add segformer‘s benchmark on cityscapes ([#1155](https://github.com/open-mmlab/mmsegmentation/pull/1155)) +- Add auto resume ([#1172](https://github.com/open-mmlab/mmsegmentation/pull/1172)) +- Add Mosaic transform and `MultiImageMixDataset` class in `dataset_wrappers` ([#1093](https://github.com/open-mmlab/mmsegmentation/pull/1093), [#1105](https://github.com/open-mmlab/mmsegmentation/pull/1105)) +- Add log collector ([#1175](https://github.com/open-mmlab/mmsegmentation/pull/1175)) + +**Improvements** + +- New-style CPU training and inference ([#1251](https://github.com/open-mmlab/mmsegmentation/pull/1251)) +- Add UNet benchmark with multiple losses supervision ([#1143](https://github.com/open-mmlab/mmsegmentation/pull/1143)) + +**Bug Fixes** + +- Fix the model statistics in doc for readthedoc ([#1153](https://github.com/open-mmlab/mmsegmentation/pull/1153)) +- Set random seed for `palette` if not given ([#1152](https://github.com/open-mmlab/mmsegmentation/pull/1152)) +- Add `COCOStuffDataset` in `class_names.py` ([#1222](https://github.com/open-mmlab/mmsegmentation/pull/1222)) +- Fix bug in non-distributed multi-gpu training/testing ([#1247](https://github.com/open-mmlab/mmsegmentation/pull/1247)) +- Delete unnecessary lines of STDCHead ([#1231](https://github.com/open-mmlab/mmsegmentation/pull/1231)) + +**Contributors** + +- @jbwang1997 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1152 +- @BeaverCC made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1206 +- @Echo-minn made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1214 +- @rstrudel made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/955 + +### V0.20.2 (12/15/2021) + +**Bug Fixes** + +- Revise --option to --options to avoid BC-breaking. ([#1140](https://github.com/open-mmlab/mmsegmentation/pull/1140)) + +### V0.20.1 (12/14/2021) + +**Improvements** + +- Change options to cfg-options ([#1129](https://github.com/open-mmlab/mmsegmentation/pull/1129)) + +**Bug Fixes** + +- Fix `` in metafile. ([#1127](https://github.com/open-mmlab/mmsegmentation/pull/1127)) +- Fix correct `num_classes` of HRNet in `LoveDA` dataset ([#1136](https://github.com/open-mmlab/mmsegmentation/pull/1136)) + +### V0.20 (12/10/2021) + +**Highlights** + +- Support Twins ([#989](https://github.com/open-mmlab/mmsegmentation/pull/989)) +- Support a real-time segmentation model STDC ([#995](https://github.com/open-mmlab/mmsegmentation/pull/995)) +- Support a widely-used segmentation model in lane detection ERFNet ([#960](https://github.com/open-mmlab/mmsegmentation/pull/960)) +- Support A Remote Sensing Land-Cover Dataset LoveDA ([#1028](https://github.com/open-mmlab/mmsegmentation/pull/1028)) +- Support focal loss ([#1024](https://github.com/open-mmlab/mmsegmentation/pull/1024)) + +**New Features** + +- Support Twins ([#989](https://github.com/open-mmlab/mmsegmentation/pull/989)) +- Support a real-time segmentation model STDC ([#995](https://github.com/open-mmlab/mmsegmentation/pull/995)) +- Support a widely-used segmentation model in lane detection ERFNet ([#960](https://github.com/open-mmlab/mmsegmentation/pull/960)) +- Add SETR cityscapes benchmark ([#1087](https://github.com/open-mmlab/mmsegmentation/pull/1087)) +- Add BiSeNetV1 COCO-Stuff 164k benchmark ([#1019](https://github.com/open-mmlab/mmsegmentation/pull/1019)) +- Support focal loss ([#1024](https://github.com/open-mmlab/mmsegmentation/pull/1024)) +- Add Cutout transform ([#1022](https://github.com/open-mmlab/mmsegmentation/pull/1022)) + +**Improvements** + +- Set a random seed when the user does not set a seed ([#1039](https://github.com/open-mmlab/mmsegmentation/pull/1039)) +- Add CircleCI setup ([#1086](https://github.com/open-mmlab/mmsegmentation/pull/1086)) +- Skip CI on ignoring given paths ([#1078](https://github.com/open-mmlab/mmsegmentation/pull/1078)) +- Add abstract and image for every paper ([#1060](https://github.com/open-mmlab/mmsegmentation/pull/1060)) +- Create a symbolic link on windows ([#1090](https://github.com/open-mmlab/mmsegmentation/pull/1090)) +- Support video demo using trained model ([#1014](https://github.com/open-mmlab/mmsegmentation/pull/1014)) + +**Bug Fixes** + +- Fix incorrectly loading init_cfg or pretrained models of several transformer models ([#999](https://github.com/open-mmlab/mmsegmentation/pull/999), [#1069](https://github.com/open-mmlab/mmsegmentation/pull/1069), [#1102](https://github.com/open-mmlab/mmsegmentation/pull/1102)) +- Fix EfficientMultiheadAttention in SegFormer ([#1037](https://github.com/open-mmlab/mmsegmentation/pull/1037)) +- Remove `fp16` folder in `configs` ([#1031](https://github.com/open-mmlab/mmsegmentation/pull/1031)) +- Fix several typos in .yml file (Dice Metric [#1041](https://github.com/open-mmlab/mmsegmentation/pull/1041), ADE20K dataset [#1120](https://github.com/open-mmlab/mmsegmentation/pull/1120), Training Memory (GB) [#1083](https://github.com/open-mmlab/mmsegmentation/pull/1083)) +- Fix test error when using `--show-dir` ([#1091](https://github.com/open-mmlab/mmsegmentation/pull/1091)) +- Fix dist training infinite waiting issue ([#1035](https://github.com/open-mmlab/mmsegmentation/pull/1035)) +- Change the upper version of mmcv to 1.5.0 ([#1096](https://github.com/open-mmlab/mmsegmentation/pull/1096)) +- Fix symlink failure on Windows ([#1038](https://github.com/open-mmlab/mmsegmentation/pull/1038)) +- Cancel previous runs that are not completed ([#1118](https://github.com/open-mmlab/mmsegmentation/pull/1118)) +- Unified links of readthedocs in docs ([#1119](https://github.com/open-mmlab/mmsegmentation/pull/1119)) + +**Contributors** + +- @Junjue-Wang made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1028 +- @ddebby made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1066 +- @del-zhenwu made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1078 +- @KangBK0120 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1106 +- @zergzzlun made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1091 +- @fingertap made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1035 +- @irvingzhang0512 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1014 +- @littleSunlxy made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/989 +- @lkm2835 +- @RockeyCoss +- @MengzhangLI +- @Junjun2016 +- @xiexinch +- @xvjiarui + +### V0.19 (11/02/2021) + +**Highlights** + +- Support TIMMBackbone wrapper ([#998](https://github.com/open-mmlab/mmsegmentation/pull/998)) +- Support custom hook ([#428](https://github.com/open-mmlab/mmsegmentation/pull/428)) +- Add codespell pre-commit hook ([#920](https://github.com/open-mmlab/mmsegmentation/pull/920)) +- Add FastFCN benchmark on ADE20K ([#972](https://github.com/open-mmlab/mmsegmentation/pull/972)) + +**New Features** + +- Support TIMMBackbone wrapper ([#998](https://github.com/open-mmlab/mmsegmentation/pull/998)) +- Support custom hook ([#428](https://github.com/open-mmlab/mmsegmentation/pull/428)) +- Add FastFCN benchmark on ADE20K ([#972](https://github.com/open-mmlab/mmsegmentation/pull/972)) +- Add codespell pre-commit hook and fix typos ([#920](https://github.com/open-mmlab/mmsegmentation/pull/920)) + +**Improvements** + +- Make inputs & channels smaller in unittests ([#1004](https://github.com/open-mmlab/mmsegmentation/pull/1004)) +- Change `self.loss_decode` back to `dict` in Single Loss situation ([#1002](https://github.com/open-mmlab/mmsegmentation/pull/1002)) + +**Bug Fixes** + +- Fix typo in usage example ([#1003](https://github.com/open-mmlab/mmsegmentation/pull/1003)) +- Add contiguous after permutation in ViT ([#992](https://github.com/open-mmlab/mmsegmentation/pull/992)) +- Fix the invalid link ([#985](https://github.com/open-mmlab/mmsegmentation/pull/985)) +- Fix bug in CI with python 3.9 ([#994](https://github.com/open-mmlab/mmsegmentation/pull/994)) +- Fix bug when loading class name form file in custom dataset ([#923](https://github.com/open-mmlab/mmsegmentation/pull/923)) + +**Contributors** + +- @ShoupingShan made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/923 +- @RockeyCoss made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/954 +- @HarborYuan made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/992 +- @lkm2835 made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/1003 +- @gszh made their first contribution in https://github.com/open-mmlab/mmsegmentation/pull/428 +- @VVsssssk +- @MengzhangLI +- @Junjun2016 + +### V0.18 (10/07/2021) + +**Highlights** + +- Support three real-time segmentation models (ICNet [#884](https://github.com/open-mmlab/mmsegmentation/pull/884), BiSeNetV1 [#851](https://github.com/open-mmlab/mmsegmentation/pull/851), and BiSeNetV2 [#804](https://github.com/open-mmlab/mmsegmentation/pull/804)) +- Support one efficient segmentation model (FastFCN [#885](https://github.com/open-mmlab/mmsegmentation/pull/885)) +- Support one efficient non-local/self-attention based segmentation model (ISANet [#70](https://github.com/open-mmlab/mmsegmentation/pull/70)) +- Support COCO-Stuff 10k and 164k datasets ([#625](https://github.com/open-mmlab/mmsegmentation/pull/625)) +- Support evaluate concated dataset separately ([#833](https://github.com/open-mmlab/mmsegmentation/pull/833)) +- Support loading GT for evaluation from multi-file backend ([#867](https://github.com/open-mmlab/mmsegmentation/pull/867)) + +**New Features** + +- Support three real-time segmentation models (ICNet [#884](https://github.com/open-mmlab/mmsegmentation/pull/884), BiSeNetV1 [#851](https://github.com/open-mmlab/mmsegmentation/pull/851), and BiSeNetV2 [#804](https://github.com/open-mmlab/mmsegmentation/pull/804)) +- Support one efficient segmentation model (FastFCN [#885](https://github.com/open-mmlab/mmsegmentation/pull/885)) +- Support one efficient non-local/self-attention based segmentation model (ISANet [#70](https://github.com/open-mmlab/mmsegmentation/pull/70)) +- Support COCO-Stuff 10k and 164k datasets ([#625](https://github.com/open-mmlab/mmsegmentation/pull/625)) +- Support evaluate concated dataset separately ([#833](https://github.com/open-mmlab/mmsegmentation/pull/833)) + +**Improvements** + +- Support loading GT for evaluation from multi-file backend ([#867](https://github.com/open-mmlab/mmsegmentation/pull/867)) +- Auto-convert SyncBN to BN when training on DP automatly([#772](https://github.com/open-mmlab/mmsegmentation/pull/772)) +- Refactor Swin-Transformer ([#800](https://github.com/open-mmlab/mmsegmentation/pull/800)) + +**Bug Fixes** + +- Update mmcv installation in dockerfile ([#860](https://github.com/open-mmlab/mmsegmentation/pull/860)) +- Fix number of iteration bug when resuming checkpoint in distributed train ([#866](https://github.com/open-mmlab/mmsegmentation/pull/866)) +- Fix parsing parse in val_step ([#906](https://github.com/open-mmlab/mmsegmentation/pull/906)) + +### V0.17 (09/01/2021) + +**Highlights** + +- Support SegFormer +- Support DPT +- Support Dark Zurich and Nighttime Driving datasets +- Support progressive evaluation + +**New Features** + +- Support SegFormer ([#599](https://github.com/open-mmlab/mmsegmentation/pull/599)) +- Support DPT ([#605](https://github.com/open-mmlab/mmsegmentation/pull/605)) +- Support Dark Zurich and Nighttime Driving datasets ([#815](https://github.com/open-mmlab/mmsegmentation/pull/815)) +- Support progressive evaluation ([#709](https://github.com/open-mmlab/mmsegmentation/pull/709)) + +**Improvements** + +- Add multiscale_output interface and unittests for HRNet ([#830](https://github.com/open-mmlab/mmsegmentation/pull/830)) +- Support inherit cityscapes dataset ([#750](https://github.com/open-mmlab/mmsegmentation/pull/750)) +- Fix some typos in README.md ([#824](https://github.com/open-mmlab/mmsegmentation/pull/824)) +- Delete convert function and add instruction to ViT/Swin README.md ([#791](https://github.com/open-mmlab/mmsegmentation/pull/791)) +- Add vit/swin/mit convert weight scripts ([#783](https://github.com/open-mmlab/mmsegmentation/pull/783)) +- Add copyright files ([#796](https://github.com/open-mmlab/mmsegmentation/pull/796)) + +**Bug Fixes** + +- Fix invalid checkpoint link in inference_demo.ipynb ([#814](https://github.com/open-mmlab/mmsegmentation/pull/814)) +- Ensure that items in dataset have the same order across multi machine ([#780](https://github.com/open-mmlab/mmsegmentation/pull/780)) +- Fix the log error ([#766](https://github.com/open-mmlab/mmsegmentation/pull/766)) + +### V0.16 (08/04/2021) + +**Highlights** + +- Support PyTorch 1.9 +- Support SegFormer backbone MiT +- Support md2yml pre-commit hook +- Support frozen stage for HRNet + +**New Features** + +- Support SegFormer backbone MiT ([#594](https://github.com/open-mmlab/mmsegmentation/pull/594)) +- Support md2yml pre-commit hook ([#732](https://github.com/open-mmlab/mmsegmentation/pull/732)) +- Support mim ([#717](https://github.com/open-mmlab/mmsegmentation/pull/717)) +- Add mmseg2torchserve tool ([#552](https://github.com/open-mmlab/mmsegmentation/pull/552)) + +**Improvements** + +- Support hrnet frozen stage ([#743](https://github.com/open-mmlab/mmsegmentation/pull/743)) +- Add template of reimplementation questions ([#741](https://github.com/open-mmlab/mmsegmentation/pull/741)) +- Output pdf and epub formats for readthedocs ([#742](https://github.com/open-mmlab/mmsegmentation/pull/742)) +- Refine the docstring of ResNet ([#723](https://github.com/open-mmlab/mmsegmentation/pull/723)) +- Replace interpolate with resize ([#731](https://github.com/open-mmlab/mmsegmentation/pull/731)) +- Update resource limit ([#700](https://github.com/open-mmlab/mmsegmentation/pull/700)) +- Update config.md ([#678](https://github.com/open-mmlab/mmsegmentation/pull/678)) + +**Bug Fixes** + +- Fix ATTENTION registry ([#729](https://github.com/open-mmlab/mmsegmentation/pull/729)) +- Fix analyze log script ([#716](https://github.com/open-mmlab/mmsegmentation/pull/716)) +- Fix doc api display ([#725](https://github.com/open-mmlab/mmsegmentation/pull/725)) +- Fix patch_embed and pos_embed mismatch error ([#685](https://github.com/open-mmlab/mmsegmentation/pull/685)) +- Fix efficient test for multi-node ([#707](https://github.com/open-mmlab/mmsegmentation/pull/707)) +- Fix init_cfg in resnet backbone ([#697](https://github.com/open-mmlab/mmsegmentation/pull/697)) +- Fix efficient test bug ([#702](https://github.com/open-mmlab/mmsegmentation/pull/702)) +- Fix url error in config docs ([#680](https://github.com/open-mmlab/mmsegmentation/pull/680)) +- Fix mmcv installation ([#676](https://github.com/open-mmlab/mmsegmentation/pull/676)) +- Fix torch version ([#670](https://github.com/open-mmlab/mmsegmentation/pull/670)) + +**Contributors** + +@sshuair @xiexinch @Junjun2016 @mmeendez8 @xvjiarui @sennnnn @puhsu @BIGWangYuDong @keke1u @daavoo + +### V0.15 (07/04/2021) + +**Highlights** + +- Support ViT, SETR, and Swin-Transformer +- Add Chinese documentation +- Unified parameter initialization + +**Bug Fixes** + +- Fix typo and links ([#608](https://github.com/open-mmlab/mmsegmentation/pull/608)) +- Fix Dockerfile ([#607](https://github.com/open-mmlab/mmsegmentation/pull/607)) +- Fix ViT init ([#609](https://github.com/open-mmlab/mmsegmentation/pull/609)) +- Fix mmcv version compatible table ([#658](https://github.com/open-mmlab/mmsegmentation/pull/658)) +- Fix model links of DMNEt ([#660](https://github.com/open-mmlab/mmsegmentation/pull/660)) + +**New Features** + +- Support loading DeiT weights ([#538](https://github.com/open-mmlab/mmsegmentation/pull/538)) +- Support SETR ([#531](https://github.com/open-mmlab/mmsegmentation/pull/531), [#635](https://github.com/open-mmlab/mmsegmentation/pull/635)) +- Add config and models for ViT backbone with UperHead ([#520](https://github.com/open-mmlab/mmsegmentation/pull/531), [#635](https://github.com/open-mmlab/mmsegmentation/pull/520)) +- Support Swin-Transformer ([#511](https://github.com/open-mmlab/mmsegmentation/pull/511)) +- Add higher accuracy FastSCNN ([#606](https://github.com/open-mmlab/mmsegmentation/pull/606)) +- Add Chinese documentation ([#666](https://github.com/open-mmlab/mmsegmentation/pull/666)) + +**Improvements** + +- Unified parameter initialization ([#567](https://github.com/open-mmlab/mmsegmentation/pull/567)) +- Separate CUDA and CPU in github action CI ([#602](https://github.com/open-mmlab/mmsegmentation/pull/602)) +- Support persistent dataloader worker ([#646](https://github.com/open-mmlab/mmsegmentation/pull/646)) +- Update meta file fields ([#661](https://github.com/open-mmlab/mmsegmentation/pull/661), [#664](https://github.com/open-mmlab/mmsegmentation/pull/664)) + +### V0.14 (06/02/2021) + +**Highlights** + +- Support ONNX to TensorRT +- Support MIM + +**Bug Fixes** + +- Fix ONNX to TensorRT verify ([#547](https://github.com/open-mmlab/mmsegmentation/pull/547)) +- Fix save best for EvalHook ([#575](https://github.com/open-mmlab/mmsegmentation/pull/575)) + +**New Features** + +- Support loading DeiT weights ([#538](https://github.com/open-mmlab/mmsegmentation/pull/538)) +- Support ONNX to TensorRT ([#542](https://github.com/open-mmlab/mmsegmentation/pull/542)) +- Support output results for ADE20k ([#544](https://github.com/open-mmlab/mmsegmentation/pull/544)) +- Support MIM ([#549](https://github.com/open-mmlab/mmsegmentation/pull/549)) + +**Improvements** + +- Add option for ViT output shape ([#530](https://github.com/open-mmlab/mmsegmentation/pull/530)) +- Infer batch size using len(result) ([#532](https://github.com/open-mmlab/mmsegmentation/pull/532)) +- Add compatible table between MMSeg and MMCV ([#558](https://github.com/open-mmlab/mmsegmentation/pull/558)) + +### V0.13 (05/05/2021) + +**Highlights** + +- Support Pascal Context Class-59 dataset. +- Support Visual Transformer Backbone. +- Support mFscore metric. + +**Bug Fixes** + +- Fixed Colaboratory tutorial ([#451](https://github.com/open-mmlab/mmsegmentation/pull/451)) +- Fixed mIoU calculation range ([#471](https://github.com/open-mmlab/mmsegmentation/pull/471)) +- Fixed sem_fpn, unet README.md ([#492](https://github.com/open-mmlab/mmsegmentation/pull/492)) +- Fixed `num_classes` in FCN for Pascal Context 60-class dataset ([#488](https://github.com/open-mmlab/mmsegmentation/pull/488)) +- Fixed FP16 inference ([#497](https://github.com/open-mmlab/mmsegmentation/pull/497)) + +**New Features** + +- Support dynamic export and visualize to pytorch2onnx ([#463](https://github.com/open-mmlab/mmsegmentation/pull/463)) +- Support export to torchscript ([#469](https://github.com/open-mmlab/mmsegmentation/pull/469), [#499](https://github.com/open-mmlab/mmsegmentation/pull/499)) +- Support Pascal Context Class-59 dataset ([#459](https://github.com/open-mmlab/mmsegmentation/pull/459)) +- Support Visual Transformer backbone ([#465](https://github.com/open-mmlab/mmsegmentation/pull/465)) +- Support UpSample Neck ([#512](https://github.com/open-mmlab/mmsegmentation/pull/512)) +- Support mFscore metric ([#509](https://github.com/open-mmlab/mmsegmentation/pull/509)) + +**Improvements** + +- Add more CI for PyTorch ([#460](https://github.com/open-mmlab/mmsegmentation/pull/460)) +- Add print model graph args for tools/print_config.py ([#451](https://github.com/open-mmlab/mmsegmentation/pull/451)) +- Add cfg links in modelzoo README.md ([#468](https://github.com/open-mmlab/mmsegmentation/pull/469)) +- Add BaseSegmentor import to segmentors/__init__.py ([#495](https://github.com/open-mmlab/mmsegmentation/pull/495)) +- Add MMOCR, MMGeneration links ([#501](https://github.com/open-mmlab/mmsegmentation/pull/501), [#506](https://github.com/open-mmlab/mmsegmentation/pull/506)) +- Add Chinese QR code ([#506](https://github.com/open-mmlab/mmsegmentation/pull/506)) +- Use MMCV MODEL_REGISTRY ([#515](https://github.com/open-mmlab/mmsegmentation/pull/515)) +- Add ONNX testing tools ([#498](https://github.com/open-mmlab/mmsegmentation/pull/498)) +- Replace data_dict calling 'img' key to support MMDet3D ([#514](https://github.com/open-mmlab/mmsegmentation/pull/514)) +- Support reading class_weight from file in loss function ([#513](https://github.com/open-mmlab/mmsegmentation/pull/513)) +- Make tags as comment ([#505](https://github.com/open-mmlab/mmsegmentation/pull/505)) +- Use MMCV EvalHook ([#438](https://github.com/open-mmlab/mmsegmentation/pull/438)) + +### V0.12 (04/03/2021) + +**Highlights** + +- Support FCN-Dilate 6 model. +- Support Dice Loss. + +**Bug Fixes** + +- Fixed PhotoMetricDistortion Doc ([#388](https://github.com/open-mmlab/mmsegmentation/pull/388)) +- Fixed install scripts ([#399](https://github.com/open-mmlab/mmsegmentation/pull/399)) +- Fixed Dice Loss multi-class ([#417](https://github.com/open-mmlab/mmsegmentation/pull/417)) + +**New Features** + +- Support Dice Loss ([#396](https://github.com/open-mmlab/mmsegmentation/pull/396)) +- Add plot logs tool ([#426](https://github.com/open-mmlab/mmsegmentation/pull/426)) +- Add opacity option to show_result ([#425](https://github.com/open-mmlab/mmsegmentation/pull/425)) +- Speed up mIoU metric ([#430](https://github.com/open-mmlab/mmsegmentation/pull/430)) + +**Improvements** + +- Refactor unittest file structure ([#440](https://github.com/open-mmlab/mmsegmentation/pull/440)) +- Fix typos in the repo ([#449](https://github.com/open-mmlab/mmsegmentation/pull/449)) +- Include class-level metrics in the log ([#445](https://github.com/open-mmlab/mmsegmentation/pull/445)) + +### V0.11 (02/02/2021) + +**Highlights** + +- Support memory efficient test, add more UNet models. + +**Bug Fixes** + +- Fixed TTA resize scale ([#334](https://github.com/open-mmlab/mmsegmentation/pull/334)) +- Fixed CI for pip 20.3 ([#307](https://github.com/open-mmlab/mmsegmentation/pull/307)) +- Fixed ADE20k test ([#359](https://github.com/open-mmlab/mmsegmentation/pull/359)) + +**New Features** + +- Support memory efficient test ([#330](https://github.com/open-mmlab/mmsegmentation/pull/330)) +- Add more UNet benchmarks ([#324](https://github.com/open-mmlab/mmsegmentation/pull/324)) +- Support Lovasz Loss ([#351](https://github.com/open-mmlab/mmsegmentation/pull/351)) + +**Improvements** + +- Move train_cfg/test_cfg inside model ([#341](https://github.com/open-mmlab/mmsegmentation/pull/341)) + +### V0.10 (01/01/2021) + +**Highlights** + +- Support MobileNetV3, DMNet, APCNet. Add models of ResNet18V1b, ResNet18V1c, ResNet50V1b. + +**Bug Fixes** + +- Fixed CPU TTA ([#276](https://github.com/open-mmlab/mmsegmentation/pull/276)) +- Fixed CI for pip 20.3 ([#307](https://github.com/open-mmlab/mmsegmentation/pull/307)) + +**New Features** + +- Add ResNet18V1b, ResNet18V1c, ResNet50V1b, ResNet101V1b models ([#316](https://github.com/open-mmlab/mmsegmentation/pull/316)) +- Support MobileNetV3 ([#268](https://github.com/open-mmlab/mmsegmentation/pull/268)) +- Add 4 retinal vessel segmentation benchmark ([#315](https://github.com/open-mmlab/mmsegmentation/pull/315)) +- Support DMNet ([#313](https://github.com/open-mmlab/mmsegmentation/pull/313)) +- Support APCNet ([#299](https://github.com/open-mmlab/mmsegmentation/pull/299)) + +**Improvements** + +- Refactor Documentation page ([#311](https://github.com/open-mmlab/mmsegmentation/pull/311)) +- Support resize data augmentation according to original image size ([#291](https://github.com/open-mmlab/mmsegmentation/pull/291)) + +### V0.9 (30/11/2020) + +**Highlights** + +- Support 4 medical dataset, UNet and CGNet. + +**New Features** + +- Support RandomRotate transform ([#215](https://github.com/open-mmlab/mmsegmentation/pull/215), [#260](https://github.com/open-mmlab/mmsegmentation/pull/260)) +- Support RGB2Gray transform ([#227](https://github.com/open-mmlab/mmsegmentation/pull/227)) +- Support Rerange transform ([#228](https://github.com/open-mmlab/mmsegmentation/pull/228)) +- Support ignore_index for BCE loss ([#210](https://github.com/open-mmlab/mmsegmentation/pull/210)) +- Add modelzoo statistics ([#263](https://github.com/open-mmlab/mmsegmentation/pull/263)) +- Support Dice evaluation metric ([#225](https://github.com/open-mmlab/mmsegmentation/pull/225)) +- Support Adjust Gamma transform ([#232](https://github.com/open-mmlab/mmsegmentation/pull/232)) +- Support CLAHE transform ([#229](https://github.com/open-mmlab/mmsegmentation/pull/229)) + +**Bug Fixes** + +- Fixed detail API link ([#267](https://github.com/open-mmlab/mmsegmentation/pull/267)) + +### V0.8 (03/11/2020) + +**Highlights** + +- Support 4 medical dataset, UNet and CGNet. + +**New Features** + +- Support customize runner ([#118](https://github.com/open-mmlab/mmsegmentation/pull/118)) +- Support UNet ([#161](https://github.com/open-mmlab/mmsegmentation/pull/162)) +- Support CHASE_DB1, DRIVE, STARE, HRD ([#203](https://github.com/open-mmlab/mmsegmentation/pull/203)) +- Support CGNet ([#223](https://github.com/open-mmlab/mmsegmentation/pull/223)) + +### V0.7 (07/10/2020) + +**Highlights** + +- Support Pascal Context dataset and customizing class dataset. + +**Bug Fixes** + +- Fixed CPU inference ([#153](https://github.com/open-mmlab/mmsegmentation/pull/153)) + +**New Features** + +- Add DeepLab OS16 models ([#154](https://github.com/open-mmlab/mmsegmentation/pull/154)) +- Support Pascal Context dataset ([#133](https://github.com/open-mmlab/mmsegmentation/pull/133)) +- Support customizing dataset classes ([#71](https://github.com/open-mmlab/mmsegmentation/pull/71)) +- Support customizing dataset palette ([#157](https://github.com/open-mmlab/mmsegmentation/pull/157)) + +**Improvements** + +- Support 4D tensor output in ONNX ([#150](https://github.com/open-mmlab/mmsegmentation/pull/150)) +- Remove redundancies in ONNX export ([#160](https://github.com/open-mmlab/mmsegmentation/pull/160)) +- Migrate to MMCV DepthwiseSeparableConv ([#158](https://github.com/open-mmlab/mmsegmentation/pull/158)) +- Migrate to MMCV collect_env ([#137](https://github.com/open-mmlab/mmsegmentation/pull/137)) +- Use img_prefix and seg_prefix for loading ([#153](https://github.com/open-mmlab/mmsegmentation/pull/153)) + +### V0.6 (10/09/2020) + +**Highlights** + +- Support new methods i.e. MobileNetV2, EMANet, DNL, PointRend, Semantic FPN, Fast-SCNN, ResNeSt. + +**Bug Fixes** + +- Fixed sliding inference ONNX export ([#90](https://github.com/open-mmlab/mmsegmentation/pull/90)) + +**New Features** + +- Support MobileNet v2 ([#86](https://github.com/open-mmlab/mmsegmentation/pull/86)) +- Support EMANet ([#34](https://github.com/open-mmlab/mmsegmentation/pull/34)) +- Support DNL ([#37](https://github.com/open-mmlab/mmsegmentation/pull/37)) +- Support PointRend ([#109](https://github.com/open-mmlab/mmsegmentation/pull/109)) +- Support Semantic FPN ([#94](https://github.com/open-mmlab/mmsegmentation/pull/94)) +- Support Fast-SCNN ([#58](https://github.com/open-mmlab/mmsegmentation/pull/58)) +- Support ResNeSt backbone ([#47](https://github.com/open-mmlab/mmsegmentation/pull/47)) +- Support ONNX export (experimental) ([#12](https://github.com/open-mmlab/mmsegmentation/pull/12)) + +**Improvements** + +- Support Upsample in ONNX ([#100](https://github.com/open-mmlab/mmsegmentation/pull/100)) +- Support Windows install (experimental) ([#75](https://github.com/open-mmlab/mmsegmentation/pull/75)) +- Add more OCRNet results ([#20](https://github.com/open-mmlab/mmsegmentation/pull/20)) +- Add PyTorch 1.6 CI ([#64](https://github.com/open-mmlab/mmsegmentation/pull/64)) +- Get version and githash automatically ([#55](https://github.com/open-mmlab/mmsegmentation/pull/55)) + +### v0.5.1 (11/08/2020) + +**Highlights** + +- Support FP16 and more generalized OHEM + +**Bug Fixes** + +- Fixed Pascal VOC conversion script (#19) +- Fixed OHEM weight assign bug (#54) +- Fixed palette type when palette is not given (#27) + +**New Features** + +- Support FP16 (#21) +- Generalized OHEM (#54) + +**Improvements** + +- Add load-from flag (#33) +- Fixed training tricks doc about different learning rates of model (#26) diff --git a/Seg_All_In_One_MMSeg/docs/en/notes/faq.md b/Seg_All_In_One_MMSeg/docs/en/notes/faq.md new file mode 100644 index 0000000..a3f8099 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/notes/faq.md @@ -0,0 +1,132 @@ +# Frequently Asked Questions (FAQ) + +We list some common troubles faced by many users and their corresponding solutions here. Feel free to enrich the list if you find any frequent issues and have ways to help others to solve them. If the contents here do not cover your issue, please create an issue using the [provided templates](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/.github/ISSUE_TEMPLATE/error-report.md/) and make sure you fill in all required information in the template. + +## Installation + +The compatible MMSegmentation, MMCV and MMEngine versions are as below. Please install the correct versions of them to avoid installation issues. + +| MMSegmentation version | MMCV version | MMEngine version | MMClassification (optional) version | MMDetection (optional) version | +| :--------------------: | :----------------------------: | :---------------: | :---------------------------------: | :----------------------------: | +| dev-1.x branch | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| main branch | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.2.2 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.2.1 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.2.0 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.1.2 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.1.1 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.1.0 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.0.0 | mmcv >= 2.0.0rc4 | MMEngine >= 0.7.1 | mmcls==1.0.0rc6 | mmdet >= 3.0.0 | +| 1.0.0rc6 | mmcv >= 2.0.0rc4 | MMEngine >= 0.5.0 | mmcls>=1.0.0rc0 | mmdet >= 3.0.0rc6 | +| 1.0.0rc5 | mmcv >= 2.0.0rc4 | MMEngine >= 0.2.0 | mmcls>=1.0.0rc0 | mmdet>=3.0.0rc6 | +| 1.0.0rc4 | mmcv == 2.0.0rc3 | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | mmdet>=3.0.0rc4, \<=3.0.0rc5 | +| 1.0.0rc3 | mmcv == 2.0.0rc3 | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | mmdet>=3.0.0rc4, \<=3.0.0rc5 | +| 1.0.0rc2 | mmcv == 2.0.0rc3 | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | mmdet>=3.0.0rc4, \<=3.0.0rc5 | +| 1.0.0rc1 | mmcv >= 2.0.0rc1, \<=2.0.0rc3> | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | Not required | +| 1.0.0rc0 | mmcv >= 2.0.0rc1, \<=2.0.0rc3> | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | Not required | + +Notes: + +- MMClassification and MMDetatction are optional for MMSegmentation. If you didn't install them, `ConvNeXt` (required MMClassification) and MaskFormer, Mask2Former (required MMDetection) cannot be used. We recommend to install them with source code. Please refer to [MMClasssication](https://github.com/open-mmlab/mmclassification) and [MMDetection](https://github.com/open-mmlab/mmdetection) for more details about their installation. + +- To install MMSegmentation 0.x and master branch, please refer to [the faq 0.x document](https://mmsegmentation.readthedocs.io/en/latest/faq.html#installation) to check compatible versions of MMCV. + +- If you have installed an incompatible version of mmcv, please run `pip uninstall mmcv` to uninstall the installed mmcv first. If you have previously installed mmcv-full (which exists in OpenMMLab 1.x), please run `pip uninstall mmcv-full` to uninstall it. + +- If "No module named 'mmcv'" appears, please follow the steps below; + + 1. Use `pip uninstall mmcv` to uninstall the existing mmcv in the environment. + 2. Install the corresponding mmcv according to the [installation instructions](https://mmsegmentation.readthedocs.io/en/dev-1.x/get_started.html#best-practices). + +## How to know the number of GPUs needed to train the model + +- Infer from the name of the config file of the model. You can refer to the `Config Name Style` part of [Learn about Configs](../user_guides/1_config.md). For example, for config file with name `segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py`, `8xb1` means training the model corresponding to it needs 8 GPUs, and the batch size of each GPU is 1. +- Infer from the log file. Open the log file of the model and search `nGPU` in the file. The number of figures following `nGPU` is the number of GPUs needed to train the model. For instance, searching for `nGPU` in the log file yields the record `nGPU 0,1,2,3,4,5,6,7`, which indicates that eight GPUs are needed to train the model. + +## What does the auxiliary head mean + +Briefly, it is a deep supervision trick to improve the accuracy. In the training phase, `decode_head` is for decoding semantic segmentation output, `auxiliary_head` is just adding an auxiliary loss, the segmentation result produced by it has no impact to your model's result, it just works in training. You may read this [paper](https://arxiv.org/pdf/1612.01105.pdf) for more information. + +## How to output the segmentation mask image when running the test script + +In the test script, we provide `--out` argument to control whether output the painted images. Users might run the following command: + +```shell +python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} --out ${OUTPUT_DIR} +``` + +## How to handle binary segmentation task + +MMSegmentation uses `num_classes` and `out_channels` to control output of last layer `self.conv_seg`. More details could be found [here](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/mmseg/models/decode_heads/decode_head.py). + +`num_classes` should be the same as number of types of labels, in binary segmentation task, dataset only has two types of labels: foreground and background, so `num_classes=2`. `out_channels` controls the output channel of last layer of model, it usually equals to `num_classes`. +But in binary segmentation task, there are two solutions: + +- Set `out_channels=2`, using Cross Entropy Loss in training, using `F.softmax()` and `argmax()` to get prediction of each pixel in inference. + +- Set `out_channels=1`, using Binary Cross Entropy Loss in training, using `F.sigmoid()` and `threshold` to get prediction of each pixel in inference. `threshold` is set 0.3 as default. + +In summary, to implement binary segmentation methods users should modify below parameters in the `decode_head` and `auxiliary_head` configs. Here is a modification example of [pspnet_unet_s5-d16.py](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/_base_/models/pspnet_unet_s5-d16.py): + +- (1) `num_classes=2`, `out_channels=2` and `use_sigmoid=False` in `CrossEntropyLoss`. + +```python +decode_head=dict( + type='PSPHead', + in_channels=64, + in_index=4, + num_classes=2, + out_channels=2, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), +auxiliary_head=dict( + type='FCNHead', + in_channels=128, + in_index=3, + num_classes=2, + out_channels=2, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), +``` + +- (2) `num_classes=2`, `out_channels=1` and `use_sigmoid=True` in `CrossEntropyLoss`. + +```python +decode_head=dict( + type='PSPHead', + in_channels=64, + in_index=4, + num_classes=2, + out_channels=1, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), +auxiliary_head=dict( + type='FCNHead', + in_channels=128, + in_index=3, + num_classes=2, + out_channels=1, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.4)), +``` + +## Functionality of `reduce_zero_label` + +The parameter type of `reduce_zero_label` in dataset is Boolean, which is default to False. It is used to ignore the dataset label 0. The specific method is to change label 0 to 255, and subtract 1 from the corresponding number of all the remaining labels. At the same time, set 255 as ignore index in the decode head, which means that it will not participate in the loss calculation. + +Following is the specific implementation logic of `reduce_zero_label`: + +```python +if self.reduce_zero_label: + # avoid using underflow conversion + gt_semantic_seg[gt_semantic_seg == 0] = 255 + gt_semantic_seg = gt_semantic_seg - 1 + gt_semantic_seg[gt_semantic_seg == 254] = 255 +``` + +Whether your dataset needs to use `reduce_zero_label`, there are two types of situations: + +- On [Potsdam](https://github.com/open-mmlab/mmsegmentation/blob/1.x/docs/en/user_guides/2_dataset_prepare.md#isprs-potsdam) dataset, there are six classes: 0-Impervious surfaces, 1-Building, 2-Low vegetation, 3-Tree, 4-Car, 5-Clutter/background. However, this dataset provides two types of RGB labels, one with black pixels at the edges of the images, and the other without. For labels with black edges, in [dataset_converters.py](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/tools/dataset_converters/potsdam.py), it converts the black edges to label 0, and the other labels are 1-Impervious surfaces, 2-Building, 3-Low vegetation, 4-Tree, 5-Car, 6-Clutter/background. Therefore, in the dataset config [potsdam.py](https://github.com/open-mmlab/mmsegmentation/blob/ff95416c3b5ce8d62b9289f743531398efce534f/mmseg/datasets/potsdam.py#L23) `reduce_zero_label=True`。 If you are using labels without black edges, then there are only class 0-5 in the mask label. At this point, you should use `reduce_zero_label=False`. `reduce_zero_label` usage needs to be considered with your actual situation. +- On a dataset with class 0 as the background class, if you need to separate the background from the rest of your classes ultimately then you do not need to use `reduce_zero_label`, which in the dataset config settings should be `reduce_zero_label=False` + +**Note:** Please confirm the number of original classes in the dataset. If there are only two classes, you should not use `reduce_zero_label` which is `reduce_zero_label=False`. diff --git a/Seg_All_In_One_MMSeg/docs/en/overview.md b/Seg_All_In_One_MMSeg/docs/en/overview.md new file mode 100644 index 0000000..bbc0b8e --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/overview.md @@ -0,0 +1,85 @@ +# Overview + +This chapter introduces you to the framework of MMSegmentation, and the basic conception of semantic segmentation. It also provides links to detailed tutorials about MMSegmentation. + +## What is semantic segmentation? + +Semantic segmentation is the task of clustering parts of an image together that belong to the same object class. +It is a form of pixel-level prediction because each pixel in an image is classified according to a category. +Some example benchmarks for this task are [Cityscapes](https://www.cityscapes-dataset.com/benchmarks/), [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/) and [ADE20K](https://groups.csail.mit.edu/vision/datasets/ADE20K/). +Models are usually evaluated with the Mean Intersection-Over-Union (Mean IoU) and Pixel Accuracy metrics. + +## What is MMSegmentation? + +MMSegmentation is a toolbox that provides a framework for unified implementation and evaluation of semant +ic segmentation methods, +and contains high-quality implementations of popular semantic segmentation methods and datasets. + +MMSeg consists of 7 main parts including apis, structures, datasets, models, engine, evaluation and visualization. + +- **apis** provides high-level APIs for model inference. + +- **structures** provides segmentation data structure `SegDataSample`. + +- **datasets** supports various datasets for semantic segmentation. + + - **transforms** contains a lot of useful data augmentation transforms. + +- **models** is the most vital part for segmentors and contains different components of a segmentor. + + - **segmentors** defines all of the segmentation model classes. + - **data_preprocessors** works for preprocessing the input data of the model. + - **backbones** contains various backbone networks that transform an image to feature maps. + - **necks** contains various neck components that connect the backbone and heads. + - **decode_heads** contains various head components that take feature map as input and predict segmentation results. + - **losses** contains various loss functions. + +- **engine** is a part for runtime components that extends function of [MMEngine](https://github.com/open-mmlab/mmengine). + + - **optimizers** provides optimizers and optimizer wrappers. + - **hooks** provides various hooks of the runner. + +- **evaluation** provides different metrics for evaluating model performance. + +- **visualization** is for visualizing segmentation results. + +## How to use this documentation + +Here is a detailed step-by-step guide to learn more about MMSegmentation: + +1. For installation instructions, please see [get_started](getting_started.md). + +2. For beginners, MMSegmentation is the best place to start the journey of semantic segmentation + as there are many SOTA and classic segmentation [models](model_zoo.md), + and it is easier to carry out a segmentation task by plugging together building blocks and convenient high-level apis. + Refer to the tutorials below for the basic usage of MMSegmentation: + + - [Config](user_guides/1_config.md) + - [Dataset Preparation](user_guides/2_dataset_prepare.md) + - [Inference](user_guides/3_inference.md) + - [Train and Test](user_guides/4_train_test.md) + +3. If you would like to learn about the fundamental classes and features that make MMSegmentation work, + please refer to the tutorials below to dive deeper: + + - [Data flow](advanced_guides/data_flow.md) + - [Structures](advanced_guides/structures.md) + - [Models](advanced_guides/models.md) + - [Datasets](advanced_guides/datasets.md) + - [Evaluation](advanced_guides/evaluation.md) + +4. MMSegmentation also provide tutorials for customization and advanced research, + please refer to the below guides to build your own segmentation project: + + - [Add new models](advanced_guides/add_models.md) + - [Add new datasets](advanced_guides/add_datasets.md) + - [Add new transforms](advanced_guides/add_transforms.md) + - [Customize runtime](advanced_guides/customize_runtime.md) + +5. If you are more familiar with MMSegmentation v0.x, there is documentation about migration from MMSegmentation v0.x to v1.x + + - [migration](migration/index.rst) + +## References + +- [Paper with code](https://paperswithcode.com/task/semantic-segmentation/codeless#task-home) diff --git a/Seg_All_In_One_MMSeg/docs/en/stat.py b/Seg_All_In_One_MMSeg/docs/en/stat.py new file mode 100644 index 0000000..c458ee3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/stat.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# Copyright (c) OpenMMLab. All rights reserved. +import functools as func +import glob +import os.path as osp +import re + +import numpy as np + +url_prefix = 'https://github.com/open-mmlab/mmsegmentation/blob/master/' + +files = sorted(glob.glob('../../configs/*/README.md')) + +stats = [] +titles = [] +num_ckpts = 0 + +for f in files: + url = osp.dirname(f.replace('../../', url_prefix)) + + with open(f) as content_file: + content = content_file.read() + + title = content.split('\n')[0].replace('#', '').strip() + ckpts = { + x.lower().strip() + for x in re.findall(r'https?://download.*\.pth', content) + if 'mmsegmentation' in x + } + if len(ckpts) == 0: + continue + + _papertype = [ + x for x in re.findall(r'', content) + ] + assert len(_papertype) > 0 + papertype = _papertype[0] + + paper = {(papertype, title)} + + titles.append(title) + num_ckpts += len(ckpts) + statsmsg = f""" +\t* [{papertype}] [{title}]({url}) ({len(ckpts)} ckpts) +""" + stats.append((paper, ckpts, statsmsg)) + +allpapers = func.reduce(lambda a, b: a.union(b), [p for p, _, _ in stats]) +msglist = '\n'.join(x for _, _, x in stats) + +papertypes, papercounts = np.unique([t for t, _ in allpapers], + return_counts=True) +countstr = '\n'.join( + [f' - {t}: {c}' for t, c in zip(papertypes, papercounts)]) + +modelzoo = f""" +# Model Zoo Statistics + +* Number of papers: {len(set(titles))} +{countstr} + +* Number of checkpoints: {num_ckpts} +{msglist} +""" + +with open('modelzoo_statistics.md', 'w') as f: + f.write(modelzoo) diff --git a/Seg_All_In_One_MMSeg/docs/en/switch_language.md b/Seg_All_In_One_MMSeg/docs/en/switch_language.md new file mode 100644 index 0000000..f58efc4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/switch_language.md @@ -0,0 +1,3 @@ +##
English + +## 简体中文 diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/1_config.md b/Seg_All_In_One_MMSeg/docs/en/user_guides/1_config.md new file mode 100644 index 0000000..291c488 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/1_config.md @@ -0,0 +1,588 @@ +# Tutorial 1: Learn about Configs + +We incorporate modular and inheritance design into our config system, which is convenient to conduct various experiments. +If you wish to inspect the config file, you may run `python tools/misc/print_config.py /PATH/TO/CONFIG` to see the complete config. +You may also pass `--cfg-options xxx.yyy=zzz` to see updated config. + +## Config File Structure + +There are 4 basic component types under `config/_base_`, datasets, models, schedules, default_runtime. +Many methods could be easily constructed with one of each like DeepLabV3, PSPNet. +The configs that are composed by components from `_base_` are called _primitive_. + +For all configs under the same folder, it is recommended to have only **one** _primitive_ config. All other configs should inherit from the _primitive_ config. In this way, the maximum of inheritance level is 3. + +For easy understanding, we recommend contributors to inherit from existing methods. +For example, if some modification is made base on DeepLabV3, user may first inherit the basic DeepLabV3 structure by specifying `_base_ = ../deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py`, then modify the necessary fields in the config files. + +If you are building an entirely new method that does not share the structure with any of the existing methods, you may create a folder `xxxnet` under `configs`, + +Please refer to [mmengine](https://mmengine.readthedocs.io/en/latest/tutorials/config.html) for detailed documentation. + +## Config Name Style + +We follow the below style to name config files. Contributors are advised to follow the same style. + +```text +{algorithm name}_{model component names [component1]_[component2]_[...]}_{training settings}_{training dataset information}_{testing dataset information} +``` + +The file name is divided to five parts. All parts and components are connected with `_` and words of each part or component should be connected with `-`. + +- `{algorithm name}`: The name of the algorithm, such as `deeplabv3`, `pspnet`, etc. +- `{model component names}`: Names of the components used in the algorithm such as backbone, head, etc. For example, `r50-d8` means using ResNet50 backbone and use output of backbone is 8 times downsampling as input. +- `{training settings}`: Information of training settings such as batch size, augmentations, loss, learning rate scheduler, and epochs/iterations. For example: `4xb4-ce-linearlr-40K` means using 4-gpus x 4-images-per-gpu, CrossEntropy loss, Linear learning rate scheduler, and train 40K iterations. + Some abbreviations: + - `{gpu x batch_per_gpu}`: GPUs and samples per GPU. `bN` indicates N batch size per GPU. E.g. `8xb2` is the short term of 8-gpus x 2-images-per-gpu. And `4xb4` is used by default if not mentioned. + - `{schedule}`: training schedule, options are `20k`, `40k`, etc. `20k` and `40k` means 20000 iterations and 40000 iterations respectively. +- `{training dataset information}`: Training dataset names like `cityscapes`, `ade20k`, etc, and input resolutions. For example: `cityscapes-768x768` means training on `cityscapes` dataset and the input shape is `768x768`. +- `{testing dataset information}` (optional): Testing dataset name for models trained on one dataset but tested on another. If not mentioned, it means the model was trained and tested on the same dataset type. + +## An Example of PSPNet + +To help the users have a basic idea of a complete config and the modules in a modern semantic segmentation system, +we make brief comments on the config of PSPNet using ResNet50V1c as the following. +For more detailed usage and the corresponding alternative for each module, please refer to the API documentation. + +```python +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] # base config file which we build new config file on. +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +``` + +`_base_/models/pspnet_r50-d8.py` is a basic model cfg file for PSPNet using ResNet50V1c + +```python +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) # Segmentation usually uses SyncBN +data_preprocessor = dict( # The config of data preprocessor, usually includes image normalization and augmentation. + type='SegDataPreProcessor', # The type of data preprocessor. + mean=[123.675, 116.28, 103.53], # Mean values used for normalizing the input images. + std=[58.395, 57.12, 57.375], # Standard variance used for normalizing the input images. + bgr_to_rgb=True, # Whether to convert image from BGR to RGB. + pad_val=0, # Padding value of image. + seg_pad_val=255) # Padding value of segmentation map. +model = dict( + type='EncoderDecoder', # Name of segmentor + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', # The ImageNet pretrained backbone to be loaded + backbone=dict( + type='ResNetV1c', # The type of backbone. Please refer to mmseg/models/backbones/resnet.py for details. + depth=50, # Depth of backbone. Normally 50, 101 are used. + num_stages=4, # Number of stages of backbone. + out_indices=(0, 1, 2, 3), # The index of output feature maps produced in each stages. + dilations=(1, 1, 2, 4), # The dilation rate of each layer. + strides=(1, 2, 1, 1), # The stride of each layer. + norm_cfg=norm_cfg, # The configuration of norm layer. + norm_eval=False, # Whether to freeze the statistics in BN + style='pytorch', # The style of backbone, 'pytorch' means that stride 2 layers are in 3x3 conv, 'caffe' means stride 2 layers are in 1x1 convs. + contract_dilation=True), # When dilation > 1, whether contract first layer of dilation. + decode_head=dict( + type='PSPHead', # Type of decode head. Please refer to mmseg/models/decode_heads for available options. + in_channels=2048, # Input channel of decode head. + in_index=3, # The index of feature map to select. + channels=512, # The intermediate channels of decode head. + pool_scales=(1, 2, 3, 6), # The avg pooling scales of PSPHead. Please refer to paper for details. + dropout_ratio=0.1, # The dropout ratio before final classification layer. + num_classes=19, # Number of segmentation class. Usually 19 for cityscapes, 21 for VOC, 150 for ADE20k. + norm_cfg=norm_cfg, # The configuration of norm layer. + align_corners=False, # The align_corners argument for resize in decoding. + loss_decode=dict( # Config of loss function for the decode_head. + type='CrossEntropyLoss', # Type of loss used for segmentation. + use_sigmoid=False, # Whether use sigmoid activation for segmentation. + loss_weight=1.0)), # Loss weight of decode_head. + auxiliary_head=dict( + type='FCNHead', # Type of auxiliary head. Please refer to mmseg/models/decode_heads for available options. + in_channels=1024, # Input channel of auxiliary head. + in_index=2, # The index of feature map to select. + channels=256, # The intermediate channels of decode head. + num_convs=1, # Number of convs in FCNHead. It is usually 1 in auxiliary head. + concat_input=False, # Whether concat output of convs with input before classification layer. + dropout_ratio=0.1, # The dropout ratio before final classification layer. + num_classes=19, # Number of segmentation class. Usually 19 for cityscapes, 21 for VOC, 150 for ADE20k. + norm_cfg=norm_cfg, # The configuration of norm layer. + align_corners=False, # The align_corners argument for resize in decoding. + loss_decode=dict( # Config of loss function for the auxiliary_head. + type='CrossEntropyLoss', # Type of loss used for segmentation. + use_sigmoid=False, # Whether use sigmoid activation for segmentation. + loss_weight=0.4)), # Loss weight of auxiliary_head. + # model training and testing settings + train_cfg=dict(), # train_cfg is just a place holder for now. + test_cfg=dict(mode='whole')) # The test mode, options are 'whole' and 'slide'. 'whole': whole image fully-convolutional test. 'slide': sliding crop window on the image. +``` + +`_base_/datasets/cityscapes.py` is the configuration file of the dataset + +```python +# dataset settings +dataset_type = 'CityscapesDataset' # Dataset type, this will be used to define the dataset. +data_root = 'data/cityscapes/' # Root path of data. +crop_size = (512, 1024) # The crop size during training. +train_pipeline = [ # Training pipeline. + dict(type='LoadImageFromFile'), # First pipeline to load images from file path. + dict(type='LoadAnnotations'), # Second pipeline to load annotations for current image. + dict(type='RandomResize', # Augmentation pipeline that resize the images and their annotations. + scale=(2048, 1024), # The scale of image. + ratio_range=(0.5, 2.0), # The augmented scale range as ratio. + keep_ratio=True), # Whether to keep the aspect ratio when resizing the image. + dict(type='RandomCrop', # Augmentation pipeline that randomly crop a patch from current image. + crop_size=crop_size, # The crop size of patch. + cat_max_ratio=0.75), # The max area ratio that could be occupied by single category. + dict(type='RandomFlip', # Augmentation pipeline that flip the images and their annotations + prob=0.5), # The ratio or probability to flip + dict(type='PhotoMetricDistortion'), # Augmentation pipeline that distort current image with several photo metric methods. + dict(type='PackSegInputs') # Pack the inputs data for the semantic segmentation. +] +test_pipeline = [ + dict(type='LoadImageFromFile'), # First pipeline to load images from file path + dict(type='Resize', # Use resize augmentation + scale=(2048, 1024), # Images scales for resizing. + keep_ratio=True), # Whether to keep the aspect ratio when resizing the image. + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), # Load annotations for semantic segmentation provided by dataset. + dict(type='PackSegInputs') # Pack the inputs data for the semantic segmentation. +] +train_dataloader = dict( # Train dataloader config + batch_size=2, # Batch size of a single GPU + num_workers=2, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate training speed. + sampler=dict(type='InfiniteSampler', shuffle=True), # Randomly shuffle during training. + dataset=dict( # Train dataset config + type=dataset_type, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='leftImg8bit/train', seg_map_path='gtFine/train'), # Prefix for training data. + pipeline=train_pipeline)) # Processing pipeline. This is passed by the train_pipeline created before. +val_dataloader = dict( + batch_size=1, # Batch size of a single GPU + num_workers=4, # Worker to pre-fetch data for each single GPU + persistent_workers=True, # Shut down the worker processes after an epoch end, which can accelerate testing speed. + sampler=dict(type='DefaultSampler', shuffle=False), # Not shuffle during validation and testing. + dataset=dict( # Test dataset config + type=dataset_type, # Type of dataset, refer to mmseg/datasets/ for details. + data_root=data_root, # The root of dataset. + data_prefix=dict( + img_path='leftImg8bit/val', seg_map_path='gtFine/val'), # Prefix for testing data. + pipeline=test_pipeline)) # Processing pipeline. This is passed by the test_pipeline created before. +test_dataloader = val_dataloader +# The metric to measure the accuracy. Here, we use IoUMetric. +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator +``` + +`_base_/schedules/schedule_40k.py` + +```python +# optimizer +optimizer = dict(type='SGD', # Type of optimizers, refer to https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py for more details + lr=0.01, # Learning rate of optimizers, see detail usages of the parameters in the documentation of PyTorch + momentum=0.9, # Momentum + weight_decay=0.0005) # Weight decay of SGD +optim_wrapper = dict(type='OptimWrapper', # Optimizer wrapper provides a common interface for updating parameters. + optimizer=optimizer, # Optimizer used to update model parameters. + clip_grad=None) # If ``clip_grad`` is not None, it will be the arguments of ``torch.nn.utils.clip_grad``. +# learning policy +param_scheduler = [ + dict( + type='PolyLR', # The policy of scheduler, also support Step, CosineAnnealing, Cyclic, etc. Refer to details of supported LrUpdater from https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py + eta_min=1e-4, # Minimum learning rate at the end of scheduling. + power=0.9, # The power of polynomial decay. + begin=0, # Step at which to start updating the parameters. + end=40000, # Step at which to stop updating the parameters. + by_epoch=False) # Whether count by epoch or not. +] +# training schedule for 40k iteration +train_cfg = dict(type='IterBasedTrainLoop', max_iters=40000, val_interval=4000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +# default hooks +default_hooks = dict( + timer=dict(type='IterTimerHook'), # Log the time spent during iteration. + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), # Collect and write logs from different components of ``Runner``. + param_scheduler=dict(type='ParamSchedulerHook'), # update some hyper-parameters in optimizer, e.g., learning rate. + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), # Save checkpoints periodically. + sampler_seed=dict(type='DistSamplerSeedHook')) # Data-loading sampler for distributed training. +``` + +in `_base_/default_runtime.py` + +```python +# Set the default scope of the registry to mmseg. +default_scope = 'mmseg' +# environment +env_cfg = dict( + cudnn_benchmark=True, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) +log_level = 'INFO' +log_processor = dict(by_epoch=False) +load_from = None # Load checkpoint from file. +resume = False # Whether to resume from existed model. +``` + +These are all the configs for training and testing PSPNet, to load and parse them, we can use [Config](https://mmengine.readthedocs.io/en/latest/tutorials/config.html) implemented in [MMEngine](https://github.com/open-mmlab/mmengine) + +```python +from mmengine.config import Config + +cfg = Config.fromfile('configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py') +print(cfg.train_dataloader) +``` + +```shell +{'batch_size': 2, + 'num_workers': 2, + 'persistent_workers': True, + 'sampler': {'type': 'InfiniteSampler', 'shuffle': True}, + 'dataset': {'type': 'CityscapesDataset', + 'data_root': 'data/cityscapes/', + 'data_prefix': {'img_path': 'leftImg8bit/train', + 'seg_map_path': 'gtFine/train'}, + 'pipeline': [{'type': 'LoadImageFromFile'}, + {'type': 'LoadAnnotations'}, + {'type': 'RandomResize', + 'scale': (2048, 1024), + 'ratio_range': (0.5, 2.0), + 'keep_ratio': True}, + {'type': 'RandomCrop', 'crop_size': (512, 1024), 'cat_max_ratio': 0.75}, + {'type': 'RandomFlip', 'prob': 0.5}, + {'type': 'PhotoMetricDistortion'}, + {'type': 'PackSegInputs'}]}} +``` + +`cfg` is an instance of `mmengine.config.Config`, its interface is the same as a dict object and also allows access config values as attributes. See [config tutorial](https://mmengine.readthedocs.io/en/latest/tutorials/config.html) in [MMEngine](https://github.com/open-mmlab/mmengine) for more information. + +## FAQ + +### Ignore some fields in the base configs + +Sometimes, you may set `_delete_=True` to ignore some of the fields in base configs. +See [config tutorial](https://mmengine.readthedocs.io/en/latest/tutorials/config.html) in [MMEngine](https://github.com/open-mmlab/mmengine) for simple illustration. + +In MMSegmentation, for example, if you would like to modify the backbone of PSPNet with the following config file `pspnet.py`: + +```python +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + pretrained='torchvision://resnet50', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=3, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))) +``` + +Load and parse the config file `pspnet.py` in the code as follows: + +```python +from mmengine.config import Config + +cfg = Config.fromfile('pspnet.py') +print(cfg.model) +``` + +```shell +{'type': 'EncoderDecoder', + 'pretrained': 'torchvision://resnet50', + 'backbone': {'type': 'ResNetV1c', + 'depth': 50, + 'num_stages': 4, + 'out_indices': (0, 1, 2, 3), + 'dilations': (1, 1, 2, 4), + 'strides': (1, 2, 1, 1), + 'norm_cfg': {'type': 'SyncBN', 'requires_grad': True}, + 'norm_eval': False, + 'style': 'pytorch', + 'contract_dilation': True}, + 'decode_head': {'type': 'PSPHead', + 'in_channels': 2048, + 'in_index': 3, + 'channels': 512, + 'pool_scales': (1, 2, 3, 6), + 'dropout_ratio': 0.1, + 'num_classes': 19, + 'norm_cfg': {'type': 'SyncBN', 'requires_grad': True}, + 'align_corners': False, + 'loss_decode': {'type': 'CrossEntropyLoss', + 'use_sigmoid': False, + 'loss_weight': 1.0}}} +``` + +`ResNet` and `HRNet` use different keywords to construct, write a new config file `hrnet.py` as follows: + +```python +_base_ = 'pspnet.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w32', + backbone=dict( + _delete_=True, + type='HRNet', + norm_cfg=norm_cfg, + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))))) +``` + +Load and parse the config file `hrnet.py` in the code as follows: + +```python +from mmengine.config import Config +cfg = Config.fromfile('hrnet.py') +print(cfg.model) +``` + +```shell +{'type': 'EncoderDecoder', + 'pretrained': 'open-mmlab://msra/hrnetv2_w32', + 'backbone': {'type': 'HRNet', + 'norm_cfg': {'type': 'SyncBN', 'requires_grad': True}, + 'extra': {'stage1': {'num_modules': 1, + 'num_branches': 1, + 'block': 'BOTTLENECK', + 'num_blocks': (4,), + 'num_channels': (64,)}, + 'stage2': {'num_modules': 1, + 'num_branches': 2, + 'block': 'BASIC', + 'num_blocks': (4, 4), + 'num_channels': (32, 64)}, + 'stage3': {'num_modules': 4, + 'num_branches': 3, + 'block': 'BASIC', + 'num_blocks': (4, 4, 4), + 'num_channels': (32, 64, 128)}, + 'stage4': {'num_modules': 3, + 'num_branches': 4, + 'block': 'BASIC', + 'num_blocks': (4, 4, 4, 4), + 'num_channels': (32, 64, 128, 256)}}}, + 'decode_head': {'type': 'PSPHead', + 'in_channels': 2048, + 'in_index': 3, + 'channels': 512, + 'pool_scales': (1, 2, 3, 6), + 'dropout_ratio': 0.1, + 'num_classes': 19, + 'norm_cfg': {'type': 'SyncBN', 'requires_grad': True}, + 'align_corners': False, + 'loss_decode': {'type': 'CrossEntropyLoss', + 'use_sigmoid': False, + 'loss_weight': 1.0}}} +``` + +The `_delete_=True` would replace all old keys in `backbone` field with new keys. + +### Use intermediate variables in configs + +Some intermediate variables are used in the configs files, like `train_pipeline`/`test_pipeline` in datasets. +It's worth noting that when modifying intermediate variables in the children configs, user need to pass the intermediate variables into corresponding fields again. +For example, we would like to change multi scale strategy to train/test a PSPNet. `train_pipeline`/`test_pipeline` are intermediate variable we would like to modify. + +```python +_base_ = '../pspnet/pspnet_r50-d8_4xb4-40k_cityscpaes-512x1024.py' +crop_size = (512, 1024) +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='RandomResize', + img_scale=(2048, 1024), + ratio_range=(1., 2.), + keep_ration=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', flip_ratio=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs'), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', + scale=(2048, 1024), + keep_ratio=True), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='leftImg8bit/train', seg_map_path='gtFine/train'), + pipeline=train_pipeline) +test_dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='leftImg8bit/val', seg_map_path='gtFine/val'), + pipeline=test_pipeline) +train_dataloader = dict(dataset=train_dataset) +val_dataloader = dict(dataset=test_dataset) +test_dataloader = val_dataloader +``` + +We first define the new `train_pipeline`/`test_pipeline` and pass them into `dataset`. + +Similarly, if we would like to switch from `SyncBN` to `BN` or `MMSyncBN`, we need to substitute every `norm_cfg` in the config. + +```python +_base_ = '../pspnet/pspnet_r50-d8_4xb4-40k_cityscpaes-512x1024.py' +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + backbone=dict(norm_cfg=norm_cfg), + decode_head=dict(norm_cfg=norm_cfg), + auxiliary_head=dict(norm_cfg=norm_cfg)) +``` + +## Modify config through script arguments + +In the [training script](https://github.com/open-mmlab/mmsegmentation/blob/1.x/tools/train.py) and the [testing script](https://github.com/open-mmlab/mmsegmentation/blob/1.x/tools/test.py), we support the script argument `--cfg-options`, it may help users override some settings in the used config, the key-value pair in `xxx=yyy` format will be merged into config file. + +For example, this is a simplified script `demo_script.py`: + +```python +import argparse + +from mmengine.config import Config, DictAction + +def parse_args(): + parser = argparse.ArgumentParser(description='Script Example') + parser.add_argument('config', help='train config file path') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + args = parser.parse_args() + return args + +def main(): + args = parse_args() + + cfg = Config.fromfile(args.config) + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + print(cfg) + +if __name__ == '__main__': + main() +``` + +An example config file `demo_config.py` as follows: + +```python +backbone = dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_eval=False, + style='pytorch', + contract_dilation=True) +``` + +Run `demo_script.py`: + +```shell +python demo_script.py demo_config.py +``` + +```shell +Config (path: demo_config.py): {'backbone': {'type': 'ResNetV1c', 'depth': 50, 'num_stages': 4, 'out_indices': (0, 1, 2, 3), 'dilations': (1, 1, 2, 4), 'strides': (1, 2, 1, 1), 'norm_eval': False, 'style': 'pytorch', 'contract_dilation': True}} +``` + +Modify config through script arguments: + +```shell +python demo_script.py demo_config.py --cfg-options backbone.depth=101 +``` + +```shell +Config (path: demo_config.py): {'backbone': {'type': 'ResNetV1c', 'depth': 101, 'num_stages': 4, 'out_indices': (0, 1, 2, 3), 'dilations': (1, 1, 2, 4), 'strides': (1, 2, 1, 1), 'norm_eval': False, 'style': 'pytorch', 'contract_dilation': True}} +``` + +- Update values of list/tuples. + + If the value to be updated is a list or a tuple. For example, the config file `demo_config.py` sets `strides=(1, 2, 1, 1)` in `backbone`. + If you want to change this key, you may specify in two ways: + + 1. `--cfg-options backbone.strides="(1, 1, 1, 1)"`. Note that the quotation mark " is necessary to support list/tuple data types. + + ```shell + python demo_script.py demo_config.py --cfg-options backbone.strides="(1, 1, 1, 1)" + ``` + + ```shell + Config (path: demo_config.py): {'backbone': {'type': 'ResNetV1c', 'depth': 50, 'num_stages': 4, 'out_indices': (0, 1, 2, 3), 'dilations': (1, 1, 2, 4), 'strides': (1, 1, 1, 1), 'norm_eval': False, 'style': 'pytorch', 'contract_dilation': True}} + ``` + + 2. `--cfg-options backbone.strides=1,1,1,1`. Note that **NO** white space is allowed in the specified value. + In addition, if the original type is tuple, it will be automatically converted to list after this way. + + ```shell + python demo_script.py demo_config.py --cfg-options backbone.strides=1,1,1,1 + ``` + + ```shell + Config (path: demo_config.py): {'backbone': {'type': 'ResNetV1c', 'depth': 50, 'num_stages': 4, 'out_indices': (0, 1, 2, 3), 'dilations': (1, 1, 2, 4), 'strides': [1, 1, 1, 1], 'norm_eval': False, 'style': 'pytorch', 'contract_dilation': True}} + ``` + +```{note} + This modification method only supports modifying configuration items of string, int, float, boolean, None, list and tuple types. + More specifically, for list and tuple types, the elements inside them must also be one of the above seven types. +``` diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/2_dataset_prepare.md b/Seg_All_In_One_MMSeg/docs/en/user_guides/2_dataset_prepare.md new file mode 100644 index 0000000..3f94a94 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/2_dataset_prepare.md @@ -0,0 +1,806 @@ +# Tutorial 2: Prepare datasets + +It is recommended to symlink the dataset root to `$MMSEGMENTATION/data`. +If your folder structure is different, you may need to change the corresponding paths in config files. +For users in China, we also recommend you get the dsdl dataset from our opensource platform [OpenDataLab](https://opendatalab.com/), for better download and use experience,here is an example: [DSDLReadme](../../../configs/dsdl/README.md), welcome to try. + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── cityscapes +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +│ ├── VOCdevkit +│ │ ├── VOC2012 +│ │ │ ├── JPEGImages +│ │ │ ├── SegmentationClass +│ │ │ ├── ImageSets +│ │ │ │ ├── Segmentation +│ │ ├── VOC2010 +│ │ │ ├── JPEGImages +│ │ │ ├── SegmentationClassContext +│ │ │ ├── ImageSets +│ │ │ │ ├── SegmentationContext +│ │ │ │ │ ├── train.txt +│ │ │ │ │ ├── val.txt +│ │ │ ├── trainval_merged.json +│ │ ├── VOCaug +│ │ │ ├── dataset +│ │ │ │ ├── cls +│ ├── ade +│ │ ├── ADEChallengeData2016 +│ │ │ ├── annotations +│ │ │ │ ├── training +│ │ │ │ ├── validation +│ │ │ ├── images +│ │ │ │ ├── training +│ │ │ │ ├── validation +│ ├── coco_stuff10k +│ │ ├── images +│ │ │ ├── train2014 +│ │ │ ├── test2014 +│ │ ├── annotations +│ │ │ ├── train2014 +│ │ │ ├── test2014 +│ │ ├── imagesLists +│ │ │ ├── train.txt +│ │ │ ├── test.txt +│ │ │ ├── all.txt +│ ├── coco_stuff164k +│ │ ├── images +│ │ │ ├── train2017 +│ │ │ ├── val2017 +│ │ ├── annotations +│ │ │ ├── train2017 +│ │ │ ├── val2017 +│ ├── CHASE_DB1 +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ ├── DRIVE +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ ├── HRF +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ ├── STARE +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +| ├── dark_zurich +| │   ├── gps +| │   │   ├── val +| │   │   └── val_ref +| │   ├── gt +| │   │   └── val +| │   ├── LICENSE.txt +| │   ├── lists_file_names +| │   │   ├── val_filenames.txt +| │   │   └── val_ref_filenames.txt +| │   ├── README.md +| │   └── rgb_anon +| │   | ├── val +| │   | └── val_ref +| ├── NighttimeDrivingTest +| | ├── gtCoarse_daytime_trainvaltest +| | │   └── test +| | │   └── night +| | └── leftImg8bit +| | | └── test +| | | └── night +│ ├── loveDA +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ │ ├── test +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── potsdam +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── vaihingen +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── iSAID +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ │ ├── test +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── synapse +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── REFUGE +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ ├── mapillary +│ │ ├── training +│ │ │ ├── images +│ │ │ ├── v1.2 +| │ │ │ ├── instances +| │ │ │ ├── labels +| │   │   │ └── panoptic +│ │ │ ├── v2.0 +| │ │ │ ├── instances +| │ │ │ ├── labels +| │ │ │ ├── panoptic +| │   │   │ └── polygons +│ │ ├── validation +│ │ │ ├── images +| │ │ ├── v1.2 +| │ │ │ ├── instances +| │ │ │ ├── labels +| │   │   │ └── panoptic +│ │ │ ├── v2.0 +| │ │ │ ├── instances +| │ │ │ ├── labels +| │ │ │ ├── panoptic +| │   │   │ └── polygons +│ ├── bdd100k +│ │ ├── images +│ │ │ └── 10k +| │ │ │ ├── test +| │ │ │ ├── train +| │   │   │ └── val +│ │ └── labels +│ │ │ └── sem_seg +| │ │ │ ├── colormaps +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── masks +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── polygons +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +| │   │   │ └── rles +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +│ ├── nyu +│ │ ├── images +│ │ │ ├── train +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── train +│ │ │ ├── test +│ ├── HSIDrive20 +│ │ ├── images +│ │ │ ├── train +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── train +│ │ │ ├── validation +│ │ │ ├── test +``` + +## Download dataset via MIM + +By using [OpenXLab](https://openxlab.org.cn/datasets), you can obtain free formatted datasets in various fields. Through the search function of the platform, you may address the dataset they look for quickly and easily. Using the formatted datasets from the platform, you can efficiently conduct tasks across datasets. + +If you use MIM to download, make sure that the version is greater than v0.3.8. You can use the following command to update, install, login and download the dataset: + +```shell +# upgrade your MIM +pip install -U openmim + +# install OpenXLab CLI tools +pip install -U openxlab +# log in OpenXLab +openxlab login + +# download ADE20K by MIM +mim download mmsegmentation --dataset ade20k +``` + +## Cityscapes + +The data could be found [here](https://www.cityscapes-dataset.com/downloads/) after registration. + +By convention, `**labelTrainIds.png` are used for cityscapes training. +We provided a [script](https://github.com/open-mmlab/mmsegmentation/blob/1.x/tools/dataset_converters/cityscapes.py) based on [cityscapesscripts](https://github.com/mcordts/cityscapesScripts)to generate `**labelTrainIds.png`. + +```shell +# --nproc means 8 process for conversion, which could be omitted as well. +python tools/dataset_converters/cityscapes.py data/cityscapes --nproc 8 +``` + +## Pascal VOC + +Pascal VOC 2012 could be downloaded from [here](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar). +Beside, most recent works on Pascal VOC dataset usually exploit extra augmentation data, which could be found [here](http://www.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/semantic_contours/benchmark.tgz). + +If you would like to use augmented VOC dataset, please run following command to convert augmentation annotations into proper format. + +```shell +# --nproc means 8 process for conversion, which could be omitted as well. +python tools/dataset_converters/voc_aug.py data/VOCdevkit data/VOCdevkit/VOCaug --nproc 8 +``` + +Please refer to [concat dataset](../advanced_guides/add_datasets.md#concatenate-dataset) and [voc_aug config example](../../../configs/_base_/datasets/pascal_voc12_aug.py) for details about how to concatenate them and train them together. + +## ADE20K + +The training and validation set of ADE20K could be download from this [link](http://data.csail.mit.edu/places/ADEchallenge/ADEChallengeData2016.zip). +We may also download test set from [here](http://data.csail.mit.edu/places/ADEchallenge/release_test.zip). + +## Pascal Context + +The training and validation set of Pascal Context could be download from [here](http://host.robots.ox.ac.uk/pascal/VOC/voc2010/VOCtrainval_03-May-2010.tar). You may also download test set from [here](http://host.robots.ox.ac.uk:8080/eval/downloads/VOC2010test.tar) after registration. + +To split the training and validation set from original dataset, you may download trainval_merged.json from [here](https://codalabuser.blob.core.windows.net/public/trainval_merged.json). + +If you would like to use Pascal Context dataset, please install [Detail](https://github.com/zhanghang1989/detail-api) and then run the following command to convert annotations into proper format. + +```shell +python tools/dataset_converters/pascal_context.py data/VOCdevkit data/VOCdevkit/VOC2010/trainval_merged.json +``` + +## COCO Stuff 10k + +The data could be downloaded [here](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/cocostuff-10k-v1.1.zip) by wget. + +For COCO Stuff 10k dataset, please run the following commands to download and convert the dataset. + +```shell +# download +mkdir coco_stuff10k && cd coco_stuff10k +wget http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/cocostuff-10k-v1.1.zip + +# unzip +unzip cocostuff-10k-v1.1.zip + +# --nproc means 8 process for conversion, which could be omitted as well. +python tools/dataset_converters/coco_stuff10k.py /path/to/coco_stuff10k --nproc 8 +``` + +By convention, mask labels in `/path/to/coco_stuff164k/annotations/*2014/*_labelTrainIds.png` are used for COCO Stuff 10k training and testing. + +## COCO Stuff 164k + +For COCO Stuff 164k dataset, please run the following commands to download and convert the augmented dataset. + +```shell +# download +mkdir coco_stuff164k && cd coco_stuff164k +wget http://images.cocodataset.org/zips/train2017.zip +wget http://images.cocodataset.org/zips/val2017.zip +wget http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip + +# unzip +unzip train2017.zip -d images/ +unzip val2017.zip -d images/ +unzip stuffthingmaps_trainval2017.zip -d annotations/ + +# --nproc means 8 process for conversion, which could be omitted as well. +python tools/dataset_converters/coco_stuff164k.py /path/to/coco_stuff164k --nproc 8 +``` + +By convention, mask labels in `/path/to/coco_stuff164k/annotations/*2017/*_labelTrainIds.png` are used for COCO Stuff 164k training and testing. + +The details of this dataset could be found at [here](https://github.com/nightrome/cocostuff#downloads). + +## CHASE DB1 + +The training and validation set of CHASE DB1 could be download from [here](https://staffnet.kingston.ac.uk/~ku15565/CHASE_DB1/assets/CHASEDB1.zip). + +To convert CHASE DB1 dataset to MMSegmentation format, you should run the following command: + +```shell +python tools/dataset_converters/chase_db1.py /path/to/CHASEDB1.zip +``` + +The script will make directory structure automatically. + +## DRIVE + +The training and validation set of DRIVE could be download from [here](https://drive.grand-challenge.org/). Before that, you should register an account. Currently '1st_manual' is not provided officially. + +To convert DRIVE dataset to MMSegmentation format, you should run the following command: + +```shell +python tools/dataset_converters/drive.py /path/to/training.zip /path/to/test.zip +``` + +The script will make directory structure automatically. + +## HRF + +First, download [healthy.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/healthy.zip), [glaucoma.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/glaucoma.zip), [diabetic_retinopathy.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/diabetic_retinopathy.zip), [healthy_manualsegm.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/healthy_manualsegm.zip), [glaucoma_manualsegm.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/glaucoma_manualsegm.zip) and [diabetic_retinopathy_manualsegm.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/diabetic_retinopathy_manualsegm.zip). + +To convert HRF dataset to MMSegmentation format, you should run the following command: + +```shell +python tools/dataset_converters/hrf.py /path/to/healthy.zip /path/to/healthy_manualsegm.zip /path/to/glaucoma.zip /path/to/glaucoma_manualsegm.zip /path/to/diabetic_retinopathy.zip /path/to/diabetic_retinopathy_manualsegm.zip +``` + +The script will make directory structure automatically. + +## STARE + +First, download [stare-images.tar](http://cecas.clemson.edu/~ahoover/stare/probing/stare-images.tar), [labels-ah.tar](http://cecas.clemson.edu/~ahoover/stare/probing/labels-ah.tar) and [labels-vk.tar](http://cecas.clemson.edu/~ahoover/stare/probing/labels-vk.tar). + +To convert STARE dataset to MMSegmentation format, you should run the following command: + +```shell +python tools/dataset_converters/stare.py /path/to/stare-images.tar /path/to/labels-ah.tar /path/to/labels-vk.tar +``` + +The script will make directory structure automatically. + +## Dark Zurich + +Since we only support test models on this dataset, you may only download [the validation set](https://data.vision.ee.ethz.ch/csakarid/shared/GCMA_UIoU/Dark_Zurich_val_anon.zip). + +## Nighttime Driving + +Since we only support test models on this dataset, you may only download [the test set](http://data.vision.ee.ethz.ch/daid/NighttimeDriving/NighttimeDrivingTest.zip). + +## LoveDA + +The data could be downloaded from Google Drive [here](https://drive.google.com/drive/folders/1ibYV0qwn4yuuh068Rnc-w4tPi0U0c-ti?usp=sharing). + +Or it can be downloaded from [zenodo](https://zenodo.org/record/5706578#.YZvN7SYRXdF), you should run the following command: + +```shell +# Download Train.zip +wget https://zenodo.org/record/5706578/files/Train.zip +# Download Val.zip +wget https://zenodo.org/record/5706578/files/Val.zip +# Download Test.zip +wget https://zenodo.org/record/5706578/files/Test.zip +``` + +For LoveDA dataset, please run the following command to re-organize the dataset. + +```shell +python tools/dataset_converters/loveda.py /path/to/loveDA +``` + +Using trained model to predict test set of LoveDA and submit it to server can be found [here](https://codalab.lisn.upsaclay.fr/competitions/421). + +More details about LoveDA can be found [here](https://github.com/Junjue-Wang/LoveDA). + +## ISPRS Potsdam + +The [Potsdam](https://www.isprs.org/education/benchmarks/UrbanSemLab/2d-sem-label-potsdam.aspx) dataset is for urban semantic segmentation used in the 2D Semantic Labeling Contest - Potsdam. + +The dataset can be requested at the challenge [homepage](https://www.isprs.org/education/benchmarks/UrbanSemLab/default.aspx). +Or download on [BaiduNetdisk](https://pan.baidu.com/s/1K-cLVZnd1X7d8c26FQ-nGg?pwd=mseg),password:mseg, [Google Drive](https://drive.google.com/drive/folders/1w3EJuyUGet6_qmLwGAWZ9vw5ogeG0zLz?usp=sharing) and [OpenDataLab](https://opendatalab.com/ISPRS_Potsdam/download). +The '2_Ortho_RGB.zip' and '5_Labels_all_noBoundary.zip' are required. + +For Potsdam dataset, please run the following command to re-organize the dataset. + +```shell +python tools/dataset_converters/potsdam.py /path/to/potsdam +``` + +In our default setting, it will generate 3456 images for training and 2016 images for validation. + +## ISPRS Vaihingen + +The [Vaihingen](https://www2.isprs.org/commissions/comm2/wg4/benchmark/2d-sem-label-vaihingen/) dataset is for urban semantic segmentation used in the 2D Semantic Labeling Contest - Vaihingen. + +The dataset can be requested at the challenge [homepage](https://www2.isprs.org/commissions/comm2/wg4/benchmark/data-request-form/). +Or [BaiduNetdisk](https://pan.baidu.com/s/109D3WLrLafsuYtLeerLiiA?pwd=mseg),password:mseg, [Google Drive](https://drive.google.com/drive/folders/1w3NhvLVA2myVZqOn2pbiDXngNC7NTP_t?usp=sharing). +The 'ISPRS_semantic_labeling_Vaihingen.zip' and 'ISPRS_semantic_labeling_Vaihingen_ground_truth_eroded_COMPLETE.zip' are required. + +For Vaihingen dataset, please run the following command to re-organize the dataset. + +```shell +python tools/dataset_converters/vaihingen.py /path/to/vaihingen +``` + +In our default setting (`clip_size`=512, `stride_size`=256), it will generate 344 images for training and 398 images for validation. + +## iSAID + +The data images could be download from [DOTA-v1.0](https://captain-whu.github.io/DOTA/dataset.html) (train/val/test) + +The data annotations could be download from [iSAID](https://captain-whu.github.io/iSAID/dataset.html) (train/val) + +The dataset is a Large-scale Dataset for Instance Segmentation (also have semantic segmentation) in Aerial Images. + +You may need to follow the following structure for dataset preparation after downloading iSAID dataset. + +```none +├── data +│ ├── iSAID +│ │ ├── train +│ │ │ ├── images +│ │ │ │ ├── part1.zip +│ │ │ │ ├── part2.zip +│ │ │ │ ├── part3.zip +│ │ │ ├── Semantic_masks +│ │ │ │ ├── images.zip +│ │ ├── val +│ │ │ ├── images +│ │ │ │ ├── part1.zip +│ │ │ ├── Semantic_masks +│ │ │ │ ├── images.zip +│ │ ├── test +│ │ │ ├── images +│ │ │ │ ├── part1.zip +│ │ │ │ ├── part2.zip +``` + +```shell +python tools/dataset_converters/isaid.py /path/to/iSAID +``` + +In our default setting (`patch_width`=896, `patch_height`=896, `overlap_area`=384), it will generate 33978 images for training and 11644 images for validation. + +## LIP(Look Into Person) dataset + +This dataset could be download from [this page](https://lip.sysuhcp.com/overview.php). + +Please run the following commands to unzip dataset. + +```shell +unzip LIP.zip +cd LIP +unzip TrainVal_images.zip +unzip TrainVal_parsing_annotations.zip +cd TrainVal_parsing_annotations +unzip TrainVal_parsing_annotations.zip +mv train_segmentations ../ +mv val_segmentations ../ +cd .. +``` + +The contents of LIP datasets include: + +```none +├── data +│ ├── LIP +│ │ ├── train_images +│   │ │ ├── 1000_1234574.jpg +│   │ │ ├── ... +│ │ ├── train_segmentations +│   │ │ ├── 1000_1234574.png +│   │ │ ├── ... +│ │ ├── val_images +│   │ │ ├── 100034_483681.jpg +│   │ │ ├── ... +│ │ ├── val_segmentations +│   │ │ ├── 100034_483681.png +│   │ │ ├── ... +``` + +## Synapse dataset + +This dataset could be download from [this page](https://www.synapse.org/#!Synapse:syn3193805/wiki/). + +To follow the data preparation setting of [TransUNet](https://arxiv.org/abs/2102.04306), which splits original training set (30 scans) into new training (18 scans) and validation set (12 scans). Please run the following command to prepare the dataset. + +```shell +unzip RawData.zip +cd ./RawData/Training +``` + +Then create `train.txt` and `val.txt` to split dataset. + +According to TransUnet, the following is the data set division. + +train.txt + +```none +img0005.nii.gz +img0006.nii.gz +img0007.nii.gz +img0009.nii.gz +img0010.nii.gz +img0021.nii.gz +img0023.nii.gz +img0024.nii.gz +img0026.nii.gz +img0027.nii.gz +img0028.nii.gz +img0030.nii.gz +img0031.nii.gz +img0033.nii.gz +img0034.nii.gz +img0037.nii.gz +img0039.nii.gz +img0040.nii.gz +``` + +val.txt + +```none +img0008.nii.gz +img0022.nii.gz +img0038.nii.gz +img0036.nii.gz +img0032.nii.gz +img0002.nii.gz +img0029.nii.gz +img0003.nii.gz +img0001.nii.gz +img0004.nii.gz +img0025.nii.gz +img0035.nii.gz +``` + +The contents of synapse datasets include: + +```none +├── Training +│ ├── img +│ │ ├── img0001.nii.gz +│ │ ├── img0002.nii.gz +│ │ ├── ... +│ ├── label +│ │ ├── label0001.nii.gz +│ │ ├── label0002.nii.gz +│ │ ├── ... +│ ├── train.txt +│ ├── val.txt +``` + +Then, use this command to convert synapse dataset. + +```shell +python tools/dataset_converters/synapse.py --dataset-path /path/to/synapse +``` + +Noted that MMSegmentation default evaluation metric (such as mean dice value) is calculated on 2D slice image, which is not comparable to results of 3D scan in some paper such as [TransUNet](https://arxiv.org/abs/2102.04306). + +## REFUGE + +Register in [REFUGE Challenge](https://refuge.grand-challenge.org) and download [REFUGE dataset](https://refuge.grand-challenge.org/REFUGE2Download). + +Then, unzip `REFUGE2.zip` and the contents of original datasets include: + +```none +├── REFUGE2 +│ ├── REFUGE2 +│ │ ├── Annotation-Training400.zip +│ │ ├── REFUGE-Test400.zip +│ │ ├── REFUGE-Test-GT.zip +│ │ ├── REFUGE-Training400.zip +│ │ ├── REFUGE-Validation400.zip +│ │ ├── REFUGE-Validation400-GT.zip +│ ├── __MACOSX +``` + +Please run the following command to convert REFUGE dataset: + +```shell +python tools/convert_datasets/refuge.py --raw_data_root=/path/to/refuge/REFUGE2/REFUGE2 +``` + +The script will make directory structure below: + +```none +│ ├── REFUGE +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +``` + +It includes 400 images for training, 400 images for validation and 400 images for testing which is the same as REFUGE 2018 dataset. + +## Mapillary Vistas Datasets + +- The dataset could be download [here](https://www.mapillary.com/dataset/vistas) after registration. + +- Mapillary Vistas Dataset use 8-bit with color-palette to store labels. No conversion operation is required. + +- Assumption you have put the dataset zip file in `mmsegmentation/data/mapillary` + +- Please run the following commands to unzip dataset. + + ```bash + cd data/mapillary + unzip An-ZjB1Zm61yAZG0ozTymz8I8NqI4x0MrYrh26dq7kPgfu8vf9ImrdaOAVOFYbJ2pNAgUnVGBmbue9lTgdBOb5BbKXIpFs0fpYWqACbrQDChAA2fdX0zS9PcHu7fY8c-FOvyBVxPNYNFQuM.zip + ``` + +- After unzip, you will get Mapillary Vistas Dataset like this structure. Semantic segmentation mask labels in `labels` folder. + + ```none + mmsegmentation + ├── mmseg + ├── tools + ├── configs + ├── data + │ ├── mapillary + │ │ ├── training + │ │ │ ├── images + │ │ │ ├── v1.2 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │   │   │ └── panoptic + │ │ │ ├── v2.0 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── panoptic + | │   │   │ └── polygons + │ │ ├── validation + │ │ │ ├── images + | │ │ ├── v1.2 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │   │   │ └── panoptic + │ │ │ ├── v2.0 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── panoptic + | │   │   │ └── polygons + ``` + +- You could set Datasets version with `MapillaryDataset_v1` and `MapillaryDataset_v2` in your configs. + View the Mapillary Vistas Datasets config file here [V1.2](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/_base_/datasets/mapillary_v1.py) and [V2.0](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/_base_/datasets/mapillary_v2.py) + +## LEVIR-CD + +[LEVIR-CD](https://justchenhao.github.io/LEVIR/) Large-scale Remote Sensing Change Detection Dataset for Building. + +Download the dataset from [here](https://justchenhao.github.io/LEVIR/). + +The supplement version of the dataset can be requested on the [homepage](https://github.com/S2Looking/Dataset) + +Please download the supplement version of the dataset, then unzip `LEVIR-CD+.zip`, the contents of original datasets include: + +```none +│ ├── LEVIR-CD+ +│ │ ├── train +│ │ │ ├── A +│ │ │ ├── B +│ │ │ ├── label +│ │ ├── test +│ │ │ ├── A +│ │ │ ├── B +│ │ │ ├── label +``` + +For LEVIR-CD dataset, please run the following command to crop images without overlap: + +```shell +python tools/dataset_converters/levircd.py --dataset-path /path/to/LEVIR-CD+ --out_dir /path/to/LEVIR-CD +``` + +The size of cropped image is 256x256, which is consistent with the original paper. + +## BDD100K + +- You could download BDD100k datasets from [here](https://bdd-data.berkeley.edu/) after registration. + +- You can download images and masks by clicking `10K Images` button and `Segmentation` button. + +- After download, unzip by the following instructions: + + ```bash + unzip ~/bdd100k_images_10k.zip -d ~/mmsegmentation/data/ + unzip ~/bdd100k_sem_seg_labels_trainval.zip -d ~/mmsegmentation/data/ + ``` + +- And get + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── bdd100k +│ │ ├── images +│ │ │ └── 10k +| │ │ │ ├── test +| │ │ │ ├── train +| │   │   │ └── val +│ │ └── labels +│ │ │ └── sem_seg +| │ │ │ ├── colormaps +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── masks +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── polygons +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +| │   │   │ └── rles +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +``` + +## NYU + +- To access the NYU dataset, you can download it from [this link](https://drive.google.com/file/d/1wC-io-14RCIL4XTUrQLk6lBqU2AexLVp/view?usp=share_link) + +- Once the download is complete, you can utilize the [tools/dataset_converters/nyu.py](/tools/dataset_converters/nyu.py) script to extract and organize the data into the required format. Run the following command in your terminal: + + ```bash + python tools/dataset_converters/nyu.py nyu.zip + ``` + +## HSI Drive 2.0 + +- You could download HSI Drive 2.0 dataset from [here](https://ipaccess.ehu.eus/HSI-Drive/#download) after just sending an email to gded@ehu.eus with the subject "download HSI-Drive". You will receive a password to uncompress the files. + +- After download, unzip by the following instructions: + + ```bash + 7z x -p"password" ./HSI_Drive_v2_0_Phyton.zip + + mv ./HSIDrive20 path_to_mmsegmentation/data + mv ./HSI_Drive_v2_0_release_notes_Python_version.md path_to_mmsegmentation/data + mv ./image_numbering.pdf path_to_mmsegmentation/data + ``` + +- After unzip, you get + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── HSIDrive20 +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── images_MF +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── RGB +│ │ ├── training_filenames.txt +│ │ ├── validation_filenames.txt +│ │ ├── test_filenames.txt +│ ├── HSI_Drive_v2_0_release_notes_Python_version.md +│ ├── image_numbering.pdf +``` diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/3_inference.md b/Seg_All_In_One_MMSeg/docs/en/user_guides/3_inference.md new file mode 100644 index 0000000..cacebd2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/3_inference.md @@ -0,0 +1,244 @@ +# Tutorial 3: Inference with existing models + +MMSegmentation provides pre-trained models for semantic segmentation in [Model Zoo](../model_zoo.md), and supports multiple standard datasets, including Cityscapes, ADE20K, etc. +This note will show how to use existing models to inference on given images. +As for how to test existing models on standard datasets, please see this [guide](./4_train_test.md) + +MMSegmentation provides several interfaces for users to easily use pre-trained models for inference. + +- [Tutorial 3: Inference with existing models](#tutorial-3-inference-with-existing-models) + - [Inferencer](#inferencer) + - [Basic Usage](#basic-usage) + - [Initialization](#initialization) + - [Visualize prediction](#visualize-prediction) + - [List model](#list-model) + - [Inference API](#inference-api) + - [mmseg.apis.init_model](#mmsegapisinit_model) + - [mmseg.apis.inference_model](#mmsegapisinference_model) + - [mmseg.apis.show_result_pyplot](#mmsegapisshow_result_pyplot) + +## Inferencer + +We provide the most **convenient** way to use the model in MMSegmentation `MMSegInferencer`. You can get segmentation mask for an image with only 3 lines of code. + +### Basic Usage + +The following example shows how to use `MMSegInferencer` to perform inference on a single image. + +``` +>>> from mmseg.apis import MMSegInferencer +>>> # Load models into memory +>>> inferencer = MMSegInferencer(model='deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024') +>>> # Inference +>>> inferencer('demo/demo.png', show=True) +``` + +The visualization result should look like: + +
+ +
+ +Moreover, you can use `MMSegInferencer` to process a list of images: + +``` +# Input a list of images +>>> images = [image1, image2, ...] # image1 can be a file path or a np.ndarray +>>> inferencer(images, show=True, wait_time=0.5) # wait_time is delay time, and 0 means forever + +# Or input image directory +>>> images = $IMAGESDIR +>>> inferencer(images, show=True, wait_time=0.5) + +# Save visualized rendering color maps and predicted results +# out_dir is the directory to save the output results, img_out_dir and pred_out_dir are subdirectories of out_dir +# to save visualized rendering color maps and predicted results +>>> inferencer(images, out_dir='outputs', img_out_dir='vis', pred_out_dir='pred') +``` + +There is a optional parameter of inferencer, `return_datasamples`, whose default value is False, and return value of inferencer is a `dict` type by default, including 2 keys 'visualization' and 'predictions'. +If `return_datasamples=True` inferencer will return [`SegDataSample`](../advanced_guides/structures.md), or list of it. + +``` +result = inferencer('demo/demo.png') +# result is a `dict` including 2 keys 'visualization' and 'predictions' +# 'visualization' includes color segmentation map +print(result['visualization'].shape) +# (512, 683, 3) + +# 'predictions' includes segmentation mask with label indice +print(result['predictions'].shape) +# (512, 683) + +result = inferencer('demo/demo.png', return_datasamples=True) +print(type(result)) +# + +# Input a list of images +results = inferencer(images) +# The output is list +print(type(results['visualization']), results['visualization'][0].shape) +# (512, 683, 3) +print(type(results['predictions']), results['predictions'][0].shape) +# (512, 683) + +results = inferencer(images, return_datasamples=True) +# +print(type(results[0])) +# +``` + +### Initialization + +`MMSegInferencer` must be initialized from a `model`, which can be a model name or a `Config` even a path of config file. +The model names can be found in models' metafile (configs/xxx/metafile.yaml), like one model name of maskformer is `maskformer_r50-d32_8xb2-160k_ade20k-512x512`, and if input model name and the weights of the model will be download automatically. Below are other input parameters: + +- weights (str, optional) - Path to the checkpoint. If it is not specified and model is a model name of metafile, the weights will be loaded from metafile. Defaults to None. +- classes (list, optional) - Input classes for result rendering, as the prediction of segmentation model is a segment map with label indices, `classes` is a list which includes items responding to the label indices. If classes is not defined, visualizer will take `cityscapes` classes by default. Defaults to None. +- palette (list, optional) - Input palette for result rendering, which is a list of colors responding to the classes. If the palette is not defined, the visualizer will take the palette of `cityscapes` by default. Defaults to None. +- dataset_name (str, optional) - [Dataset name or alias](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/utils/class_names.py#L302-L317), visualizer will use the meta information of the dataset i.e. classes and palette, but the `classes` and `palette` have higher priority. Defaults to None. +- device (str, optional) - Device to run inference. If None, the available device will be automatically used. Defaults to None. +- scope (str, optional) - The scope of the model. Defaults to 'mmseg'. + +### Visualize prediction + +`MMSegInferencer` supports 4 parameters for visualize prediction, you can use them when call initialized inferencer: + +- show (bool) - Whether to display the image in a popup window. Defaults to False. +- wait_time (float) - The interval of show (s). Defaults to 0. +- img_out_dir (str) - Subdirectory of `out_dir`, used to save rendering color segmentation mask, so `out_dir` must be defined if you would like to save predicted mask. Defaults to 'vis'. +- opacity (int, float) - The transparency of segmentation mask. Defaults to 0.8. + +The examples of these parameters is in [Basic Usage](#basic-usage) + +### List model + +There is a very easy to list all model names in MMSegmentation + +``` +>>> from mmseg.apis import MMSegInferencer +# models is a list of model names, and them will print automatically +>>> models = MMSegInferencer.list_models('mmseg') +``` + +## Inference API + +### mmseg.apis.init_model + +Initialize a segmentor from config file. + +Parameters: + +- config (str, `Path`, or `mmengine.Config`) - Config file path or the config object. +- checkpoint (str, optional) - Checkpoint path. If left as None, the model will not load any weights. +- device (str, optional) - CPU/CUDA device option. Default 'cuda:0'. +- cfg_options (dict, optional) - Options to override some settings in the used config. + +Returns: + +- nn.Module: The constructed segmentor. + +Example: + +```python +from mmseg.apis import init_model + +config_path = 'configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +checkpoint_path = 'checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth' + +# initialize model without checkpoint +model = init_model(config_path) + +# init model and load checkpoint +model = init_model(config_path, checkpoint_path) + +# init model and load checkpoint on CPU +model = init_model(config_path, checkpoint_path, 'cpu') +``` + +### mmseg.apis.inference_model + +Inference image(s) with the segmentor. + +Parameters: + +- model (nn.Module) - The loaded segmentor +- imgs (str, np.ndarray, or list\[str/np.ndarray\]) - Either image files or loaded images + +Returns: + +- `SegDataSample` or list\[`SegDataSample`\]: If imgs is a list or tuple, the same length list type results will be returned, otherwise return the segmentation results directly. + +**Note:** [SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py) is a data structure interface of MMSegmentation, it is used as interfaces between different components. `SegDataSample` implement the abstract data element `mmengine.structures.BaseDataElement`, please refer to data element [documentation](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/data_element.html) in [MMEngine](https://github.com/open-mmlab/mmengine) for more information. + +The attributes in `SegDataSample` are divided into several parts: + +- `gt_sem_seg` (`PixelData`) - Ground truth of semantic segmentation. +- `pred_sem_seg` (`PixelData`) - Prediction of semantic segmentation. +- `seg_logits` (`PixelData`) - Predicted logits of semantic segmentation. + +**Note** [PixelData](https://github.com/open-mmlab/mmengine/blob/main/mmengine/structures/pixel_data.py) is the data structure for pixel-level annotations or predictions, please refer to PixelData [documentation](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/data_element.html) in [MMEngine](https://github.com/open-mmlab/mmengine) for more information. + +Example: + +```python +from mmseg.apis import init_model, inference_model + +config_path = 'configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +checkpoint_path = 'checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth' +img_path = 'demo/demo.png' + + +model = init_model(config_path, checkpoint_path) +result = inference_model(model, img_path) +``` + +### mmseg.apis.show_result_pyplot + +Visualize the segmentation results on the image. + +Parameters: + +- model (nn.Module) - The loaded segmentor. +- img (str or np.ndarray) - Image filename or loaded image. +- result (`SegDataSample`) - The prediction SegDataSample result. +- opacity (float) - Opacity of painted segmentation map. Default `0.5`, must be in `(0, 1]` range. +- title (str) - The title of pyplot figure. Default is ''. +- draw_gt (bool) - Whether to draw GT SegDataSample. Default to `True`. +- draw_pred (draws_pred) - Whether to draw Prediction SegDataSample. Default to `True`. +- wait_time (float) - The interval of show (s), 0 is the special value that means "forever". Default to `0`. +- show (bool) - Whether to display the drawn image. Default to `True`. +- save_dir (str, optional) - Save file dir for all storage backends. If it is `None`, the backend storage will not save any data. +- out_file (str, optional) - Path to output file. Default to `None`. + +Returns: + +- np.ndarray: the drawn image which channel is RGB. + +Example: + +```python +from mmseg.apis import init_model, inference_model, show_result_pyplot + +config_path = 'configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +checkpoint_path = 'checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth' +img_path = 'demo/demo.png' + + +# build the model from a config file and a checkpoint file +model = init_model(config_path, checkpoint_path, device='cuda:0') + +# inference on given image +result = inference_model(model, img_path) + +# display the segmentation result +vis_image = show_result_pyplot(model, img_path, result) + +# save the visualization result, the output image would be found at the path `work_dirs/result.png` +vis_iamge = show_result_pyplot(model, img_path, result, out_file='work_dirs/result.png') + +# Modify the time of displaying images, note that 0 is the special value that means "forever" +vis_image = show_result_pyplot(model, img_path, result, wait_time=5) +``` + +**Note:** If your current device doesn't have graphical user interface, it is recommended that setting `show` to `False` and specify the `out_file` or `save_dir` to save the results. If you would like to display the result on a window, no special settings are required. diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/4_train_test.md b/Seg_All_In_One_MMSeg/docs/en/user_guides/4_train_test.md new file mode 100644 index 0000000..9b2d17d --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/4_train_test.md @@ -0,0 +1,315 @@ +# Tutorial 4: Train and test with existing models + +MMSegmentation supports training and testing models on a variety of devices, which are described below for single-GPU, distributed, and cluster training and testing, respectively. Through this tutorial, you will learn how to train and test using the scripts provided by MMSegmentation. + +## Training and testing on a single GPU + +### Training on a single GPU + +We provide `tools/train.py` to launch training jobs on a single GPU. +The basic usage is as follows. + +```shell +python tools/train.py ${CONFIG_FILE} [optional arguments] +``` + +This tool accepts several optional arguments, including: + +- `--work-dir ${WORK_DIR}`: Override the working directory. +- `--amp`: Use auto mixed precision training. +- `--resume`: Resume from the latest checkpoint in the work_dir automatically. +- `--cfg-options ${OVERRIDE_CONFIGS}`: Override some settings in the used config, and the key-value pair in xxx=yyy format will be merged into the config file. + For example, '--cfg-option model.encoder.in_channels=6'. Please see this [guide](./1_config.md#Modify-config-through-script-arguments) for more details. + +Below are the optional arguments for the multi-gpu test: + +- `--launcher`: Items for distributed job initialization launcher. Allowed choices are `none`, `pytorch`, `slurm`, `mpi`. Especially, if set to none, it will test in a non-distributed mode. +- `--local_rank`: ID for local rank. If not specified, it will be set to 0. + +**Note:** Difference between the argument `--resume` and the field `load_from` in the config file: + +`--resume` only determines whether to resume from the latest checkpoint in the work_dir. It is usually used for resuming the training process that is interrupted accidentally. + +`load_from` will specify the checkpoint to be loaded and the training iteration starts from 0. It is usually used for fine-tuning. + +If you would like to resume training from a specific checkpoint, you can use: + +```python +python tools/train.py ${CONFIG_FILE} --resume --cfg-options load_from=${CHECKPOINT} +``` + +**Training on CPU**: The process of training on the CPU is consistent with single GPU training if a machine does not have GPU. If it has GPUs but not wanting to use them, we just need to disable GPUs before the training process. + +```shell +export CUDA_VISIBLE_DEVICES=-1 +``` + +And then run the script [above](#training-on-a-single-gpu). + +### Testing on a single GPU + +We provide `tools/test.py` to launch training jobs on a single GPU. +The basic usage is as follows. + +```shell +python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [optional arguments] +``` + +This tool accepts several optional arguments, including: + +- `--work-dir`: If specified, results will be saved in this directory. If not specified, the results will be automatically saved to `work_dirs/{CONFIG_NAME}`. +- `--show`: Show prediction results at runtime, available when `--show-dir` is not specified. +- `--show-dir`: Directory where painted images will be saved. If specified, the visualized segmentation mask will be saved to the `work_dir/timestamp/show_dir`. +- `--wait-time`: The interval of show (s), which takes effect when `--show` is activated. Default to 2. +- `--cfg-options`: If specified, the key-value pair in xxx=yyy format will be merged into the config file. +- `--tta`: Test time augmentation option. + +**Testing on CPU**: The process of testing on the CPU is consistent with single GPU testing if a machine does not have GPU. If it has GPUs but not wanting to use them, we just need to disable GPUs before the training process. + +```shell +export CUDA_VISIBLE_DEVICES=-1 +``` + +then run the script [above](#testing-on-a-single-gpu). + +## Training and testing on multiple GPUs and multiple machines + +### Training on multiple GPUs + +OpenMMLab2.0 implements **distributed** training with `MMDistributedDataParallel`. +We provide `tools/dist_train.sh` to launch training on multiple GPUs. + +The basic usage is as follows: + +```shell +sh tools/dist_train.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments] +``` + +Optional arguments remain the same as stated [above](#training-on-a-single-gpu) and have additional arguments to specify the number of GPUs. + +An example: + +```shell +# checkpoints and logs saved in WORK_DIR=work_dirs/pspnet_r50-d8_4xb4-80k_ade20k-512x512/ +# If work_dir is not set, it will be generated automatically. +sh tools/dist_train.sh configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py 8 --work-dir work_dirs/pspnet_r50-d8_4xb4-80k_ade20k-512x512 +``` + +**Note**: During training, checkpoints and logs are saved in the same folder structure as the config file under `work_dirs/`. A custom work directory is not recommended since evaluation scripts infer work directories from the config file name. If you want to save your weights somewhere else, please use a symlink, for example: + +```shell +ln -s ${YOUR_WORK_DIRS} ${MMSEG}/work_dirs +``` + +### Testing on multiple GPUs + +We provide `tools/dist_test.sh` to launch testing on multiple GPUs. +The basic usage is as follows. + +```shell +sh tools/dist_test.sh ${CONFIG_FILE} ${CHECKPOINT_FILE} ${GPU_NUM} [optional arguments] +``` + +Optional arguments remain the same as stated [above](#testing-on-a-single-gpu) and have additional arguments to specify the number of GPUs. + +An example: + +```shell +./tools/dist_test.sh configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py \ + checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth 4 +``` + +### Launch multiple jobs on a single machine + +If you launch multiple jobs on a single machine, e.g., 2 jobs of 4-GPU training on a machine with 8 GPUs, you need to specify different ports (29500 by default) for each job to avoid communication conflict. Otherwise, there will be an error message saying `RuntimeError: Address already in use`. +If you use `dist_train.sh` to launch training jobs, you can set the port in commands with the environment variable `PORT`. + +```shell +CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 sh tools/dist_train.sh ${CONFIG_FILE} 4 +CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 sh tools/dist_train.sh ${CONFIG_FILE} 4 +``` + +### Training with multiple machines + +MMSegmentation relies on `torch.distributed` package for distributed training. +Thus, as a basic usage, one can launch distributed training via PyTorch's [launch utility](https://pytorch.org/docs/stable/distributed.html#launch-utility). + +If you launch with multiple machines simply connected with ethernet, you can simply run the following commands: +On the first machine: + +```shell +NNODES=2 NODE_RANK=0 PORT=${MASTER_PORT} MASTER_ADDR=${MASTER_ADDR} sh tools/dist_train.sh ${CONFIG_FILE} ${GPUS} +``` + +On the second machine: + +```shell +NNODES=2 NODE_RANK=1 PORT=${MASTER_PORT} MASTER_ADDR=${MASTER_ADDR} sh tools/dist_train.sh ${CONFIG_FILE} ${GPUS} +``` + +Usually, it is slow if you do not have high-speed networking like InfiniBand. + +## Manage jobs with Slurm + +[Slurm](https://slurm.schedmd.com/) is a good job scheduling system for computing clusters. + +### Training on a cluster with Slurm + +On a cluster managed by Slurm, you can use `slurm_train.sh` to spawn training jobs. It supports both single-node and multi-node training. + +The basic usage is as follows: + +```shell +[GPUS=${GPUS}] sh tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} [optional arguments] +``` + +Below is an example of using 4 GPUs to train PSPNet on a Slurm partition named _dev_, and set the work-dir to some shared file systems. + +```shell +GPUS=4 sh tools/slurm_train.sh dev pspnet configs/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes.py --work-dir work_dir/pspnet +``` + +You can check [the source code](../../../tools/slurm_train.sh) to review full arguments and environment variables. + +### Testing on a cluster with Slurm + +Similar to the training task, MMSegmentation provides `slurm_test.sh` to launch testing jobs. + +The basic usage is as follows: + +```shell +[GPUS=${GPUS}] sh tools/slurm_test.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${CHECKPOINT_FILE} [optional arguments] +``` + +You can check [the source code](../../../tools/slurm_test.sh) to review full arguments and environment variables. + +**Note:** When using Slurm, the port option needs to be set in one of the following ways: + +1. Set the port through `--cfg-options`. This is more recommended since it does not change the original configs. + + ```shell + GPUS=4 GPUS_PER_NODE=4 sh tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} --cfg-options env_cfg.dist_cfg.port=29500 + GPUS=4 GPUS_PER_NODE=4 sh tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} --cfg-options env_cfg.dist_cfg.port=29501 + ``` + +2. Modify the config files to set different communication ports. + In `config1.py`: + + ```python + enf_cfg = dict(dist_cfg=dict(backend='nccl', port=29500)) + ``` + + In `config2.py`: + + ```python + enf_cfg = dict(dist_cfg=dict(backend='nccl', port=29501)) + ``` + + Then you can launch two jobs with config1.py and config2.py. + + ```shell + CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 sh tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} + CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 sh tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} + ``` + +3. Set the port in the command using the environment variable 'MASTER_PORT': + +```shell +CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 MASTER_PORT=29500 sh tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} +CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 MASTER_PORT=29501 sh tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} +``` + +## Testing and saving segment files + +### Basic Usage + +When you want to save the results, you can use `--out` to specify the output directory. + +```shell +python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} --out ${OUTPUT_DIR} +``` + +Here is an example to save the predicted results from model `fcn_r50-d8_4xb4-80k_ade20k-512x512` on ADE20k validatation dataset. + +```shell +python tools/test.py configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py ckpt/fcn_r50-d8_512x512_80k_ade20k_20200614_144016-f8ac5082.pth --out work_dirs/format_results +``` + +You also can modify the config file to define `output_dir`. We also take +`fcn_r50-d8_4xb4-80k_ade20k-512x512` as example just add +`test_evaluator` in `configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py` + +```python +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU'], output_dir='work_dirs/format_results') +``` + +then run command without `--out`: + +```shell +python tools/test.py configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py ckpt/fcn_r50-d8_512x512_80k_ade20k_20200614_144016-f8ac5082.pth +``` + +If you would like to only save the predicted results without evaluation as annotation is not released by the official dataset, you can set `format_only=True` and modify `test_dataloader`. +As there is no annotation in dataset, we remove `dict(type='LoadAnnotations')` from `test_dataloader` Here is the example configuration: + +```python +test_evaluator = dict( + type='IoUMetric', + iou_metrics=['mIoU'], + format_only=True, + output_dir='work_dirs/format_results') +test_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type = 'ADE20KDataset' + data_root='data/ade/release_test', + data_prefix=dict(img_path='testing'), + # we don't load annotation in test transform pipeline. + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + dict(type='PackSegInputs') + ])) +``` + +then run test command: + +```shell +python tools/test.py configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py ckpt/fcn_r50-d8_512x512_80k_ade20k_20200614_144016-f8ac5082.pth +``` + +### Testing Cityscape dataset and save predicted segment files + +We recommend `CityscapesMetric` which is the wrapper of Cityscapes'sdk, when you want to +save the predicted results of Cityscape test dataset to submit them in [Cityscape test server](https://www.cityscapes-dataset.com/submit/). Here is the example configuration: + +```python +test_evaluator = dict( + type='CityscapesMetric', + format_only=True, + keep_results=True, + output_dir='work_dirs/format_results') +test_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='CityscapesDataset', + data_root='data/cityscapes/', + data_prefix=dict(img_path='leftImg8bit/test'), + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + dict(type='PackSegInputs') + ])) +``` + +then run test command, for example: + +```shell +python tools/test.py configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py ckpt/fcn_r18-d8_512x1024_80k_cityscapes_20201225_021327-6c50f8b4.pth +``` diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/5_deployment.md b/Seg_All_In_One_MMSeg/docs/en/user_guides/5_deployment.md new file mode 100644 index 0000000..b3b8f57 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/5_deployment.md @@ -0,0 +1,255 @@ +# Tutorial 5: Model Deployment + +# MMSegmentation Model Deployment + +- [Tutorial 5: Model Deployment](#tutorial-5-model-deployment) +- [MMSegmentation Model Deployment](#mmsegmentation-model-deployment) + - [Installation](#installation) + - [Install mmseg](#install-mmseg) + - [Install mmdeploy](#install-mmdeploy) + - [Convert model](#convert-model) + - [Model specification](#model-specification) + - [Model inference](#model-inference) + - [Backend model inference](#backend-model-inference) + - [SDK model inference](#sdk-model-inference) + - [Supported models](#supported-models) + - [Note](#note) + +______________________________________________________________________ + +[MMSegmentation](https://github.com/open-mmlab/mmsegmentation/tree/main), also known as `mmseg`, is an open source semantic segmentation toolbox based on Pytorch. It's a part of the [OpenMMLab](<(https://openmmlab.com/)>) object. + +## Installation + +### Install mmseg + +Please follow the [Installation Guide](https://mmsegmentation.readthedocs.io/en/latest/get_started.html). + +### Install mmdeploy + +`mmdeploy` can be installed as follows: + +**Option 1:** Install precompiled package + +Please follow the [Installation overview](https://mmdeploy.readthedocs.io/zh_CN/latest/get_started.html#mmdeploy) + +**Option 2:** Automatic Installation script + +If the deployment platform is **Ubuntu 18.04 +**, please follow the [scription installation](../01-how-to-build/build_from_script.md) to install. +For example, the following commands describe how to install mmdeploy and inference engine-`ONNX Runtime`. + +```shell +git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git +cd mmdeploy +python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc) +export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH +export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH +``` + +**NOTE**: + +- Add `$(pwd)/build/lib` to `PYTHONPATH`, can loading mmdeploy SDK python package `mmdeploy_runtime`. See [SDK model inference](#SDK-model-inference) for more information. +- With [ONNX Runtime model inference](#Backend-model-inference), need to load custom operator library and add ONNX Runtime Library's PATH to `LD_LIBRARY_PATH`. + +**Option 3:** Install with mim + +1. Use mim to install mmcv + +```shell +pip install -U openmim +mim install "mmcv>=2.0.0rc2" +``` + +2. Install mmdeploy + +```shell +git clone https://github.com/open-mmlab/mmdeploy.git +cd mmdeploy +mim install -e . +``` + +**Option 4:** Build MMDeploy from source + +If the first three methods aren't suitable, please [Build MMDeploy from source](<(../01-how-to-build/build_from_source.md)>) + +## Convert model + +[tools/deploy.py](https://github.com/open-mmlab/mmdeploy/tree/main/tools/deploy.py) can convert mmseg Model to backend model conveniently. See [this](https://github.com/open-mmlab/mmdeploy/tree/main/docs/en/02-how-to-run/convert_model.md#usage) for detailed information. + +Then convert `unet` to onnx model as follows: + +```shell +cd mmdeploy + +# download unet model from mmseg model zoo +mim download mmsegmentation --config unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024 --dest . + +# convert mmseg model to onnxruntime model with dynamic shape +python tools/deploy.py \ + configs/mmseg/segmentation_onnxruntime_dynamic.py \ + unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py \ + fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes_20211210_145204-6860854e.pth \ + demo/resources/cityscapes.png \ + --work-dir mmdeploy_models/mmseg/ort \ + --device cpu \ + --show \ + --dump-info +``` + +It is crucial to specify the correct deployment config during model conversion. MMDeploy has already provided builtin deployment config [files](https://github.com/open-mmlab/mmdeploy/tree/main/configs/mmseg) of all supported backends for mmsegmentation, under which the config file path follows the pattern: + +``` +segmentation_{backend}-{precision}_{static | dynamic}_{shape}.py +``` + +- **{backend}:** inference backend, such as onnxruntime, tensorrt, pplnn, ncnn, openvino, coreml etc. +- **{precision}:** fp16, int8. When it's empty, it means fp32 +- **{static | dynamic}:** static shape or dynamic shape +- **{shape}:** input shape or shape range of a model + +Therefore, in the above example, you can also convert `unet` to tensorrt-fp16 model by `segmentation_tensorrt-fp16_dynamic-512x1024-2048x2048.py`. + +```{tip} +When converting mmsegmentation models to tensorrt models, --device should be set to "cuda" +``` + +## Model specification + +Before moving on to model inference chapter, let's know more about the converted model structure which is very important for model inference. + +The converted model locates in the working directory like `mmdeploy_models/mmseg/ort` in the previous example. It includes: + +``` +mmdeploy_models/mmseg/ort +├── deploy.json +├── detail.json +├── end2end.onnx +└── pipeline.json +``` + +in which, + +- **end2end.onnx**: backend model which can be inferred by ONNX Runtime +- ***xxx*.json**: the necessary information for mmdeploy SDK + +The whole package **mmdeploy_models/mmseg/ort** is defined as **mmdeploy SDK model**, i.e., **mmdeploy SDK model** includes both backend model and inference meta information. + +## Model inference + +### Backend model inference + +Take the previous converted `end2end.onnx` model as an example, you can use the following code to inference the model and visualize the results: + +```python +from mmdeploy.apis.utils import build_task_processor +from mmdeploy.utils import get_input_shape, load_config +import torch + +deploy_cfg = 'configs/mmseg/segmentation_onnxruntime_dynamic.py' +model_cfg = './unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py' +device = 'cpu' +backend_model = ['./mmdeploy_models/mmseg/ort/end2end.onnx'] +image = './demo/resources/cityscapes.png' + +# read deploy_cfg and model_cfg +deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg) + +# build task and backend model +task_processor = build_task_processor(model_cfg, deploy_cfg, device) +model = task_processor.build_backend_model(backend_model) + +# process input image +input_shape = get_input_shape(deploy_cfg) +model_inputs, _ = task_processor.create_input(image, input_shape) + +# do model inference +with torch.no_grad(): + result = model.test_step(model_inputs) + +# visualize results +task_processor.visualize( + image=image, + model=model, + result=result[0], + window_name='visualize', + output_file='./output_segmentation.png') +``` + +### SDK model inference + +You can also perform SDK model inference like following: + +```python +from mmdeploy_runtime import Segmentor +import cv2 +import numpy as np + +img = cv2.imread('./demo/resources/cityscapes.png') + +# create a classifier +segmentor = Segmentor(model_path='./mmdeploy_models/mmseg/ort', device_name='cpu', device_id=0) +# perform inference +seg = segmentor(img) + +# visualize inference result +## random a palette with size 256x3 +palette = np.random.randint(0, 256, size=(256, 3)) +color_seg = np.zeros((seg.shape[0], seg.shape[1], 3), dtype=np.uint8) +for label, color in enumerate(palette): + color_seg[seg == label, :] = color +# convert to BGR +color_seg = color_seg[..., ::-1] +img = img * 0.5 + color_seg * 0.5 +img = img.astype(np.uint8) +cv2.imwrite('output_segmentation.png', img) +``` + +Besides python API, mmdeploy SDK also provides other FFI (Foreign Function Interface), such as C, C++, C#, Java and so on. You can learn their usage from [demo](https://github.com/open-mmlab/mmdeploy/tree/main/demo) + +## Supported models + +| Model | TorchScript | OnnxRuntime | TensorRT | ncnn | PPLNN | OpenVino | +| :-------------------------------------------------------------------------------------------------------- | :---------: | :---------: | :------: | :--: | :---: | :------: | +| [FCN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/fcn) | Y | Y | Y | Y | Y | Y | +| [PSPNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/pspnet)[\*](#static_shape) | Y | Y | Y | Y | Y | Y | +| [DeepLabV3](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/deeplabv3) | Y | Y | Y | Y | Y | Y | +| [DeepLabV3+](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/deeplabv3plus) | Y | Y | Y | Y | Y | Y | +| [Fast-SCNN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/fastscnn)[\*](#static_shape) | Y | Y | Y | N | Y | Y | +| [UNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/unet) | Y | Y | Y | Y | Y | Y | +| [ANN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/ann)[\*](#static_shape) | Y | Y | Y | N | N | N | +| [APCNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/apcnet) | Y | Y | Y | Y | N | N | +| [BiSeNetV1](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/bisenetv1) | Y | Y | Y | Y | N | Y | +| [BiSeNetV2](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/bisenetv2) | Y | Y | Y | Y | N | Y | +| [CGNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/cgnet) | Y | Y | Y | Y | N | Y | +| [DMNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/dmnet) | ? | Y | N | N | N | N | +| [DNLNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/dnlnet) | ? | Y | Y | Y | N | Y | +| [EMANet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/emanet) | Y | Y | Y | N | N | Y | +| [EncNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/encnet) | Y | Y | Y | N | N | Y | +| [ERFNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/erfnet) | Y | Y | Y | Y | N | Y | +| [FastFCN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/fastfcn) | Y | Y | Y | Y | N | Y | +| [GCNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/gcnet) | Y | Y | Y | N | N | N | +| [ICNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/icnet)[\*](#static_shape) | Y | Y | Y | N | N | Y | +| [ISANet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/isanet)[\*](#static_shape) | N | Y | Y | N | N | Y | +| [NonLocal Net](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/nonlocal_net) | ? | Y | Y | Y | N | Y | +| [OCRNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/ocrnet) | Y | Y | Y | Y | N | Y | +| [PointRend](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/point_rend)[\*](#static_shape) | Y | Y | Y | N | N | N | +| [Semantic FPN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/sem_fpn) | Y | Y | Y | Y | N | Y | +| [STDC](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/stdc) | Y | Y | Y | Y | N | Y | +| [UPerNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/upernet)[\*](#static_shape) | N | Y | Y | N | N | N | +| [DANet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/danet) | ? | Y | Y | N | N | Y | +| [Segmenter](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/segmenter)[\*](#static_shape) | N | Y | Y | Y | N | Y | +| [SegFormer](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/segformer)[\*](#static_shape) | ? | Y | Y | N | N | Y | +| [SETR](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/setr) | ? | Y | N | N | N | Y | +| [CCNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/ccnet) | ? | N | N | N | N | N | +| [PSANet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/psanet) | ? | N | N | N | N | N | +| [DPT](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/dpt) | ? | N | N | N | N | N | + +## Note + +- All mmseg models only support the 'whole' inference mode. + +- PSPNet,Fast-SCNN only supports static input, because most inference framework's [nn.AdaptiveAvgPool2d](https://github.com/open-mmlab/mmsegmentation/blob/0c87f7a0c9099844eff8e90fa3db5b0d0ca02fee/mmseg/models/decode_heads/psp_head.py#L38) don't support dynamic input。 + +- For models that only support static shapes, should use the static shape deployment config file, such as `configs/mmseg/segmentation_tensorrt_static-1024x2048.py` + +- To deploy models to generate probabilistic feature maps, please add `codebase_config = dict(with_argmax=False)` to deployment config file. diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/index.rst b/Seg_All_In_One_MMSeg/docs/en/user_guides/index.rst new file mode 100644 index 0000000..1feb127 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/index.rst @@ -0,0 +1,21 @@ +Train & Test +************** + +.. toctree:: + :maxdepth: 1 + + 1_config.md + 2_dataset_prepare.md + 3_inference.md + 4_train_test.md + +Useful Tools +************* + +.. toctree:: + :maxdepth: 2 + + visualization.md + useful_tools.md + deployment.md + visualization_feature_map.md diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/useful_tools.md b/Seg_All_In_One_MMSeg/docs/en/user_guides/useful_tools.md new file mode 100644 index 0000000..0d86778 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/useful_tools.md @@ -0,0 +1,245 @@ +# \[WIP\] Useful Tools + +Apart from training/testing scripts, We provide lots of useful tools under the +`tools/` directory. + +## Analysis Tools + +### Plot training logs + +`tools/analyze_logs.py` plots loss/mIoU curves given a training log file. `pip install seaborn` first to install the dependency. + +```shell +python tools/analysis_tools/analyze_logs.py xxx.json [--keys ${KEYS}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}] +``` + +Examples: + +- Plot the mIoU, mAcc, aAcc metrics. + + ```shell + python tools/analysis_tools/analyze_logs.py log.json --keys mIoU mAcc aAcc --legend mIoU mAcc aAcc + ``` + +- Plot loss metric. + + ```shell + python tools/analysis_tools/analyze_logs.py log.json --keys loss --legend loss + ``` + +### Confusion Matrix (experimental) + +In order to generate and plot a `nxn` confusion matrix where `n` is the number of classes, you can follow the steps: + +#### 1.Generate a prediction result in pkl format using `test.py` + +```shell +python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${PATH_TO_RESULT_FILE}] +``` + +Example: + +```shell +python tools/test.py \ +configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py \ +checkpoint/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608-efe53f0d.pth \ +--out result/pred_result.pkl +``` + +#### 2. Use `confusion_matrix.py` to generate and plot a confusion matrix + +```shell +python tools/confusion_matrix.py ${CONFIG_FILE} ${PATH_TO_RESULT_FILE} ${SAVE_DIR} --show +``` + +Description of arguments: + +- `config`: Path to the test config file. +- `prediction_path`: Path to the prediction .pkl result. +- `save_dir`: Directory where confusion matrix will be saved. +- `--show`: Enable result visualize. +- `--color-theme`: Theme of the matrix color map. +- `--cfg_options`: Custom options to replace the config file. + +Example: + +```shell +python tools/confusion_matrix.py \ +configs/fcn/fcn_r50-d8_512x1024_40k_cityscapes.py \ +result/pred_result.pkl \ +result/confusion_matrix \ +--show +``` + +### Get the FLOPs and params (experimental) + +We provide a script adapted from [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) to compute the FLOPs and params of a given model. + +```shell +python tools/analysis_tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}] +``` + +You will get the result like this. + +```none +============================== +Input shape: (3, 2048, 1024) +Flops: 1429.68 GMac +Params: 48.98 M +============================== +``` + +:::{note} +This tool is still experimental and we do not guarantee that the number is correct. You may well use the result for simple comparisons, but double check it before you adopt it in technical reports or papers. +::: + +(1) FLOPs are related to the input shape while parameters are not. The default input shape is (1, 3, 1280, 800). +(2) Some operators are not counted into FLOPs like GN and custom operators. + +## Miscellaneous + +### Publish a model + +Before you upload a model to AWS, you may want to +(1) convert model weights to CPU tensors, (2) delete the optimizer states and +(3) compute the hash of the checkpoint file and append the hash id to the filename. + +```shell +python tools/misc/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME} +``` + +E.g., + +```shell +python tools/publish_model.py work_dirs/pspnet/latest.pth psp_r50_512x1024_40k_cityscapes.pth +``` + +The final output filename will be `psp_r50_512x1024_40k_cityscapes-{hash id}.pth`. + +### Print the entire config + +`tools/misc/print_config.py` prints the whole config verbatim, expanding all its +imports. + +```shell +python tools/misc/print_config.py \ + ${CONFIG} \ + --graph \ + --cfg-options ${OPTIONS [OPTIONS...]} \ +``` + +Description of arguments: + +- `config` : The path of a pytorch model config file. +- `--graph` : Determines whether to print the models graph. +- `--cfg-options`: Custom options to replace the config file. + +## Model conversion + +`tools/model_converters/` provide several scripts to convert pretrain models released by other repos to MMSegmentation style. + +### ViT Swin MiT Transformer Models + +- ViT + + `tools/model_converters/vit2mmseg.py` convert keys in timm pretrained vit models to MMSegmentation style. + + ```shell + python tools/model_converters/vit2mmseg.py ${SRC} ${DST} + ``` + +- Swin + + `tools/model_converters/swin2mmseg.py` convert keys in official pretrained swin models to MMSegmentation style. + + ```shell + python tools/model_converters/swin2mmseg.py ${SRC} ${DST} + ``` + +- SegFormer + + `tools/model_converters/mit2mmseg.py` convert keys in official pretrained mit models to MMSegmentation style. + + ```shell + python tools/model_converters/mit2mmseg.py ${SRC} ${DST} + ``` + +## Model Serving + +In order to serve an `MMSegmentation` model with [`TorchServe`](https://pytorch.org/serve/), you can follow the steps: + +### 1. Convert model from MMSegmentation to TorchServe + +```shell +python tools/torchserve/mmseg2torchserve.py ${CONFIG_FILE} ${CHECKPOINT_FILE} \ +--output-folder ${MODEL_STORE} \ +--model-name ${MODEL_NAME} +``` + +:::{note} +${MODEL_STORE} needs to be an absolute path to a folder. +::: + +### 2. Build `mmseg-serve` docker image + +```shell +docker build -t mmseg-serve:latest docker/serve/ +``` + +### 3. Run `mmseg-serve` + +Check the official docs for [running TorchServe with docker](https://github.com/pytorch/serve/blob/master/docker/README.md#running-torchserve-in-a-production-docker-environment). + +In order to run in GPU, you need to install [nvidia-docker](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html). You can omit the `--gpus` argument in order to run in CPU. + +Example: + +```shell +docker run --rm \ +--cpus 8 \ +--gpus device=0 \ +-p8080:8080 -p8081:8081 -p8082:8082 \ +--mount type=bind,source=$MODEL_STORE,target=/home/model-server/model-store \ +mmseg-serve:latest +``` + +[Read the docs](https://github.com/pytorch/serve/blob/072f5d088cce9bb64b2a18af065886c9b01b317b/docs/rest_api.md) about the Inference (8080), Management (8081) and Metrics (8082) APIs + +### 4. Test deployment + +```shell +curl -O https://raw.githubusercontent.com/open-mmlab/mmsegmentation/master/resources/3dogs.jpg +curl http://127.0.0.1:8080/predictions/${MODEL_NAME} -T 3dogs.jpg -o 3dogs_mask.png +``` + +The response will be a ".png" mask. + +You can visualize the output as follows: + +```python +import matplotlib.pyplot as plt +import mmcv +plt.imshow(mmcv.imread("3dogs_mask.png", "grayscale")) +plt.show() +``` + +You should see something similar to: + +![3dogs_mask](../../resources/3dogs_mask.png) + +And you can use `test_torchserve.py` to compare result of torchserve and pytorch, and visualize them. + +```shell +python tools/torchserve/test_torchserve.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} ${MODEL_NAME} +[--inference-addr ${INFERENCE_ADDR}] [--result-image ${RESULT_IMAGE}] [--device ${DEVICE}] +``` + +Example: + +```shell +python tools/torchserve/test_torchserve.py \ +demo/demo.png \ +configs/fcn/fcn_r50-d8_512x1024_40k_cityscapes.py \ +checkpoint/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608-efe53f0d.pth \ +fcn +``` diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/visualization.md b/Seg_All_In_One_MMSeg/docs/en/user_guides/visualization.md new file mode 100644 index 0000000..e7c3359 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/visualization.md @@ -0,0 +1,174 @@ +# Visualization + +MMSegmentation 1.x provides convenient ways for monitoring training status or visualizing data and model predictions. + +## Training status Monitor + +MMSegmentation 1.x uses TensorBoard to monitor training status. + +### TensorBoard Configuration + +Install TensorBoard following [official instructions](https://www.tensorflow.org/install) e.g. + +```shell +pip install tensorboardX +pip install future tensorboard +``` + +Add `TensorboardVisBackend` in `vis_backend` of `visualizer` in `default_runtime.py` config file: + +```python +vis_backends = [dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +``` + +### Examining scalars in TensorBoard + +Launch training experiment e.g. + +```shell +python tools/train.py configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py --work-dir work_dir/test_visual +``` + +Find the `vis_data` path of `work_dir` after starting training, for example, the vis_data path of this particular test is as follows: + +```shell +work_dirs/test_visual/20220810_115248/vis_data +``` + +The scalar file in vis_data path includes learning rate, losses and data_time etc, also record metrics results and you can refer [logging tutorial](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/logging.html) in MMEngine to log custom data. The tensorboard visualization results are executed with the following command: + +```shell +tensorboard --logdir work_dirs/test_visual/20220810_115248/vis_data +``` + +## Data and Results visualization + +### Visualizer Data Samples during Model Testing or Validation + +MMSegmentation provides `SegVisualizationHook` which is a [hook](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/hook.md) working to visualize ground truth and prediction of segmentation during model testing and evaluation. Its configuration is in `default_hooks`, please see [Runner tutorial](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/runner.md) for more details. + +For example, In `_base_/schedules/schedule_20k.py`, modify the `SegVisualizationHook` configuration, set `draw` to `True` to enable the storage of network inference results, `interval` indicates the sampling interval of the prediction results, and when set to 1, each inference result of the network will be saved. `interval` is set to 50 by default: + +```python +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=2000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook', draw=True, interval=1)) + +``` + +After launch training experiment, visualization results will be stored in the local folder in validation loop, +or when launch evaluation a model on one dataset, the prediction results will be store in the local. +The stored results of the local visualization are kept in `vis_image` under `$WORK_DIRS/vis_data`, e.g.: + +```shell +work_dirs/test_visual/20220810_115248/vis_data/vis_image +``` + +In addition, if `TensorboardVisBackend` is add in `vis_backends`, like [above](#tensorboard-configuration), +we can also run the following command to view them in TensorBoard: + +```shell +tensorboard --logdir work_dirs/test_visual/20220810_115248/vis_data +``` + +### Visualize a Single Data Sample + +If you want to visualize a single data sample, we suggest to use `SegLocalVisualizer`. + +`SegLocalVisualizer` is child class inherits from `Visualizer` in MMEngine and works for MMSegmentation visualization, for more details about `Visualizer` please refer to [visualization tutorial](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/visualization.md) in MMEngine. + +Here is an example about `SegLocalVisualizer`, first you may download example data below by following commands: + +
+ +
+ +```shell +wget https://user-images.githubusercontent.com/24582831/189833109-eddad58f-f777-4fc0-b98a-6bd429143b06.png --output-document aachen_000000_000019_leftImg8bit.png +wget https://user-images.githubusercontent.com/24582831/189833143-15f60f8a-4d1e-4cbb-a6e7-5e2233869fac.png --output-document aachen_000000_000019_gtFine_labelTrainIds.png +``` + +Then you can find their local path and use the scripts below to visualize: + +```python +import mmcv +import os.path as osp +import torch +# `PixelData` is data structure for pixel-level annotations or predictions defined in MMEngine. +# Please refer to below tutorial file of data structures in MMEngine: +# https://github.com/open-mmlab/mmengine/tree/main/docs/en/advanced_tutorials/data_element.md + +from mmengine.structures import PixelData + +# `SegDataSample` is data structure interface between different components +# defined in MMSegmentation, it includes ground truth, prediction and +# predicted logits of semantic segmentation. +# Please refer to below tutorial file of `SegDataSample` for more details: +# https://github.com/open-mmlab/mmsegmentation/blob/1.x/docs/en/advanced_guides/structures.md + +from mmseg.structures import SegDataSample +from mmseg.visualization import SegLocalVisualizer + +out_file = 'out_file_cityscapes' +save_dir = './work_dirs' + +image = mmcv.imread( + osp.join( + osp.dirname(__file__), + './aachen_000000_000019_leftImg8bit.png' + ), + 'color') +sem_seg = mmcv.imread( + osp.join( + osp.dirname(__file__), + './aachen_000000_000019_gtFine_labelTrainIds.png' # noqa + ), + 'unchanged') +sem_seg = torch.from_numpy(sem_seg) +gt_sem_seg_data = dict(data=sem_seg) +gt_sem_seg = PixelData(**gt_sem_seg_data) +data_sample = SegDataSample() +data_sample.gt_sem_seg = gt_sem_seg + +seg_local_visualizer = SegLocalVisualizer( + vis_backends=[dict(type='LocalVisBackend')], + save_dir=save_dir) + +# The meta information of dataset usually includes `classes` for class names and +# `palette` for visualization color of each foreground. +# All class names and palettes are defined in the file: +# https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/utils/class_names.py + +seg_local_visualizer.dataset_meta = dict( + classes=('road', 'sidewalk', 'building', 'wall', 'fence', + 'pole', 'traffic light', 'traffic sign', + 'vegetation', 'terrain', 'sky', 'person', 'rider', + 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle'), + palette=[[128, 64, 128], [244, 35, 232], [70, 70, 70], + [102, 102, 156], [190, 153, 153], [153, 153, 153], + [250, 170, 30], [220, 220, 0], [107, 142, 35], + [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], + [0, 60, 100], [0, 80, 100], [0, 0, 230], + [119, 11, 32]]) +# When `show=True`, the results would be shown directly, +# else if `show=False`, the results would be saved in local directory folder. +seg_local_visualizer.add_datasample(out_file, image, + data_sample, show=False) +``` + +Then the visualization result of image with its corresponding ground truth could be found in `./work_dirs/vis_data/vis_image/` whose name is `out_file_cityscapes_0.png`: + +
+ +
+ +If you would like to know more visualization usage, you can refer to [visualization tutorial](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/visualization.html) in MMEngine. diff --git a/Seg_All_In_One_MMSeg/docs/en/user_guides/visualization_feature_map.md b/Seg_All_In_One_MMSeg/docs/en/user_guides/visualization_feature_map.md new file mode 100644 index 0000000..08398e5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/en/user_guides/visualization_feature_map.md @@ -0,0 +1,201 @@ +# Wandb Feature Map Visualization + +MMSegmentation 1.x provides backend support for Weights & Biases to facilitate visualization and management of project code results. + +## Wandb Configuration + +Install Weights & Biases following [official instructions](https://docs.wandb.ai/quickstart) e.g. + +```shell +pip install wandb +wandb login +``` + +Add `WandbVisBackend` in `vis_backend` of `visualizer` in `default_runtime.py` config file: + +```python +vis_backends=[dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend'), + dict(type='WandbVisBackend')] +``` + +## Examining feature map visualization in Wandb + +`SegLocalVisualizer` is child class inherits from `Visualizer` in MMEngine and works for MMSegmentation visualization, for more details about `Visualizer` please refer to [visualization tutorial](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/visualization.md) in MMEngine. + +Here is an example about `SegLocalVisualizer`, first you may download example data below by following commands: + +
+ +
+ +```shell +wget https://user-images.githubusercontent.com/24582831/189833109-eddad58f-f777-4fc0-b98a-6bd429143b06.png --output-document aachen_000000_000019_leftImg8bit.png +wget https://user-images.githubusercontent.com/24582831/189833143-15f60f8a-4d1e-4cbb-a6e7-5e2233869fac.png --output-document aachen_000000_000019_gtFine_labelTrainIds.png + +wget https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_40k_cityscapes/ann_r50-d8_512x1024_40k_cityscapes_20200605_095211-049fc292.pth + +``` + +```python +# Copyright (c) OpenMMLab. All rights reserved. +from argparse import ArgumentParser +from typing import Type + +import mmcv +import torch +import torch.nn as nn + +from mmengine.model import revert_sync_batchnorm +from mmengine.structures import PixelData +from mmseg.apis import inference_model, init_model +from mmseg.structures import SegDataSample +from mmseg.utils import register_all_modules +from mmseg.visualization import SegLocalVisualizer + + +class Recorder: + """record the forward output feature map and save to data_buffer.""" + + def __init__(self) -> None: + self.data_buffer = list() + + def __enter__(self, ): + self._data_buffer = list() + + def record_data_hook(self, model: nn.Module, input: Type, output: Type): + self.data_buffer.append(output) + + def __exit__(self, *args, **kwargs): + pass + + +def visualize(args, model, recorder, result): + seg_visualizer = SegLocalVisualizer( + vis_backends=[dict(type='WandbVisBackend')], + save_dir='temp_dir', + alpha=0.5) + seg_visualizer.dataset_meta = dict( + classes=model.dataset_meta['classes'], + palette=model.dataset_meta['palette']) + + image = mmcv.imread(args.img, 'color') + + seg_visualizer.add_datasample( + name='predict', + image=image, + data_sample=result, + draw_gt=False, + draw_pred=True, + wait_time=0, + out_file=None, + show=False) + + # add feature map to wandb visualizer + for i in range(len(recorder.data_buffer)): + feature = recorder.data_buffer[i][0] # remove the batch + drawn_img = seg_visualizer.draw_featmap( + feature, image, channel_reduction='select_max') + seg_visualizer.add_image(f'feature_map{i}', drawn_img) + + if args.gt_mask: + sem_seg = mmcv.imread(args.gt_mask, 'unchanged') + sem_seg = torch.from_numpy(sem_seg) + gt_mask = dict(data=sem_seg) + gt_mask = PixelData(**gt_mask) + data_sample = SegDataSample() + data_sample.gt_sem_seg = gt_mask + + seg_visualizer.add_datasample( + name='gt_mask', + image=image, + data_sample=data_sample, + draw_gt=True, + draw_pred=False, + wait_time=0, + out_file=None, + show=False) + + seg_visualizer.add_image('image', image) + + +def main(): + parser = ArgumentParser( + description='Draw the Feature Map During Inference') + parser.add_argument('img', help='Image file') + parser.add_argument('config', help='Config file') + parser.add_argument('checkpoint', help='Checkpoint file') + parser.add_argument('--gt_mask', default=None, help='Path of gt mask file') + parser.add_argument('--out-file', default=None, help='Path to output file') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--opacity', + type=float, + default=0.5, + help='Opacity of painted segmentation map. In (0, 1] range.') + parser.add_argument( + '--title', default='result', help='The image identifier.') + args = parser.parse_args() + + register_all_modules() + + # build the model from a config file and a checkpoint file + model = init_model(args.config, args.checkpoint, device=args.device) + if args.device == 'cpu': + model = revert_sync_batchnorm(model) + + # show all named module in the model and use it in source list below + for name, module in model.named_modules(): + print(name) + + source = [ + 'decode_head.fusion.stages.0.query_project.activate', + 'decode_head.context.stages.0.key_project.activate', + 'decode_head.context.bottleneck.activate' + ] + source = dict.fromkeys(source) + + count = 0 + recorder = Recorder() + # registry the forward hook + for name, module in model.named_modules(): + if name in source: + count += 1 + module.register_forward_hook(recorder.record_data_hook) + if count == len(source): + break + + with recorder: + # test a single image, and record feature map to data_buffer + result = inference_model(model, args.img) + + visualize(args, model, recorder, result) + + +if __name__ == '__main__': + main() + +``` + +Save the above code as feature_map_visual.py and execute the following code in terminal + +```shell +python feature_map_visual.py ${image} ${config} ${checkpoint} [optional args] +``` + +e.g + +```shell +python feature_map_visual.py \ +aachen_000000_000019_leftImg8bit.png \ +configs/ann/ann_r50-d8_4xb2-40k_cityscapes-512x1024.py \ +ann_r50-d8_512x1024_40k_cityscapes_20200605_095211-049fc292.pth \ +--gt_mask aachen_000000_000019_gtFine_labelTrainIds.png +``` + +The visualized image result and its corresponding feature map will appear in the wandb account. + +
+ +
diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/.readthedocs.yaml b/Seg_All_In_One_MMSeg/docs/zh_cn/.readthedocs.yaml new file mode 100644 index 0000000..fc3efd0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +formats: + - epub + +sphinx: + configuration: docs/zh_cn/conf.py + +python: + install: + - requirements: requirements/docs.txt + - requirements: requirements/readthedocs.txt diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/Makefile b/Seg_All_In_One_MMSeg/docs/zh_cn/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/_static/css/readthedocs.css b/Seg_All_In_One_MMSeg/docs/zh_cn/_static/css/readthedocs.css new file mode 100644 index 0000000..2e38d08 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/_static/css/readthedocs.css @@ -0,0 +1,6 @@ +.header-logo { + background-image: url("../images/mmsegmentation.png"); + background-size: 201px 40px; + height: 40px; + width: 201px; +} diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/_static/images/mmsegmentation.png b/Seg_All_In_One_MMSeg/docs/zh_cn/_static/images/mmsegmentation.png new file mode 100644 index 0000000000000000000000000000000000000000..009083a9e80599a1893591ff362da377c94a0975 GIT binary patch literal 44728 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 literal 0 HcmV?d00001 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_datasets.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_datasets.md new file mode 100644 index 0000000..316feb0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_datasets.md @@ -0,0 +1,199 @@ +# 新增自定义数据集 + +## 新增自定义数据集 + +在这里,我们展示如何构建一个新的数据集。 + +1. 创建一个新文件 `mmseg/datasets/example.py` + + ```python + from mmseg.registry import DATASETS + from .basesegdataset import BaseSegDataset + + + @DATASETS.register_module() + class ExampleDataset(BaseSegDataset): + + METAINFO = dict( + classes=('xxx', 'xxx', ...), + palette=[[x, x, x], [x, x, x], ...]) + + def __init__(self, arg1, arg2): + pass + ``` + +2. 在 `mmseg/datasets/__init__.py` 中导入模块 + + ```python + from .example import ExampleDataset + ``` + +3. 通过创建一个新的数据集配置文件 `configs/_base_/datasets/example_dataset.py` 来使用它 + + ```python + dataset_type = 'ExampleDataset' + data_root = 'data/example/' + ... + ``` + +4. 在 `mmseg/utils/class_names.py` 中补充数据集元信息 + + ```python + def example_classes(): + return [ + 'xxx', 'xxx', + ... + ] + + def example_palette(): + return [ + [x, x, x], [x, x, x], + ... + ] + dataset_aliases ={ + 'example': ['example', ...], + ... + } + ``` + +**注意:** 如果新数据集不满足 mmseg 的要求,则需要在 `tools/dataset_converters/` 中准备一个数据集预处理脚本 + +## 通过重新组织数据来定制数据集 + +最简单的方法是将您的数据集进行转化,并组织成文件夹的形式。 + +如下的文件结构就是一个例子。 + +```none +├── data +│ ├── my_dataset +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ │ ├── xxx{img_suffix} +│ │ │ │ ├── yyy{img_suffix} +│ │ │ │ ├── zzz{img_suffix} +│ │ │ ├── val +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ │ ├── xxx{seg_map_suffix} +│ │ │ │ ├── yyy{seg_map_suffix} +│ │ │ │ ├── zzz{seg_map_suffix} +│ │ │ ├── val + +``` + +一个训练对将由 img_dir/ann_dir 里同样首缀的文件组成。 + +有些数据集不会发布测试集或测试集的标注,如果没有测试集的标注,我们就无法在本地进行评估模型,因此我们在配置文件中将验证集设置为默认测试集。 + +关于如何构建自己的数据集或实现新的数据集类,请参阅[数据集指南](./datasets.md)以获取更多详细信息。 + +**注意:** 标注是跟图像同样的形状 (H, W),其中的像素值的范围是 `[0, num_classes - 1]`。 +您也可以使用 [pillow](https://pillow.readthedocs.io/en/stable/handbook/concepts.html#palette) 的 `'P'` 模式去创建包含颜色的标注。 + +## 通过混合数据去定制数据集 + +MMSegmentation 同样支持混合数据集去训练。 +当前它支持拼接 (concat), 重复 (repeat) 和多图混合 (multi-image mix) 数据集。 + +### 重复数据集 + +我们使用 `RepeatDataset` 作为包装 (wrapper) 去重复数据集。 +例如,假设原始数据集是 `Dataset_A`,为了重复它,配置文件如下: + +```python +dataset_A_train = dict( + type='RepeatDataset', + times=N, + dataset=dict( # 这是 Dataset_A 数据集的原始配置 + type='Dataset_A', + ... + pipeline=train_pipeline + ) +) +``` + +### 拼接数据集 + +如果要拼接不同的数据集,可以按如下方式连接数据集配置。 + +```python +dataset_A_train = dict() +dataset_B_train = dict() +concatenate_dataset = dict( + type='ConcatDataset', + datasets=[dataset_A_train, dataset_B_train]) +``` + +下面是一个更复杂的示例,它分别重复 `Dataset_A` 和 `Dataset_B` N 次和 M 次,然后连接重复的数据集。 + +```python +dataset_A_train = dict( + type='RepeatDataset', + times=N, + dataset=dict( + type='Dataset_A', + ... + pipeline=train_pipeline + ) +) +dataset_A_val = dict( + ... + pipeline=test_pipeline +) +dataset_A_test = dict( + ... + pipeline=test_pipeline +) +dataset_B_train = dict( + type='RepeatDataset', + times=M, + dataset=dict( + type='Dataset_B', + ... + pipeline=train_pipeline + ) +) +train_dataloader = dict( + dataset=dict( + type='ConcatDataset', + datasets=[dataset_A_train, dataset_B_train])) + +val_dataloader = dict(dataset=dataset_A_val) +test_dataloader = dict(dataset=dataset_A_test) + +``` + +您可以参考 mmengine 的基础数据集[教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/basedataset.html)以了解更多详细信息 + +### 多图混合集 + +我们使用 `MultiImageMixDataset` 作为包装(wrapper)去混合多个数据集的图片。 +`MultiImageMixDataset`可以被类似 mosaic 和 mixup 的多图混合数据増广使用。 + +`MultiImageMixDataset` 与 `Mosaic` 数据増广一起使用的例子: + +```python +train_pipeline = [ + dict(type='RandomMosaic', prob=1), + dict(type='Resize', img_scale=(1024, 512), keep_ratio=True), + dict(type='RandomFlip', prob=0.5), + dict(type='PackSegInputs') +] + +train_dataset = dict( + type='MultiImageMixDataset', + dataset=dict( + type=dataset_type, + reduce_zero_label=False, + img_dir=data_root + "images/train", + ann_dir=data_root + "annotations/train", + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + ] + ), + pipeline=train_pipeline +) + +``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_metrics.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_metrics.md new file mode 100644 index 0000000..0637b44 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_metrics.md @@ -0,0 +1,81 @@ +# 新增评测指标 + +## 使用 MMSegmentation 的源代码进行开发 + +在这里,我们用 `CustomMetric` 作为例子来展示如何开发一个新的评测指标。 + +1. 创建一个新文件 `mmseg/evaluation/metrics/custom_metric.py`。 + + ```python + from typing import List, Sequence + + from mmengine.evaluator import BaseMetric + + from mmseg.registry import METRICS + + + @METRICS.register_module() + class CustomMetric(BaseMetric): + + def __init__(self, arg1, arg2): + """ + The metric first processes each batch of data_samples and predictions, + and appends the processed results to the results list. Then it + collects all results together from all ranks if distributed training + is used. Finally, it computes the metrics of the entire dataset. + """ + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + pass + + def compute_metrics(self, results: list) -> dict: + pass + + def evaluate(self, size: int) -> dict: + pass + ``` + + 在上面的示例中,`CustomMetric` 是 `BaseMetric` 的子类。它有三个方法:`process`,`compute_metrics` 和 `evaluate`。 + + - `process()` 处理一批数据样本和预测。处理后的结果需要显示地传给 `self.results` ,将在处理所有数据样本后用于计算指标。更多细节请参考 [MMEngine 文档](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/design/evaluation.md) + + - `compute_metrics()` 用于从处理后的结果中计算指标。 + + - `evaluate()` 是一个接口,用于计算指标并返回结果。它将由 `ValLoop` 或 `TestLoop` 在 `Runner` 中调用。在大多数情况下,您不需要重写此方法,但如果您想做一些额外的工作,可以重写它。 + + **注意:** 您可以在[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L366) 找到 `Runner` 调用 `evaluate()` 方法的过程。`Runner` 是训练和测试过程的执行器,您可以在[训练引擎文档](./engine.md)中找到有关它的详细信息。 + +2. 在 `mmseg/evaluation/metrics/__init__.py` 中导入新的指标。 + + ```python + from .custom_metric import CustomMetric + __all__ = ['CustomMetric', ...] + ``` + +3. 在配置文件中设置新的评测指标 + + ```python + val_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx) + test_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx) + ``` + +## 使用发布版本的 MMSegmentation 进行开发 + +上面的示例展示了如何使用 MMSegmentation 的源代码开发新指标。如果您想使用 MMSegmentation 的发布版本开发新指标,可以按照以下步骤操作。 + +1. 创建一个新文件 `/Path/to/metrics/custom_metric.py`,实现 `process`,`compute_metrics` 和 `evaluate` 方法,`evaluate` 方法是可选的。 + +2. 在代码或配置文件中导入新的指标。 + + ```python + from path.to.metrics import CustomMetric + ``` + + 或者 + + ```python + custom_imports = dict(imports=['/Path/to/metrics'], allow_failed_imports=False) + + val_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx) + test_evaluator = dict(type='CustomMetric', arg1=xxx, arg2=xxx) + ``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_models.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_models.md new file mode 100644 index 0000000..e05c07c --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_models.md @@ -0,0 +1,260 @@ +# 新增模块 + +## 开发新组件 + +我们可以自定义 [模型文档](./models.md) 中介绍的所有组件,例如**主干网络(backbone)**、**头(head)**、**损失函数(loss function)**和**数据预处理器(data preprocessor)**。 + +### 添加新的主干网络(backbone) + +在这里,我们以 MobileNet 为例展示如何开发新的主干网络。 + +1. 创建一个新文件 `mmseg/models/backbones/mobilenet.py`。 + + ```python + import torch.nn as nn + + from mmseg.registry import MODELS + + + @MODELS.register_module() + class MobileNet(nn.Module): + + def __init__(self, arg1, arg2): + pass + + def forward(self, x): # should return a tuple + pass + + def init_weights(self, pretrained=None): + pass + ``` + +2. 在 `mmseg/models/backbones/__init__.py` 中引入模块。 + + ```python + from .mobilenet import MobileNet + ``` + +3. 在配置文件中使用它。 + + ```python + model = dict( + ... + backbone=dict( + type='MobileNet', + arg1=xxx, + arg2=xxx), + ... + ``` + +### 添加新的头(head) + +在 MMSegmentation 中,我们提供 [BaseDecodeHead](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/models/decode_heads/decode_head.py#L17) 用于开发所有分割头。 +所有新实现的解码头都应该从中派生出来。 +接下来我们以 [PSPNet](https://arxiv.org/abs/1612.01105) 为例说明如何开发新的头。 + +首先,在 `mmseg/models/decode_heads/psp_head.py` 中添加一个新的解码头。 +PSPNet 实现了用于分割解码的解码头。 +为了实现解码头,在新模块中我们需要执行以下三个函数。 + +```python +from mmseg.registry import MODELS + +@MODELS.register_module() +class PSPHead(BaseDecodeHead): + + def __init__(self, pool_scales=(1, 2, 3, 6), **kwargs): + super(PSPHead, self).__init__(**kwargs) + + def init_weights(self): + pass + + def forward(self, inputs): + pass +``` + +接下来,用户需要在 `mmseg/models/decode_heads/__init__.py` 中添加模块,这样相应的注册器就可以找到并加载它们。 + +PSPNet 的配置文件如下 + +```python +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + pretrained='pretrain_model/resnet50_v1c_trick-2cccc1ad.pth', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=3, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))) + +``` + +### 添加新的损失函数(loss) + +假设您想为分割解码添加一个叫做 `MyLoss` 的新的损失函数。 +要添加新的损失函数,用户需要在 `mmseg/models/loss/my_loss.py` 中实现它。 +修饰器 `weighted_loss` 可以对损失的每个元素进行加权。 + +```python +import torch +import torch.nn as nn + +from mmseg.registry import MODELS +from .utils import weighted_loss + +@weighted_loss +def my_loss(pred, target): + assert pred.size() == target.size() and target.numel() > 0 + loss = torch.abs(pred - target) + return loss + +@MODELS.register_module() +class MyLoss(nn.Module): + + def __init__(self, reduction='mean', loss_weight=1.0): + super(MyLoss, self).__init__() + self.reduction = reduction + self.loss_weight = loss_weight + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None): + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + loss = self.loss_weight * my_loss( + pred, target, weight, reduction=reduction, avg_factor=avg_factor) + return loss +``` + +然后,用户需要将其添加到 `mmseg/models/loss/__init__.py` 中。 + +```python +from .my_loss import MyLoss, my_loss + +``` + +要使用它,请修改 `loss_xx` 字段。 +然后需要修改头中的 `loss_decode` 字段。 +`loss_weight` 可用于平衡多重损失。 + +```python +loss_decode=dict(type='MyLoss', loss_weight=1.0)) +``` + +### 添加新的数据预处理器(data preprocessor) + +在 MMSegmentation 1.x 版本中,我们使用 [SegDataPreProcessor](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/data_preprocessor.py#L13) 将数据复制到目标设备,并将数据预处理为默认的模型输入格式。这里我们将展示如何开发一个新的数据预处理器。 + +1. 创建一个新文件 `mmseg/models/my_datapreprocessor.py`。 + + ```python + from mmengine.model import BaseDataPreprocessor + + from mmseg.registry import MODELS + + @MODELS.register_module() + class MyDataPreProcessor(BaseDataPreprocessor): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def forward(self, data: dict, training: bool=False) -> Dict[str, Any]: + # TODO Define the logic for data pre-processing in the forward method + pass + ``` + +2. 在 `mmseg/models/__init__.py` 中导入数据预处理器 + + ```python + from .my_datapreprocessor import MyDataPreProcessor + ``` + +3. 在配置文件中使用它。 + + ```python + model = dict( + data_preprocessor=dict(type='MyDataPreProcessor) + ... + ) + ``` + +## 开发新的分割器(segmentor) + +分割器是一种户可以通过添加自定义组件和定义算法执行逻辑来自定义其算法的算法架构。请参考[模型文档](./models.md)了解更多详情。 + +由于 MMSegmentation 中的 [BaseSegmenter](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/models/segmentors/base.py#L15) 统一了前向过程的三种模式,为了开发新的分割器,用户需要重写与 `loss`、`predict` 和 `tensor` 相对应的 `loss`、`predict` 和 `_forward` 方法。 + +这里我们将展示如何开发一个新的分割器。 + +1. 创建一个新文件 `mmseg/models/segmentors/my_segmentor.py`。 + + ```python + from typing import Dict, Optional, Union + + import torch + + from mmseg.registry import MODELS + from mmseg.models import BaseSegmentor + + @MODELS.register_module() + class MySegmentor(BaseSegmentor): + def __init__(self, **kwargs): + super().__init__(**kwargs) + # TODO users should build components of the network here + + def loss(self, inputs: Tensor, data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples.""" + pass + + def predict(self, inputs: Tensor, data_samples: OptSampleList=None) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing.""" + pass + + def _forward(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> Tuple[List[Tensor]]: + """Network forward process. + + Usually includes backbone, neck and head forward without any post- + processing. + """ + pass + ``` + +2. 在 `mmseg/models/segmentors/__init__.py` 中导入分割器。 + + ```python + from .my_segmentor import MySegmentor + ``` + +3. 在配置文件中使用它。 + + ```python + model = dict( + type='MySegmentor' + ... + ) + ``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_transforms.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_transforms.md new file mode 100644 index 0000000..d720668 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/add_transforms.md @@ -0,0 +1,51 @@ +# 新增数据增强 + +## 自定义数据增强 + +自定义数据增强必须继承 `BaseTransform` 并实现 `transform` 函数。这里我们使用一个简单的翻转变换作为示例: + +```python +import random +import mmcv +from mmcv.transforms import BaseTransform, TRANSFORMS + +@TRANSFORMS.register_module() +class MyFlip(BaseTransform): + def __init__(self, direction: str): + super().__init__() + self.direction = direction + + def transform(self, results: dict) -> dict: + img = results['img'] + results['img'] = mmcv.imflip(img, direction=self.direction) + return results +``` + +此外,新的类需要被导入。 + +```python +from .my_pipeline import MyFlip +``` + +这样,我们就可以实例化一个 `MyFlip` 对象并使用它来处理数据字典。 + +```python +import numpy as np + +transform = MyFlip(direction='horizontal') +data_dict = {'img': np.random.rand(224, 224, 3)} +data_dict = transform(data_dict) +processed_img = data_dict['img'] +``` + +或者,我们可以在配置文件中的数据流程中使用 `MyFlip` 变换。 + +```python +pipeline = [ + ... + dict(type='MyFlip', direction='horizontal'), + ... +] +``` + +需要注意,如果要在配置文件中使用 `MyFlip`,必须确保在运行时导入了包含 `MyFlip` 的文件。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/contribute_dataset.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/contribute_dataset.md new file mode 100644 index 0000000..4222de3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/contribute_dataset.md @@ -0,0 +1,461 @@ +# 在 mmsegmentation projects 中贡献一个标准格式的数据集 + +- 在开始您的贡献流程前,请先阅读[《OpenMMLab 贡献代码指南》](https://mmcv.readthedocs.io/zh_CN/latest/community/contributing.html),以详细的了解 OpenMMLab 代码库的代码贡献流程。 +- 该教程以 [Gaofen Image Dataset (GID)](https://www.sciencedirect.com/science/article/pii/S0034425719303414) 高分 2 号卫星所拍摄的遥感图像语义分割数据集作为样例,来演示在 mmsegmentation 中的数据集贡献流程。 + +## 步骤 1: 配置 mmsegmentation 开发所需必要环境 + +- 开发所必需的环境安装请参考[中文快速入门指南](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/get_started.md)或[英文 get_started](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/en/get_started.md)。 + +- 如果您已安装了最新版的 pytorch、mmcv、mmengine,那么您可以跳过步骤 1 至[步骤 2](<#[步骤-2](#%E6%AD%A5%E9%AA%A4-2%E4%BB%A3%E7%A0%81%E8%B4%A1%E7%8C%AE%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C)>)。 + +- **注:** 在此处无需安装 mmsegmentation,只需安装开发 mmsegmentation 所必需的 pytorch、mmcv、mmengine 等即可。 + +**新建虚拟环境(如已有合适的开发环境,可跳过)** + +- 从[官方网站](https://docs.conda.io/en/latest/miniconda.html)下载并安装 Miniconda +- 创建一个 conda 环境,并激活 + +```shell +conda create --name openmmlab python=3.8 -y +conda activate openmmlab +``` + +**安装 pytorch (如环境下已安装 pytorch,可跳过)** + +- 参考 [official instructions](https://pytorch.org/get-started/locally/) 安装 **PyTorch** + +**使用 mim 安装 mmcv、mmengine** + +- 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMCV](https://github.com/open-mmlab/mmcv) + +```shell +pip install -U openmim +mim install mmengine +mim install "mmcv>=2.0.0" +``` + +## 步骤 2:代码贡献前的准备工作 + +### 2.1 Fork mmsegmentation 仓库 + +- 通过浏览器打开[mmsegmentation 官方仓库](https://github.com/open-mmlab/mmsegmentation/tree/main)。 +- 登录您的 GitHub 账户,以下步骤均需在 GitHub 登录的情况下进行。 +- Fork mmsegmentation 仓库 + ![image](https://user-images.githubusercontent.com/50650583/233825567-b8bf273c-38f5-4487-b4c6-75ede1e283ee.png) +- Fork 之后,mmsegmentation 仓库将会出现在您的个人仓库中。 + +### 2.2 在您的代码编写软件中 git clone mmsegmentation + +这里以 VSCODE 为例 + +- 打开 VSCODE,新建终端窗口并激活您在[步骤 1 ](#%E6%AD%A5%E9%AA%A4-1-%E9%85%8D%E7%BD%AE-mmsegmentation-%E5%BC%80%E5%8F%91%E6%89%80%E9%9C%80%E5%BF%85%E8%A6%81%E7%8E%AF%E5%A2%83)中所安装的虚拟环境。 +- 在您 GitHub 的个人仓库中找到您 Fork 的 mmsegmentation 仓库,复制其链接。 + ![image](https://github.com/AI-Tianlong/OpenMMLabCamp/assets/50650583/92ad555b-c5b2-4a7f-a800-ebee1e405ab6) +- 在终端中执行命令 + ```bash + git clone {您所复制的个人仓库的链接} + ``` + ![image](https://github.com/AI-Tianlong/OpenMMLabCamp/assets/50650583/23ba2636-e66f-4ea5-9077-9dd6b69deb1d) + **注:** 如提示以下信息,请在 GitHub 中添加 [SSH 秘钥](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) + ![image](https://github.com/AI-Tianlong/OpenMMLabCamp/assets/50650583/6fcab213-0739-483c-b345-c59656027377) +- 进入 mmsegmentation 目录(之后的操作均在 mmsegmentation 目录下)。 + ```bash + cd mmsegmentation + ``` +- 在终端中执行以下命令,添加官方仓库为上游仓库。 + ```bash + git remote add upstream git@github.com:open-mmlab/mmsegmentation.git + ``` +- 使用以下命令检查 remote 是否添加成功。 + ```bash + git remote -v + ``` + ![image](https://github.com/AI-Tianlong/OpenMMLabCamp/assets/50650583/beec7e5e-2b00-4e49-ab38-f0c79e346594) + +### 2.3 切换目录至 mmsegmentation 并从源码安装mmsegmentation + +在`mmsegmentation`目录下执行`pip install -v -e .`,通过源码构建方式安装 mmsegmentaion 库。 +安装完成后,您将能看到如下图所示的文件树。 +image + +### 2.4 切换分支为 dev-1.x + +正如您在[ mmsegmentation 官网](https://github.com/open-mmlab/mmsegmentation/tree/main)所见,该仓库有许多分支,默认分支`main`为稳定的发行版本,以及用于贡献者进行开发的`dev-1.x`分支。`dev-1.x`分支是贡献者们用来提交创意和 PR 的分支,`dev-1.x`分支的内容会被周期性的合入到`main`分支。 +![image](https://user-images.githubusercontent.com/50650583/233826225-f4b7299d-de23-47db-900d-dfb01ba0efc3.png) + +回到 VSCODE 中,在终端执行命令 + +```bash +git checkout dev-1.x +``` + +### 2.5 创新属于自己的新分支 + +在基于`dev-1.x`分支下,使用如下命令,创建属于您自己的分支。 + +```bash +# git checkout -b 您的GitHubID/您的分支想要实现的功能的名字 +# git checkout -b AI-Tianlong/support_GID_dataset +git checkout -b {您的GitHubID/您的分支想要实现的功能的名字} +``` + +### 2.6 配置 pre-commit + +OpenMMLab 仓库对代码质量有着较高的要求,所有提交的 PR 必须要通过代码格式检查。pre-commit 详细配置参阅[配置 pre-commit](https://mmcv.readthedocs.io/zh_CN/latest/community/contributing.html#pre-commit)。 + +## 步骤 3:在`mmsegmentation/projects`下贡献您的代码 + +**先对 GID 数据集进行分析** + +这里以贡献高分 2 号遥感图像语义分割数据集 GID 为例,GID 数据集是由我国自主研发的高分 2 号卫星所拍摄的光学遥感图像所创建,经图像预处理后共提供了 150 张 6800x7200 像素的 RGB 三通道遥感图像。并提供了两种不同类别数的数据标注,一种是包含 5 类有效物体的 RGB 标签,另一种是包含 15 类有效物体的 RGB 标签。本教程将针对 5 类标签进行数据集贡献流程讲解。 + +GID 的 5 类有效标签分别为:0-背景-\[0,0,0\](mask 标签值-标签名称-RGB 标签值)、1-建筑-\[255,0,0\]、2-农田-\[0,255,0\]、3-森林-\[0,0,255\]、4-草地-\[255,255,0\]、5-水-\[0,0,255\]。在语义分割任务中,标签是与原图尺寸一致的单通道图像,标签图像中的像素值为真实样本图像中对应像素所包含的物体的类别。GID 数据集提供的是具有 RGB 三通道的彩色标签,为了模型的训练需要将 RGB 标签转换为 mask 标签。并且由于图像尺寸为 6800x7200 像素,对于神经网络的训练来有些过大,所以将每张图像裁切成了没有重叠的 512x512 的图像以便进行训练。 +image + +### 3.1 在`mmsegmentation/projects`下创建新的项目文件夹 + +在`mmsegmentation/projects`下创建文件夹`gid_dataset` +![image](https://user-images.githubusercontent.com/50650583/233829687-8f2b6600-bc9d-48ff-a865-d462af54d55a.png) + +### 3.2 贡献您的数据集代码 + +为了最终能将您在 projects 中贡献的代码更加顺畅的移入核心库中(对代码要求质量更高),非常建议按照核心库的目录来编辑您的数据集文件。 +关于数据集有 4 个必要的文件: + +- **1** `mmseg/datasets/gid.py` 定义了数据集的尾缀、CLASSES、PALETTE、reduce_zero_label等 +- **2** `configs/_base_/gid.py` GID 数据集的配置文件,定义了数据集的`dataset_type`(数据集类型,`mmseg/datasets/gid.py`中注册的数据集的类名)、`data_root`(数据集所在的根目录,建议将数据集通过软连接的方式将数据集放至`mmsegmentation/data`)、`train_pipline`(训练的数据流)、`test_pipline`(测试和验证时的数据流)、`img_rations`(多尺度预测时的多尺度配置)、`tta_pipeline`(多尺度预测)、`train_dataloader`(训练集的数据加载器)、`val_dataloader`(验证集的数据加载器)、`test_dataloader`(测试集的数据加载器)、`val_evaluator`(验证集的评估器)、`test_evaluator`(测试集的评估器)。 +- **3** 使用了 GID 数据集的模型训练配置文件 + 这个是可选的,但是强烈建议您添加。在核心库中,所贡献的数据集需要和参考文献中所提出的结果精度对齐,为了后期将您贡献的代码合并入核心库。如您的算力充足,最好能提供对应的模型配置文件在您贡献的数据集上所验证的结果以及相应的权重文件,并撰写较为详细的README.md文档。[示例参考结果](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/deeplabv3plus#mapillary-vistas-v12) + ![image](https://user-images.githubusercontent.com/50650583/233877682-eabe8723-bce9-40e4-a303-08c8385cb6b5.png) +- **4** 使用如下命令格式: 撰写`docs/zh_cn/user_guides/2_dataset_prepare.md`来添加您的数据集介绍,包括但不限于数据集的下载方式,数据集目录结构、数据集生成等一些必要性的文字性描述和运行命令。以更好地帮助用户能更快的实现数据集的准备工作。 + +### 3.3 贡献`tools/dataset_converters/gid.py` + +由于 GID 数据集是由未经过切分的 6800x7200 图像所构成的数据集,并且没有划分训练集、验证集与测试集。以及其标签为 RGB 彩色标签,需要将标签转换为单通道的 mask label。为了方便训练,首先将 GID 数据集进行裁切和标签转换,并进行数据集划分,构建为 mmsegmentation 所支持的格式。 + +```python +# tools/dataset_converters/gid.py +import argparse +import glob +import math +import os +import os.path as osp +from PIL import Image + +import mmcv +import numpy as np +from mmengine.utils import ProgressBar, mkdir_or_exist + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert GID dataset to mmsegmentation format') + parser.add_argument('dataset_img_path', help='GID images folder path') + parser.add_argument('dataset_label_path', help='GID labels folder path') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path', default='data/gid') + parser.add_argument( + '--clip_size', + type=int, + help='clipped size of image after preparation', + default=256) + parser.add_argument( + '--stride_size', + type=int, + help='stride of clipping original images', + default=256) + args = parser.parse_args() + return args + +GID_COLORMAP = dict( + Background=(0, 0, 0), #0-背景-黑色 + Building=(255, 0, 0), #1-建筑-红色 + Farmland=(0, 255, 0), #2-农田-绿色 + Forest=(0, 0, 255), #3-森林-蓝色 + Meadow=(255, 255, 0),#4-草地-黄色 + Water=(0, 0, 255)#5-水-蓝色 +) +palette = list(GID_COLORMAP.values()) +classes = list(GID_COLORMAP.keys()) + +#############用列表来存一个 RGB 和一个类别的对应################ +def colormap2label(palette): + colormap2label_list = np.zeros(256**3, dtype = np.longlong) + for i, colormap in enumerate(palette): + colormap2label_list[(colormap[0] * 256 + colormap[1])*256+colormap[2]] = i + return colormap2label_list + +#############给定那个列表,和vis_png然后生成masks_png################ +def label_indices(RGB_label, colormap2label_list): + RGB_label = RGB_label.astype('int32') + idx = (RGB_label[:, :, 0] * 256 + RGB_label[:, :, 1]) * 256 + RGB_label[:, :, 2] + # print(idx.shape) + return colormap2label_list[idx] + +def RGB2mask(RGB_label, colormap2label_list): + # RGB_label = np.array(Image.open(RGB_label).convert('RGB')) #打开RGB_png + mask_label = label_indices(RGB_label, colormap2label_list) # .numpy() + return mask_label + +colormap2label_list = colormap2label(palette) + +def clip_big_image(image_path, clip_save_dir, args, to_label=False): + """ + Original image of GID dataset is very large, thus pre-processing + of them is adopted. Given fixed clip size and stride size to generate + clipped image, the intersection of width and height is determined. + For example, given one 6800 x 7200 original image, the clip size is + 256 and stride size is 256, thus it would generate 29 x 27 = 783 images + whose size are all 256 x 256. + + """ + + image = mmcv.imread(image_path, channel_order='rgb') + # image = mmcv.bgr2gray(image) + + h, w, c = image.shape + clip_size = args.clip_size + stride_size = args.stride_size + + num_rows = math.ceil((h - clip_size) / stride_size) if math.ceil( + (h - clip_size) / + stride_size) * stride_size + clip_size >= h else math.ceil( + (h - clip_size) / stride_size) + 1 + num_cols = math.ceil((w - clip_size) / stride_size) if math.ceil( + (w - clip_size) / + stride_size) * stride_size + clip_size >= w else math.ceil( + (w - clip_size) / stride_size) + 1 + + x, y = np.meshgrid(np.arange(num_cols + 1), np.arange(num_rows + 1)) + xmin = x * clip_size + ymin = y * clip_size + + xmin = xmin.ravel() + ymin = ymin.ravel() + xmin_offset = np.where(xmin + clip_size > w, w - xmin - clip_size, + np.zeros_like(xmin)) + ymin_offset = np.where(ymin + clip_size > h, h - ymin - clip_size, + np.zeros_like(ymin)) + boxes = np.stack([ + xmin + xmin_offset, ymin + ymin_offset, + np.minimum(xmin + clip_size, w), + np.minimum(ymin + clip_size, h) + ], axis=1) + + if to_label: + image = RGB2mask(image, colormap2label_list) #这里得改一下 + + for count, box in enumerate(boxes): + start_x, start_y, end_x, end_y = box + clipped_image = image[start_y:end_y, + start_x:end_x] if to_label else image[ + start_y:end_y, start_x:end_x, :] + img_name = osp.basename(image_path).replace('.tif', '') + img_name = img_name.replace('_label', '') + if count % 3 == 0: + mmcv.imwrite( + clipped_image.astype(np.uint8), + osp.join( + clip_save_dir.replace('train', 'val'), + f'{img_name}_{start_x}_{start_y}_{end_x}_{end_y}.png')) + else: + mmcv.imwrite( + clipped_image.astype(np.uint8), + osp.join( + clip_save_dir, + f'{img_name}_{start_x}_{start_y}_{end_x}_{end_y}.png')) + count += 1 + +def main(): + args = parse_args() + + """ + According to this paper: https://ieeexplore.ieee.org/document/9343296/ + select 15 images contained in GID, , which cover the whole six + categories, to generate train set and validation set. + + According to Paper: https://ieeexplore.ieee.org/document/9343296/ + + """ + + if args.out_dir is None: + out_dir = osp.join('data', 'gid') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'val')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'val')) + + src_path_list = glob.glob(os.path.join(args.dataset_img_path, '*.tif')) + print(f'Find {len(src_path_list)} pictures') + + prog_bar = ProgressBar(len(src_path_list)) + + dst_img_dir = osp.join(out_dir, 'img_dir', 'train') + dst_label_dir = osp.join(out_dir, 'ann_dir', 'train') + + for i, img_path in enumerate(src_path_list): + label_path = osp.join(args.dataset_label_path, osp.basename(img_path.replace('.tif', '_label.tif'))) + + clip_big_image(img_path, dst_img_dir, args, to_label=False) + clip_big_image(label_path, dst_label_dir, args, to_label=True) + prog_bar.update() + + print('Done!') + +if __name__ == '__main__': + main() +``` + +### 3.4 贡献`mmseg/datasets/gid.py` + +可参考[`projects/mapillary_dataset/mmseg/datasets/mapillary.py`](https://github.com/open-mmlab/mmsegmentation/blob/main/projects/mapillary_dataset/mmseg/datasets/mapillary.py)并在此基础上修改相应变量以适配您的数据集。 + +```python +# mmseg/datasets/gid.py +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.datasets.basesegdataset import BaseSegDataset +from mmseg.registry import DATASETS + +# 注册数据集类 +@DATASETS.register_module() +class GID_Dataset(BaseSegDataset): + """Gaofen Image Dataset (GID) + + Dataset paper link: + https://www.sciencedirect.com/science/article/pii/S0034425719303414 + https://x-ytong.github.io/project/GID.html + + GID 6 classes: background(others), built-up, farmland, forest, meadow, water + + In This example, select 10 images from GID dataset as training set, + and select 5 images as validation set. + The selected images are listed as follows: + + GF2_PMS1__L1A0000647767-MSS1 + GF2_PMS1__L1A0001064454-MSS1 + GF2_PMS1__L1A0001348919-MSS1 + GF2_PMS1__L1A0001680851-MSS1 + GF2_PMS1__L1A0001680853-MSS1 + GF2_PMS1__L1A0001680857-MSS1 + GF2_PMS1__L1A0001757429-MSS1 + GF2_PMS2__L1A0000607681-MSS2 + GF2_PMS2__L1A0000635115-MSS2 + GF2_PMS2__L1A0000658637-MSS2 + GF2_PMS2__L1A0001206072-MSS2 + GF2_PMS2__L1A0001471436-MSS2 + GF2_PMS2__L1A0001642620-MSS2 + GF2_PMS2__L1A0001787089-MSS2 + GF2_PMS2__L1A0001838560-MSS2 + + The ``img_suffix`` is fixed to '.tif' and ``seg_map_suffix`` is + fixed to '.tif' for GID. + """ + METAINFO = dict( + classes=('Others', 'Built-up', 'Farmland', 'Forest', + 'Meadow', 'Water'), + + palette=[[0, 0, 0], [255, 0, 0], [0, 255, 0], [0, 255, 255], + [255, 255, 0], [0, 0, 255]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=None, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) +``` + +### 3.5 贡献使用 GID 的训练 config file + +```python +_base_ = [ + '../../../configs/_base_/models/deeplabv3plus_r50-d8.py', + './_base_/datasets/gid.py', + '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_240k.py' +] +custom_imports = dict( + imports=['projects.gid_dataset.mmseg.datasets.gid']) + +crop_size = (256, 256) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101), + decode_head=dict(num_classes=6), + auxiliary_head=dict(num_classes=6)) + +``` + +### 3.6 撰写`docs/zh_cn/user_guides/2_dataset_prepare.md` + +**Gaofen Image Dataset (GID)** + +- GID 数据集可在[此处](https://x-ytong.github.io/project/Five-Billion-Pixels.html)进行下载。 +- GID 数据集包含 150 张 6800x7200 的大尺寸图像,标签为 RGB 标签。 +- 此处选择 15 张图像生成训练集和验证集,该 15 张图像包含了所有六类信息。所选的图像名称如下: + +```None + GF2_PMS1__L1A0000647767-MSS1 + GF2_PMS1__L1A0001064454-MSS1 + GF2_PMS1__L1A0001348919-MSS1 + GF2_PMS1__L1A0001680851-MSS1 + GF2_PMS1__L1A0001680853-MSS1 + GF2_PMS1__L1A0001680857-MSS1 + GF2_PMS1__L1A0001757429-MSS1 + GF2_PMS2__L1A0000607681-MSS2 + GF2_PMS2__L1A0000635115-MSS2 + GF2_PMS2__L1A0000658637-MSS2 + GF2_PMS2__L1A0001206072-MSS2 + GF2_PMS2__L1A0001471436-MSS2 + GF2_PMS2__L1A0001642620-MSS2 + GF2_PMS2__L1A0001787089-MSS2 + GF2_PMS2__L1A0001838560-MSS2 +``` + +执行以下命令进行裁切及标签的转换,需要修改为您所存储 15 张图像及标签的路径。 + +``` +python projects/gid_dataset/tools/dataset_converters/gid.py [15 张图像的路径] [15 张标签的路径] +``` + +完成裁切后的 GID 数据结构如下: + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── gid +│ │ ├── ann_dir +| │ │ │ ├── train +| │ │ │ ├── val +│ │ ├── img_dir +| │ │ │ ├── train +| │ │ │ ├── val + +``` + +### 3.7 贡献的代码及文档通过`pre-commit`检查 + +使用命令 + +```bash +git add . +git commit -m "添加描述" +git push +``` + +### 3.8 在 GitHub 中向 mmsegmentation 提交 PR + +具体步骤可见[《OpenMMLab 贡献代码指南》](https://mmcv.readthedocs.io/zh_CN/latest/community/contributing.html) diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/customize_runtime.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/customize_runtime.md new file mode 100644 index 0000000..a80aca6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/customize_runtime.md @@ -0,0 +1,162 @@ +# 自定义运行设定 + +## 实现自定义钩子 + +### Step 1: 创建一个新的钩子 + +MMEngine 已实现了训练和测试常用的[钩子](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/hook.md), +当有定制化需求时, 可以按照如下示例实现适用于自身训练需求的钩子, 例如想修改一个超参数 `model.hyper_paramete` 的值, 让它随着训练迭代次数而变化: + +```python +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence + +from mmengine.hooks import Hook +from mmengine.model import is_model_wrapper + +from mmseg.registry import HOOKS + + +@HOOKS.register_module() +class NewHook(Hook): + """Docstring for NewHook. + """ + + def __init__(self, a: int, b: int) -> None: + self.a = a + self.b = b + + def before_train_iter(self, + runner, + batch_idx: int, + data_batch: Optional[Sequence[dict]] = None) -> None: + cur_iter = runner.iter + # 当模型被包在 wrapper 里时获取这个模型 + if is_model_wrapper(runner.model): + model = runner.model.module + model.hyper_parameter = self.a * cur_iter + self.b +``` + +### Step 2: 导入一个新的钩子 + +为了让上面定义的模块可以被执行的程序发现, 这个模块需要先被导入主命名空间 (main namespace) 里面, +假设 NewHook 在 `mmseg/engine/hooks/new_hook.py` 里面, 有两种方式去实现它: + +- 修改 `mmseg/engine/hooks/__init__.py` 来导入它. + 新定义的模块应该在 `mmseg/engine/hooks/__init__.py` 里面导入, 这样注册器可以发现并添加这个新的模块: + +```python +from .new_hook import NewHook + +__all__ = [..., NewHook] +``` + +- 在配置文件里使用 custom_imports 来手动导入它. + +```python +custom_imports = dict(imports=['mmseg.engine.hooks.new_hook'], allow_failed_imports=False) +``` + +### Step 3: 修改配置文件 + +可以按照如下方式, 在训练或测试中配置并使用自定义的钩子. 不同钩子在同一位点的优先级可以参考[这里](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/hook.md#%E5%86%85%E7%BD%AE%E9%92%A9%E5%AD%90), 自定义钩子如果没有指定优先, 默认是 `NORMAL`. + +```python +custom_hooks = [ + dict(type='NewHook', a=a_value, b=b_value, priority='ABOVE_NORMAL') +] +``` + +## 实现自定义优化器 + +### Step 1: 创建一个新的优化器 + +如果增加一个叫作 `MyOptimizer` 的优化器, 它有参数 `a`, `b` 和 `c`. 推荐在 `mmseg/engine/optimizers/my_optimizer.py` 文件中实现 + +```python +from mmseg.registry import OPTIMIZERS +from torch.optim import Optimizer + + +@OPTIMIZERS.register_module() +class MyOptimizer(Optimizer): + + def __init__(self, a, b, c) +``` + +### Step 2: 导入一个新的优化器 + +为了让上面定义的模块可以被执行的程序发现, 这个模块需要先被导入主命名空间 (main namespace) 里面, +假设 `MyOptimizer` 在 `mmseg/engine/optimizers/my_optimizer.py` 里面, 有两种方式去实现它: + +- 修改 `mmseg/engine/optimizers/__init__.py` 来导入它. + 新定义的模块应该在 `mmseg/engine/optimizers/__init__.py` 里面导入, 这样注册器可以发现并添加这个新的模块: + +```python +from .my_optimizer import MyOptimizer +``` + +- 在配置文件里使用 `custom_imports` 来手动导入它. + +```python +custom_imports = dict(imports=['mmseg.engine.optimizers.my_optimizer'], allow_failed_imports=False) +``` + +### Step 3: 修改配置文件 + +随后需要修改配置文件 `optim_wrapper` 里的 `optimizer` 参数, 如果要使用你自己的优化器 `MyOptimizer`, 字段可以被修改成: + +```python +optim_wrapper = dict(type='OptimWrapper', + optimizer=dict(type='MyOptimizer', + a=a_value, b=b_value, c=c_value), + clip_grad=None) +``` + +## 实现自定义优化器封装构造器 + +### Step 1: 创建一个新的优化器封装构造器 + +构造器可以用来创建优化器, 优化器包, 以及自定义模型网络不同层的超参数. 一些模型的优化器可能会根据特定的参数而调整, 例如 BatchNorm 层的 weight decay. 使用者可以通过自定义优化器构造器来精细化设定不同参数的优化策略. + +```python +from mmengine.optim import DefaultOptimWrapperConstructor +from mmseg.registry import OPTIM_WRAPPER_CONSTRUCTORS + +@OPTIM_WRAPPER_CONSTRUCTORS.register_module() +class LearningRateDecayOptimizerConstructor(DefaultOptimWrapperConstructor): + def __init__(self, optim_wrapper_cfg, paramwise_cfg=None): + + def __call__(self, model): + + return my_optimizer +``` + +默认的优化器构造器在[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L19) 被实现, 它也可以用来作为新的优化器构造器的模板. + +### Step 2: 导入一个新的优化器封装构造器 + +为了让上面定义的模块可以被执行的程序发现, 这个模块需要先被导入主命名空间 (main namespace) 里面, 假设 `MyOptimizerConstructor` 在 `mmseg/engine/optimizers/my_optimizer_constructor.py` 里面, 有两种方式去实现它: + +- 修改 `mmseg/engine/optimizers/__init__.py` 来导入它. + 新定义的模块应该在 `mmseg/engine/optimizers/__init__.py` 里面导入, 这样注册器可以发现并添加这个新的模块: + +```python +from .my_optimizer_constructor import MyOptimizerConstructor +``` + +- 在配置文件里使用 `custom_imports` 来手动导入它. + +```python +custom_imports = dict(imports=['mmseg.engine.optimizers.my_optimizer_constructor'], allow_failed_imports=False) +``` + +### Step 3: 修改配置文件 + +随后需要修改配置文件 `optim_wrapper` 里的 `constructor` 参数, 如果要使用你自己的优化器封装构造器 `MyOptimizerConstructor`, 字段可以被修改成: + +```python +optim_wrapper = dict(type='OptimWrapper', + constructor='MyOptimizerConstructor', + clip_grad=None) +``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/data_flow.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/data_flow.md new file mode 100644 index 0000000..20dbe07 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/data_flow.md @@ -0,0 +1,90 @@ +# 数据流 + +在本章节中,我们将介绍 [Runner](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/runner.html) 管理的内部模块之间的数据流和数据格式约定。 + +## 数据流概述 + +[Runner](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/design/runner.md) 相当于 MMEngine 中的“集成器”。它覆盖了框架的所有方面,并肩负着组织和调度几乎所有模块的责任,这意味着各模块之间的数据流也由 `Runner` 控制。 如 [MMEngine 中的 Runner 文档](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/runner.html)所示,下图展示了基本的数据流。 + +![Basic dataflow](https://user-images.githubusercontent.com/112053249/199228350-5f80699e-7fd2-4b4c-ac32-0b16b1922c2e.png) + +虚线边框、灰色填充形状代表不同的数据格式,而实心框表示模块/方法。由于 MMEngine 极大的灵活性和可扩展性,一些重要的基类可以被继承,并且它们的方法可以被覆写。 上图所示数据流仅适用于当用户没有自定义 `Runner` 中的 `TrainLoop`、`ValLoop` 和 `TestLoop`,并且没有在其自定义模型中覆写 `train_step`、`val_step` 和 `test_step` 方法时。MMSegmentation 中 loop 的默认设置如下:使用`IterBasedTrainLoop` 训练模型,共计 20000 次迭代,并且在每 2000 次迭代后进行一次验证。 + +```python +train_cfg = dict(type='IterBasedTrainLoop', max_iters=20000, val_interval=2000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +``` + +在上图中,红色线表示 [train_step](./models.md#train_step),在每次训练迭代中,数据加载器(dataloader)从存储中加载图像并传输到数据预处理器(data preprocessor),数据预处理器会将图像放到特定的设备上,并将数据堆叠到批处理中,之后模型接受批处理数据作为输入,最后将模型的输出发送给优化器(optimizer)。蓝色线表示 [val_step](./models.md#val_step) 和 [test_step](./models.md#test_step)。这两个过程的数据流除了模型输出与 `train_step` 不同外,其余均和 `train_step` 类似。由于在评估时模型参数会被冻结,因此模型的输出将被传递给 [Evaluator](./evaluation.md#ioumetric)。 +来计算指标。 + +## MMSegmentation 中的数据流约定 + +在上面的图中,我们可以看到基本的数据流。在本节中,我们将分别介绍数据流中涉及的数据的格式约定。 + +### 数据加载器到数据预处理器 + +数据加载器(DataLoader)是 MMEngine 的训练和测试流程中的一个重要组件。 +从概念上讲,它源于 [PyTorch](https://pytorch.org/) 并保持一致。DataLoader 从文件系统加载数据,原始数据通过数据准备流程后被发送给数据预处理器。 + +MMSegmentation 在 [PackSegInputs](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/transforms/formatting.py#L12) 中定义了默认数据格式, 它是 `train_pipeline` 和 `test_pipeline` 的最后一个组件。有关数据转换 `pipeline` 的更多信息,请参阅[数据转换文档](./transforms.md)。 + +在没有任何修改的情况下,PackSegInputs 的返回值通常是一个包含 `inputs` 和 `data_samples` 的 `dict`。以下伪代码展示了 mmseg 中数据加载器输出的数据类型,它是从数据集中获取的一批数据样本,数据加载器将它们打包成一个字典列表。`inputs` 是输入进模型的张量列表,`data_samples` 包含了输入图像的 meta information 和相应的 ground truth。 + +```python +dict( + inputs=List[torch.Tensor], + data_samples=List[SegDataSample] +) +``` + +**注意:** [SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py) 是 MMSegmentation 的数据结构接口,用于连接不同组件。`SegDataSample` 实现了抽象数据元素 `mmengine.structures.BaseDataElement`,更多信息请在 [MMEngine](https://github.com/open-mmlab/mmengine) 中参阅 [SegDataSample 文档](./structures.md)和[数据元素文档](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/data_element.html)。 + +### 数据预处理器到模型 + +虽然在[上面的图](##数据流概述)中分开绘制了数据预处理器和模型,但数据预处理器是模型的一部分,因此可以在[模型教程](./models.md)中找到数据预处理器章节。 + +数据预处理器的返回值是一个包含 `inputs` 和 `data_samples` 的字典,其中 `inputs` 是批处理图像的 4D 张量,`data_samples` 中添加了一些用于数据预处理的额外元信息。当传递给网络时,字典将被解包为两个值。 以下伪代码展示了数据预处理器的返回值和模型的输入值。 + +```python +dict( + inputs=torch.Tensor, + data_samples=List[SegDataSample] +) +``` + +```python +class Network(BaseSegmentor): + + def forward(self, inputs: torch.Tensor, data_samples: List[SegDataSample], mode: str): + pass +``` + +**注意:** 模型的前向传播有 3 种模式,由输入参数 mode 控制,更多信息请参阅[模型教程](./models.md)。 + +### 模型输出 + +如[模型教程](./models.md#forward) ***([中文链接待更新](./models.md#forward))*** 所提到的 3 种前向传播具有 3 种输出。 +`train_step` 和 `test_step`(或 `val_step`)分别对应于 `'loss'` 和 `'predict'`。 + +在 `test_step` 或 `val_step` 中,推理结果会被传递给 `Evaluator` 。您可以参阅[评估文档](./evaluation.md)来获取更多关于 `Evaluator` 的信息。 + +在推理后,MMSegmentation 中的 [BaseSegmentor](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/segmentors/base.py#L15) 会对推理结果进行简单的后处理以打包推理结果。神经网络生成的分割 logits,经过 `argmax` 操作后的分割 mask 和 ground truth(如果存在)将被打包到类似 `SegDataSample` 的实例。 [postprocess_result](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/segmentors/base.py#L132) 的返回值是一个 **`SegDataSample`的`List`**。下图显示了这些 `SegDataSample` 实例的关键属性。 + +![SegDataSample](https://user-images.githubusercontent.com/15952744/209912225-ab46a8d9-904a-43cb-8bf1-8bec4938ed29.png) + +与数据预处理器一致,损失函数也是模型的一部分,它是[解码头](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/decode_heads/decode_head.py#L142)的属性之一。 + +在 MMSegmentation 中,`decode_head` 的 [loss_by_feat](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/models/decode_heads/decode_head.py#L291) 方法是用于计算损失的统一接口。 + +参数: + +- seg_logits (Tensor):解码头前向函数的输出 +- batch_data_samples (List\[SegDataSample\]):分割数据样本,通常包括如 `metainfo` 和 `gt_sem_seg` 等信息 + +返回值: + +- dict\[str, Tensor\]:一个损失组件的字典 + +**注意:** `train_step` 将损失传递进 OptimWrapper 以更新模型中的权重,更多信息请参阅 [train_step](./models.md#train_step)。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/datasets.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/datasets.md new file mode 100644 index 0000000..b45f2d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/datasets.md @@ -0,0 +1,363 @@ +# 数据集 + +在 MMSegmentation 算法库中, 所有 Dataset 类的功能有两个: 加载[预处理](../user_guides/2_dataset_prepare.md) 之后的数据集的信息, 和将数据送入[数据集变换流水线](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/basesegdataset.py#L141) 中, 进行[数据变换操作](./transforms.md). 加载的数据集信息包括两类: 元信息 (meta information), 数据集本身的信息, 例如数据集总共的类别, 和它们对应调色盘信息: 数据信息 (data information) 是指每组数据中图片和对应标签的路径. 下文中介绍了 MMSegmentation 1.x 中数据集的常用接口, 和 mmseg 数据集基类中数据信息加载与修改数据集类别的逻辑, 以及数据集与数据变换流水线 (pipeline) 的关系. + +## 常用接口 + +以 Cityscapes 为例, 介绍数据集常用接口. 如需运行以下示例, 请在当前工作目录下的 `data` 目录下载并[预处理](../user_guides/2_dataset_prepare.md#cityscapes) Cityscapes 数据集. + +实例化 Cityscapes 训练数据集: + +```python +from mmengine.registry import init_default_scope +from mmseg.datasets import CityscapesDataset + +init_default_scope('mmseg') + +data_root = 'data/cityscapes/' +data_prefix=dict(img_path='leftImg8bit/train', seg_map_path='gtFine/train') +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='RandomCrop', crop_size=(512, 1024), cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PackSegInputs') +] + +dataset = CityscapesDataset(data_root=data_root, data_prefix=data_prefix, test_mode=False, pipeline=train_pipeline) +``` + +查看训练数据集长度: + +```python +print(len(dataset)) + +2975 +``` + +获取数据信息, 数据信息的类型是一个字典, 包括 `'img_path'` 字段的存放图片的路径和 `'seg_map_path'` 字段存放分割标注的路径, 以及标签重映射的字段 `'label_map'` 和 `'reduce_zero_label'`(主要功能在下文中介绍), 还有存放已加载标签字段 `'seg_fields'`, 和当前样本的索引字段 `'sample_idx'`. + +```python +# 获取数据集中第一组样本的数据信息 +print(dataset.get_data_info(0)) + +{'img_path': 'data/cityscapes/leftImg8bit/train/aachen/aachen_000000_000019_leftImg8bit.png', + 'seg_map_path': 'data/cityscapes/gtFine/train/aachen/aachen_000000_000019_gtFine_labelTrainIds.png', + 'label_map': None, + 'reduce_zero_label': False, + 'seg_fields': [], + 'sample_idx': 0} +``` + +获取数据集元信息, MMSegmentation 的数据集元信息的类型同样是一个字典, 包括 `'classes'` 字段存放数据集类别, `'palette'` 存放数据集类别对应的可视化时调色盘的颜色, 以及标签重映射的字段 `'label_map'` 和 `'reduce_zero_label'`. + +```python +print(dataset.metainfo) + +{'classes': ('road', + 'sidewalk', + 'building', + 'wall', + 'fence', + 'pole', + 'traffic light', + 'traffic sign', + 'vegetation', + 'terrain', + 'sky', + 'person', + 'rider', + 'car', + 'truck', + 'bus', + 'train', + 'motorcycle', + 'bicycle'), + 'palette': [[128, 64, 128], + [244, 35, 232], + [70, 70, 70], + [102, 102, 156], + [190, 153, 153], + [153, 153, 153], + [250, 170, 30], + [220, 220, 0], + [107, 142, 35], + [152, 251, 152], + [70, 130, 180], + [220, 20, 60], + [255, 0, 0], + [0, 0, 142], + [0, 0, 70], + [0, 60, 100], + [0, 80, 100], + [0, 0, 230], + [119, 11, 32]], + 'label_map': None, + 'reduce_zero_label': False} +``` + +数据集 `__getitem__` 方法的返回值, 是经过数据增强的样本数据的输出, 同样也是一个字典, 包括两个字段, `'inputs'` 字段是当前样本经过数据增强操作的图像, 类型为 torch.Tensor, `'data_samples'` 字段存放的数据类型是 MMSegmentation 1.x 新添加的数据结构 [`Segdatasample`](./structures.md), 其中`gt_sem_seg` 字段是经过数据增强的标签数据. + +```python +print(dataset[0]) + +{'inputs': tensor([[[131, 130, 130, ..., 23, 23, 23], + [132, 132, 132, ..., 23, 22, 23], + [134, 133, 133, ..., 23, 23, 23], + ..., + [ 66, 67, 67, ..., 71, 71, 71], + [ 66, 67, 66, ..., 68, 68, 68], + [ 67, 67, 66, ..., 70, 70, 70]], + + [[143, 143, 142, ..., 28, 28, 29], + [145, 145, 145, ..., 28, 28, 29], + [145, 145, 145, ..., 27, 28, 29], + ..., + [ 75, 75, 76, ..., 80, 81, 81], + [ 75, 76, 75, ..., 80, 80, 80], + [ 77, 76, 76, ..., 82, 82, 82]], + + [[126, 125, 126, ..., 21, 21, 22], + [127, 127, 128, ..., 21, 21, 22], + [127, 127, 126, ..., 21, 21, 22], + ..., + [ 63, 63, 64, ..., 69, 69, 70], + [ 64, 65, 64, ..., 69, 69, 69], + [ 65, 66, 66, ..., 72, 71, 71]]], dtype=torch.uint8), + 'data_samples': + _gt_sem_seg: + )} +``` + +## BaseSegDataset + +由于 MMSegmentation 中的所有数据集的基本功能均包括(1) 加载[数据集预处理](../user_guides/2_dataset_prepare.md) 之后的数据信息和 (2) 将数据送入数据变换流水线中进行数据变换, 因此在 MMSegmentation 中将其中的共同接口抽象成 [`BaseSegDataset`](https://mmsegmentation.readthedocs.io/zh_CN/latest/api.html?highlight=BaseSegDataset#mmseg.datasets.BaseSegDataset),它继承自 [MMEngine 的 `BaseDataset`](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/basedataset.md), 遵循 OpenMMLab 数据集初始化统一流程, 支持高效的内部数据存储格式, 支持数据集拼接、数据集重复采样等功能. +在 MMSegmentation BaseSegDataset 中重新定义了**数据信息加载方法**(`load_data_list`)和并新增了 `get_label_map` 方法用来**修改数据集的类别信息**. + +### 数据信息加载 + +数据信息加载的内容是样本数据的图片路径和标签路径, 具体实现在 MMSegmentation 的 BaseSegDataset 的 [`load_data_list`](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/basesegdataset.py#L231) 中. +主要有两种获取图片和标签的路径方法, 如果当数据集目录按以下目录结构组织, [`load_data_list`](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/basesegdataset.py#L231)) 会根据数据路径和后缀来解析. + +``` +├── data +│ ├── my_dataset +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ │ ├── xxx{img_suffix} +│ │ │ │ ├── yyy{img_suffix} +│ │ │ ├── val +│ │ │ │ ├── zzz{img_suffix} +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ │ ├── xxx{seg_map_suffix} +│ │ │ │ ├── yyy{seg_map_suffix} +│ │ │ ├── val +│ │ │ │ ├── zzz{seg_map_suffix} +``` + +例如 ADE20k 数据集结构如下所示: + +``` +├── ade +│ ├── ADEChallengeData2016 +│ │ ├── annotations +│ │ │ ├── training +│ │ │ │ ├── ADE_train_00000001.png +│ │ │ │ ├── ... +│ │ │ │── validation +│ │ │ │ ├── ADE_val_00000001.png +│ │ │ │ ├── ... +│ │ ├── images +│ │ │ ├── training +│ │ │ │ ├── ADE_train_00000001.jpg +│ │ │ │ ├── ... +│ │ │ ├── validation +│ │ │ │ ├── ADE_val_00000001.jpg +│ │ │ │ ├── ... +``` + +实例化 ADE20k 数据集时,输入图片和标签的路径和后缀: + +```python +from mmseg.datasets import ADE20KDataset + +ADE20KDataset(data_root = 'data/ade/ADEChallengeData2016', + data_prefix=dict(img_path='images/training', seg_map_path='annotations/training'), + img_suffix='.jpg', + seg_map_suffix='.png', + reduce_zero_label=True) +``` + +如果数据集有标注文件, 实例化数据集时会根据输入的数据集标注文件加载数据信息. 例如, PascalContext 数据集实例, 输入标注文件的内容为: + +```python +2008_000008 +... +``` + +实例化时需要定义 `ann_file` + +```python +PascalContextDataset(data_root='data/VOCdevkit/VOC2010/', + data_prefix=dict(img_path='JPEGImages', seg_map_path='SegmentationClassContext'), + ann_file='ImageSets/SegmentationContext/train.txt') +``` + +### 数据集类别修改 + +- 通过输入 metainfo 修改 + `BaseSegDataset` 的子类元信息在数据集实现时定义为类变量,例如 Cityscapes 的 `METAINFO` 变量: + +```python +class CityscapesDataset(BaseSegDataset): + """Cityscapes dataset. + + The ``img_suffix`` is fixed to '_leftImg8bit.png' and ``seg_map_suffix`` is + fixed to '_gtFine_labelTrainIds.png' for Cityscapes dataset. + """ + METAINFO = dict( + classes=('road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', + 'sky', 'person', 'rider', 'car', 'truck', 'bus', 'train', + 'motorcycle', 'bicycle'), + palette=[[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, + 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], + [220, 20, 60], [255, 0, 0], [0, 0, 142], [0, 0, 70], + [0, 60, 100], [0, 80, 100], [0, 0, 230], [119, 11, 32]]) + +``` + +这里的 `'classes'` 中定义了 Cityscapes 数据集标签中的类别名, 如果训练时只关注几个交通工具类别, **忽略其他类别**, +在实例化 Cityscapes 数据集时通过定义 `metainfo` 输入参数的 classes 的字段来修改数据集的元信息: + +```python +from mmseg.datasets import CityscapesDataset + +data_root = 'data/cityscapes/' +data_prefix=dict(img_path='leftImg8bit/train', seg_map_path='gtFine/train') +# metainfo 中只保留以下 classes +metainfo=dict(classes=( 'car', 'truck', 'bus', 'train', 'motorcycle', 'bicycle')) +dataset = CityscapesDataset(data_root=data_root, data_prefix=data_prefix, metainfo=metainfo) + +print(dataset.metainfo) + +{'classes': ('car', 'truck', 'bus', 'train', 'motorcycle', 'bicycle'), + 'palette': [[0, 0, 142], + [0, 0, 70], + [0, 60, 100], + [0, 80, 100], + [0, 0, 230], + [119, 11, 32], + [128, 64, 128], + [244, 35, 232], + [70, 70, 70], + [102, 102, 156], + [190, 153, 153], + [153, 153, 153], + [250, 170, 30], + [220, 220, 0], + [107, 142, 35], + [152, 251, 152], + [70, 130, 180], + [220, 20, 60], + [255, 0, 0]], + # 类别索引为 255 的像素,在计算损失时会被忽略 + 'label_map': {0: 255, + 1: 255, + 2: 255, + 3: 255, + 4: 255, + 5: 255, + 6: 255, + 7: 255, + 8: 255, + 9: 255, + 10: 255, + 11: 255, + 12: 255, + 13: 0, + 14: 1, + 15: 2, + 16: 3, + 17: 4, + 18: 5}, + 'reduce_zero_label': False} +``` + +可以看到, 数据集元信息的类别和默认 Cityscapes 不同. 并且, 定义了标签重映射的字段 `label_map` 用来修改每个分割掩膜上的像素的类别索引, 分割标签类别会根据 `label_map`, 将类别重映射, [具体实现](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/datasets/basesegdataset.py#L151): + +```python +gt_semantic_seg_copy = gt_semantic_seg.copy() +for old_id, new_id in results['label_map'].items(): + gt_semantic_seg[gt_semantic_seg_copy == old_id] = new_id +``` + +- 通过 `reduce_zero_label` 修改 + 对于常见的忽略 0 号标签的场景, `BaseSegDataset` 的子类中可以用 `reduce_zero_label` 输入参数来控制。`reduce_zero_label` (默认为 `False`) + 用来控制是否将标签 0 忽略, 当该参数为 `True` 时(最常见的应用是 ADE20k 数据集), 对分割标签中第 0 个类别对应的类别索引改为 255 (MMSegmentation 模型中计算损失时, 默认忽略 255), 其他类别对应的类别索引减一: + +```python +gt_semantic_seg[gt_semantic_seg == 0] = 255 +gt_semantic_seg = gt_semantic_seg - 1 +gt_semantic_seg[gt_semantic_seg == 254] = 255 +``` + +## 数据集与数据变换流水线 + +在常用接口的例子中可以看到, 输入的参数中定义了数据变换流水线参数 `pipeline`, 数据集 `__getitem__` 方法返回经过数据变换的值. +当数据集输入参数没有定义 pipeline, 返回值和 `get_data_info` 方法返回值相同, 例如: + +```python +from mmseg.datasets import CityscapesDataset + +data_root = 'data/cityscapes/' +data_prefix=dict(img_path='leftImg8bit/train', seg_map_path='gtFine/train') +dataset = CityscapesDataset(data_root=data_root, data_prefix=data_prefix, test_mode=False) + +print(dataset[0]) + +{'img_path': 'data/cityscapes/leftImg8bit/train/aachen/aachen_000000_000019_leftImg8bit.png', + 'seg_map_path': 'data/cityscapes/gtFine/train/aachen/aachen_000000_000019_gtFine_labelTrainIds.png', + 'label_map': None, + 'reduce_zero_label': False, + 'seg_fields': [], + 'sample_idx': 0} +``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/engine.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/engine.md new file mode 100644 index 0000000..79b4c8d --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/engine.md @@ -0,0 +1,281 @@ +# 训练引擎 + +MMEngine 定义了一些[基础循环控制器](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py) 例如基于轮次的训练循环 (`EpochBasedTrainLoop`), 基于迭代次数的训练循环 (`IterBasedTrainLoop`), 标准的验证循环 (`ValLoop`) 和标准的测试循环 (`TestLoop`). +OpenMMLab 的算法库如 MMSegmentation 将模型训练, 测试和推理抽象为执行器(`Runner`) 来处理. 用户可以直接使用 MMEngine 中的默认执行器, 也可以对执行器进行修改以满足定制化需求. 这个文档主要介绍用户如何配置已有的运行设定, 钩子和优化器的基本概念与使用方法. + +## 配置运行设定 + +### 配置训练长度 + +循环控制器指的是训练, 验证和测试时的执行流程, 在配置文件里面使用 `train_cfg`, `val_cfg` 和 `test_cfg` 来构建这些流程. MMSegmentation 在 `configs/_base_/schedules` 文件夹里面的 `train_cfg` 设置常用的训练长度. +例如, 使用基于迭代次数的训练循环 (`IterBasedTrainLoop`) 去训练 80,000 个迭代次数, 并且每 8,000 iteration 做一次验证, 可以如下设置: + +```python +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=8000) +``` + +### 配置训练优化器 + +这里是一个 SGD 优化器的例子: + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005), + clip_grad=None) +``` + +OpenMMLab 支持 PyTorch 里面所有的优化器, 更多细节可以参考 MMEngine [优化器文档](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/optim_wrapper.md). + +需要强调的是, `optim_wrapper` 是 `runner` 的变量, 所以需要配置优化器时配置的字段是 `optim_wrapper` 字段. 更多关于优化器的使用方法, 可以看下面优化器的章节. + +### 配置训练参数调度器 + +在配置训练参数调度器前, 推荐先了解 [MMEngine 文档](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/param_scheduler.md) 里面关于参数调度器的基本概念. + +以下是一个参数调度器的例子, 训练时前 1,000 个 iteration 时采用线性变化的学习率策略作为训练预热, 从 1,000 iteration 之后直到最后 16,000 个 iteration 时则采用默认的多项式学习率衰减: + +```python +param_scheduler = [ + dict(type='LinearLR', by_epoch=False, start_factor=0.1, begin=0, end=1000), + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=1000, + end=160000, + by_epoch=False, + ) +] +``` + +注意: 当修改 `train_cfg` 里面 `max_iters` 的时候, 请确保参数调度器 `param_scheduler` 里面的参数也被同时修改. + +## 钩子 (Hook) + +### 介绍 + +OpenMMLab 将模型训练和测试过程抽象为 `Runner`, 插入钩子可以实现在 `Runner` 中不同的训练和测试节点 (例如 "每个训练 iter 前后", "每个验证 iter 前后" 等不同阶段) 所需要的相应功能. 更多钩子机制的介绍可以参考[这里](https://www.calltutors.com/blog/what-is-hook). + +`Runner` 中所使用的钩子分为两类: + +- 默认钩子 (default hooks) + +它们实现了训练时所必需的功能, 在配置文件中用 `default_hooks` 定义传给 `Runner`, `Runner` 通过 [`register_default_hooks`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py#L1780) 方法注册. +钩子有对应的优先级, 优先级越高, 越早被执行器调用. 如果优先级一样, 被调用的顺序和钩子注册的顺序一致. +不建议用户修改默认钩子的优先级, 可以参考 [mmengine hooks 文档](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/hook.md) 了解钩子优先级的定义. +下面是 MMSegmentation 中所用到的默认钩子: + +| 钩子 | 功能 | 优先级 | +| :--------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------: | :---------------: | +| [IterTimerHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/iter_timer_hook.py) | 记录 iteration 花费的时间. | NORMAL (50) | +| [LoggerHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/logger_hook.py) | 从 `Runner` 里不同的组件中收集日志记录, 并将其输出到终端, JSON 文件, tensorboard, wandb 等下游. | BELOW_NORMAL (60) | +| [ParamSchedulerHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/param_scheduler_hook.py) | 更新优化器里面的一些超参数, 例如学习率的动量. | LOW (70) | +| [CheckpointHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py) | 规律性地保存 checkpoint 文件. | VERY_LOW (90) | +| [DistSamplerSeedHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/sampler_seed_hook.py) | 确保分布式采样器 shuffle 是打开的. | NORMAL (50) | +| [SegVisualizationHook](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/visualization/local_visualizer.py) | 可视化验证和测试过程里的预测结果. | NORMAL (50) | + +MMSegmentation 会在 [`defualt_hooks`](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/_base_/schedules/schedule_160k.py#L19-L25) 里面注册一些训练所必需功能的钩子:: + +```python +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=32000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) +``` + +以上默认钩子除 `SegVisualizationHook` 外都是在 MMEngine 中所实现, `SegVisualizationHook` 是在 MMSegmentation 里被实现的钩子, 之后会专门介绍. + +- 修改默认的钩子 + +以 `default_hooks` 里面的 `logger` 和 `checkpoint` 为例, 我们来介绍如何修改 `default_hooks` 中默认的钩子. + +(1) 模型保存配置 + +`default_hooks` 使用 `checkpoint` 字段来初始化[模型保存钩子 (CheckpointHook)](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L19). + +```python +checkpoint = dict(type='CheckpointHook', interval=1) +``` + +用户可以设置 `max_keep_ckpts` 来只保存少量的检查点或者用 `save_optimizer` 来决定是否保存 optimizer 的信息. +更多相关参数的细节可以参考[这里](https://mmengine.readthedocs.io/zh_CN/latest/api/generated/mmengine.hooks.CheckpointHook.html#checkpointhook). + +(2) 日志配置 + +`日志钩子 (LoggerHook)` 被用来收集 `执行器 (Runner)` 里面不同组件的日志信息然后写入终端, JSON 文件, tensorboard 和 wandb 等地方. + +```python +logger=dict(type='LoggerHook', interval=10) +``` + +在最新的 1.x 版本的 MMSegmentation 里面, 一些日志钩子 (LoggerHook) 例如 `TextLoggerHook`, `WandbLoggerHook` 和 `TensorboardLoggerHook` 将不再被使用. +作为替代, MMEngine 使用 `LogProcessor` 来处理上述钩子处理的信息, 它们现在在 [`MessageHub`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/logging/message_hub.py#L17), +[`WandbVisBackend`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/visualization/vis_backend.py#L324) 和 [`TensorboardVisBackend`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/visualization/vis_backend.py#L472) 里面. + +具体使用方法如下, 配置可视化器和同时指定可视化后端, 这里使用 Tensorboard 作为可视化器的后端: + +```python +# TensorboardVisBackend +visualizer = dict( + type='SegLocalVisualizer', vis_backends=[dict(type='TensorboardVisBackend')], name='visualizer') +``` + +关于更多相关用法, 可以参考 [MMEngine 可视化后端用户教程](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/advanced_tutorials/visualization.md). + +- 自定义钩子 (custom hooks) + +自定义钩子在配置通过 `custom_hooks` 定义, `Runner` 通过 [`register_custom_hooks`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py#L1820) 方法注册. +自定义钩子优先级需要在配置文件里设置, 如果没有设置, 则会被默认设置为 `NORMAL`. 下面是部分 MMEngine 中实现的自定义钩子: + +| 钩子 | 用法 | +| :----------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------: | +| [EMAHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/ema_hook.py) | 在模型训练时使用指数滑动平均 (Exponential Moving Average, EMA). | +| [EmptyCacheHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/empty_cache_hook.py) | 在训练时释放所有没有被缓存占用的 GPU 显存. | +| [SyncBuffersHook](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/sync_buffer_hook.py) | 在每个训练 Epoch 结束时同步模型 buffer 里的参数例如 BN 里的 `running_mean` 和 `running_var`. | + +以下是 `EMAHook` 的用例, 配置文件中, 将已经实现的自定义钩子的配置作为 `custom_hooks` 列表中的成员. + +```python +custom_hooks = [ + dict(type='EMAHook', start_iters=500, priority='NORMAL') +] +``` + +### SegVisualizationHook + +MMSegmentation 实现了 [`SegVisualizationHook`](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/engine/hooks/visualization_hook.py#L17), 用来在验证和测试时可视化预测结果. +`SegVisualizationHook` 重写了基类 `Hook` 中的 `_after_iter` 方法, 在验证或测试时, 根据指定的迭代次数间隔调用 `visualizer` 的 `add_datasample` 方法绘制语义分割结果, 具体实现如下: + +```python +... +@HOOKS.register_module() +class SegVisualizationHook(Hook): +... + def _after_iter(self, + runner: Runner, + batch_idx: int, + data_batch: dict, + outputs: Sequence[SegDataSample], + mode: str = 'val') -> None: +... + # 如果是训练阶段或者 self.draw 为 False 则直接跳出 + if self.draw is False or mode == 'train': + return +... + if self.every_n_inner_iters(batch_idx, self.interval): + for output in outputs: + img_path = output.img_path + img_bytes = self.file_client.get(img_path) + img = mmcv.imfrombytes(img_bytes, channel_order='rgb') + window_name = f'{mode}_{osp.basename(img_path)}' + + self._visualizer.add_datasample( + window_name, + img, + data_sample=output, + show=self.show, + wait_time=self.wait_time, + step=runner.iter) + +``` + +关于可视化更多的细节可以查看[这里](../user_guides/visualization.md). + +## 优化器 + +在上面配置运行设定里, 我们给出了配置训练优化器的简单示例. 本章节将进一步详细介绍在 MMSegmentation 里如何配置优化器. + +### 优化器封装 + +OpenMMLab 2.0 设计了优化器封装, 它支持不同的训练策略, 包括混合精度训练、梯度累加和梯度截断等, 用户可以根据需求选择合适的训练策略. +优化器封装还定义了一套标准的参数更新流程, 用户可以基于这一套流程, 在同一套代码里, 实现不同训练策略的切换. 如果想了解更多, 可以参考 [MMEngine 优化器封装文档](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/optim_wrapper.md). + +以下是 MMSegmentation 中常用的使用方法: + +#### 配置 PyTorch 支持的优化器 + +OpenMMLab 2.0 支持 PyTorch 原生所有优化器, 参考[这里](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/optim_wrapper.md#%E7%AE%80%E5%8D%95%E9%85%8D%E7%BD%AE). + +在配置文件中设置训练时 `Runner` 所使用的优化器, 需要定义 `optim_wrapper`, 而不是 `optimizer`, 下面是一个配置训练中优化器的例子: + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005), + clip_grad=None) +``` + +#### 配置梯度裁剪 + +当模型训练需要使用梯度裁剪的训练技巧式, 可以按照如下示例进行配置: + +```python +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, + clip_grad=dict(max_norm=0.01, norm_type=2)) +``` + +这里 `max_norm` 指的是裁剪后梯度的最大值, `norm_type` 指的是裁剪梯度时使用的范数. 相关方法可参考 [torch.nn.utils.clip_grad_norm\_](https://pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_norm_.html). + +#### 配置混合精度训练 + +当需要使用混合精度训练降低内存时, 可以使用 `AmpOptimWrapper`, 具体配置如下: + +```python +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001) +optim_wrapper = dict(type='AmpOptimWrapper', optimizer=optimizer) +``` + +[`AmpOptimWrapper`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/amp_optimizer_wrapper.py#L20) 中 `loss_scale` 的默认设置是 `dynamic`. + +#### 配置模型网络不同层的超参数 + +在模型训练中, 如果想在优化器里为不同参数分别设置优化策略, 例如设置不同的学习率、权重衰减等超参数, 可以通过设置配置文件里 `optim_wrapper` 中的 `paramwise_cfg` 来实现. + +下面的配置文件以 [ViT `optim_wrapper`](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/vit/vit_vit-b16-ln_mln_upernet_8xb2-160k_ade20k-512x512.py#L15-L27) 为例介绍 `paramwise_cfg` 参数使用. +训练时将 `pos_embed`, `mask_token`, `norm` 模块的 weight decay 参数的系数设置成 0. +即: 在训练时, 这些模块的 weight decay 将被变为 `weight_decay * decay_mult`=0. + +```python +optimizer = dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01) +optim_wrapper = dict( + type='OptimWrapper', + optimizer=optimizer, + paramwise_cfg=dict( + custom_keys={ + 'pos_embed': dict(decay_mult=0.), + 'cls_token': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) +``` + +其中 `decay_mult` 指的是对应参数的权重衰减的系数. +关于更多 `paramwise_cfg` 的使用可以在 [MMEngine 优化器封装文档](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/tutorials/optim_wrapper.md) 里面查到. + +### 优化器封装构造器 + +默认的优化器封装构造器 [`DefaultOptimWrapperConstructor`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L19) 根据输入的 `optim_wrapper` 和 `optim_wrapper` 中定义的 `paramwise_cfg` 来构建训练中使用的优化器. 当 [`DefaultOptimWrapperConstructor`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L19) 功能不能满足需求时, 可以自定义优化器封装构造器来实现超参数的配置. + +MMSegmentation 中的实现了 [`LearningRateDecayOptimizerConstructor`](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/engine/optimizers/layer_decay_optimizer_constructor.py#L104), 可以对以 ConvNeXt, BEiT 和 MAE 为骨干网络的模型训练时, 骨干网络的模型参数的学习率按照定义的衰减比例(`decay_rate`)逐层递减, 在配置文件中的配置如下: + +```python +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05), + paramwise_cfg={ + 'decay_rate': 0.9, + 'decay_type': 'stage_wise', + 'num_layers': 12 + }, + constructor='LearningRateDecayOptimizerConstructor', + loss_scale='dynamic') +``` + +`_delete_=True` 的作用是 OpenMMLab Config 中的忽略继承的配置, 在该代码片段中忽略继承的 `optim_wrapper` 配置, 更多 `_delete_` 字段的内容可以参考 [MMEngine 文档](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/advanced_tutorials/config.md#%E5%88%A0%E9%99%A4%E5%AD%97%E5%85%B8%E4%B8%AD%E7%9A%84-key). diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/evaluation.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/evaluation.md new file mode 100644 index 0000000..dc93a46 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/evaluation.md @@ -0,0 +1,158 @@ +# 模型评测 + +模型评测过程会分别在 [ValLoop](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L300) 和 [TestLoop](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L373) 中被执行,用户可以在训练期间或使用配置文件中简单设置的测试脚本进行模型性能评估。`ValLoop` 和 `TestLoop` 属于 [Runner](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/runner.py#L59),它们会在第一次被调用时构建。由于 `dataloader` 与 `evaluator` 是必需的参数,所以要成功构建 `ValLoop`,在构建 `Runner` 时必须设置 `val_dataloader` 和 `val_evaluator`,`TestLoop` 亦然。有关 Runner 设计的更多信息,请参阅 [MMEngine](https://github.com/open-mmlab/mmengine) 的[文档](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/design/runner.md)。 + +
+ +
测试/验证 数据流
+
+ +在 MMSegmentation 中,默认情况下,我们将 dataloader 和 metrics 的设置写在数据集配置文件中,并将 evaluation loop 的配置写在 `schedule_x` 配置文件中。 + +例如,在 ADE20K 配置文件 `configs/_base_/dataset/ADE20K.py` 中,在第37到48行,我们配置了 `val_dataloader`,在第51行,我们选择 `IoUMetric` 作为 evaluator,并设置 `mIoU` 作为指标: + +```python +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +``` + +为了能够在训练期间进行评估模型,我们将评估配置添加到了 `configs/schedules/schedule_40k.py` 文件的第15至16行: + +```python +train_cfg = dict(type='IterBasedTrainLoop', max_iters=40000, val_interval=4000) +val_cfg = dict(type='ValLoop') +``` + +使用以上两种设置,MMSegmentation 在 40K 迭代训练期间,每 4000 次迭代进行一次模型 **mIoU** 指标的评估。 + +如果我们希望在训练后测试模型,则需要将 `test_dataloader`、`test_evaluator` 和 `test_cfg` 配置添加到配置文件中。 + +```python +test_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) + +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_cfg = dict(type='TestLoop') +``` + +在 MMSegmentation 中,默认情况下,`test_dataloader` 和 `test_evaluator` 的设置与 `ValLoop` 的 dataloader 和 evaluator 相同,我们可以修改这些设置以满足我们的需要。 + +## IoUMetric + +MMSegmentation 基于 [MMEngine](https://github.com/open-mmlab/mmengine) 提供的 [BaseMetric](https://github.com/open-mmlab/mmengine/blob/main/mmengine/evaluator/metric.py) 实现 [IoUMetric](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/evaluation/metrics/iou_metric.py) 和 [CityscapesMetric](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/evaluation/metrics/citys_metric.py),以评估模型的性能。有关统一评估接口的更多详细信息,请参阅[文档](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/evaluation.html)。 + +这里我们简要介绍 `IoUMetric` 的参数和两种主要方法。 + +除了 `collect_device` 和 `prefix` 之外,`IoUMetric` 的构建还包含一些其他参数。 + +构造函数的参数: + +- ignore_index(int)- 将在评估中忽略的类别索引。默认值:255。 +- iou_metrics(list\[str\] | str)- 需要计算的指标,可选项包括 'mIoU'、'mDice' 和 'mFscore'。 +- nan_to_num(int,可选)- 如果指定,NaN 值将被用户定义的数字替换。默认值:None。 +- beta(int)- 决定综合评分中 recall 的权重。默认值:1。 +- collect_device(str)- 用于在分布式训练期间从不同进程收集结果的设备名称。必须是 'cpu' 或 'gpu'。默认为 'cpu'。 +- prefix(str,可选)- 将添加到指标名称中的前缀,以消除不同 evaluator 的同名指标的歧义。如果参数中未提供前缀,则将使用 self.default_prefix 进行替代。默认为 None。 + +`IoUMetric` 实现 IoU 指标的计算,`IoUMetric` 的两个核心方法是 `process` 和 `compute_metrics`。 + +- `process` 方法处理一批 data 和 data_samples。 +- `compute_metrics` 方法根据处理的结果计算指标。 + +### IoUMetric.process + +参数: + +- data_batch(Any)- 来自 dataloader 的一批数据。 +- data_samples(Sequence\[dict\])- 模型的一批输出。 + +返回值: + +此方法没有返回值,因为处理的结果将存储在 `self.results` 中,以在处理完所有批次后进行指标的计算。 + +### IoUMetric.compute_metrics + +参数: + +- results(list)- 每个批次的处理结果。 + +返回值: + +- Dict\[str,float\] - 计算的指标。指标的名称为 key,值是相应的结果。key 主要包括 **aAcc**、**mIoU**、**mAcc**、**mDice**、**mFscore**、**mPrecision**、**mPrecall**。 + +## CityscapesMetric + +`CityscapesMetric` 使用由 Cityscapes 官方提供的 [CityscapesScripts](https://github.com/mcordts/cityscapesScripts) 进行模型性能的评估。 + +### 使用方法 + +在使用之前,请先安装 `cityscapesscripts` 包: + +```shell +pip install cityscapesscripts +``` + +由于 `IoUMetric` 在 MMSegmentation 中作为默认的 evaluator 使用,如果您想使用 `CityscapesMetric`,则需要自定义配置文件。在自定义配置文件中,应按如下方式替换默认 evaluator。 + +```python +val_evaluator = dict(type='CityscapesMetric', output_dir='tmp') +test_evaluator = val_evaluator +``` + +### 接口 + +构造函数的参数: + +- output_dir (str) - 预测结果输出的路径 +- ignore_index (int) - 将在评估中忽略的类别索引。默认值:255。 +- format_only (bool) - 只为提交进行结果格式化而不进行评估。当您希望将结果格式化为特定格式并将其提交给测试服务器时有用。默认为 False。 +- keep_results (bool) - 是否保留结果。当 `format_only` 为 True 时,`keep_results` 必须为 True。默认为 False。 +- collect_device (str) - 用于在分布式训练期间从不同进程收集结果的设备名称。必须是 'cpu' 或 'gpu'。默认为 'cpu'。 +- prefix (str,可选) - 将添加到指标名称中的前缀,以消除不同 evaluator 的同名指标的歧义。如果参数中未提供前缀,则将使用 self.default_prefix 进行替代。默认为 None。 + +#### CityscapesMetric.process + +该方法将在图像上绘制 mask,并将绘制的图像保存到 `work_dir` 中。 + +参数: + +- data_batch(dict)- 来自 dataloader 的一批数据。 +- data_samples(Sequence\[dict\])- 模型的一批输出。 + +返回值: + +此方法没有返回值,因为处理的结果将存储在 `self.results` 中,以在处理完所有批次后进行指标的计算。 + +#### CityscapesMetric.compute_metrics + +此方法将调用 `cityscapessscripts.evaluation.evalPixelLevelSemanticLabeling` 工具来计算指标。 + +参数: + +- results(list)- 数据集的测试结果。 + +返回值: + +- dict\[str:float\] - Cityscapes 评测结果。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/index.rst b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/index.rst new file mode 100644 index 0000000..2aec1ac --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/index.rst @@ -0,0 +1,26 @@ +基本概念 +*************** + +.. toctree:: + :maxdepth: 1 + + data_flow.md + structures.md + models.md + datasets.md + transforms.md + evaluation.md + engine.md + training_tricks.md + +自定义组件 +************************ + +.. toctree:: + :maxdepth: 1 + + add_models.md + add_datasets.md + add_transforms.md + add_metrics.md + customize_runtime.md diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/models.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/models.md new file mode 100644 index 0000000..6eb2251 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/models.md @@ -0,0 +1,177 @@ +# 模型 + +我们通常将深度学习任务中的神经网络定义为模型,这个模型即是算法的核心。[MMEngine](https://github.com/open-mmlab/mmengine) 抽象出了一个统一模型 [BaseModel](https://github.com/open-mmlab/mmengine/blob/main/mmengine/model/base_model/base_model.py#L16) 以标准化训练、测试和其他过程。MMSegmentation 实现的所有模型都继承自 `BaseModel`,并且在 MMSegmention 中,我们实现了前向传播并为语义分割算法添加了一些功能。 + +## 常用组件 + +### 分割器(Segmentor) + +在 MMSegmentation 中,我们将网络架构抽象为**分割器**,它是一个包含网络所有组件的模型。我们已经实现了**编码器解码器(EncoderDecoder)**和**级联编码器解码器(CascadeEncoderDecoder)**,它们通常由**数据预处理器**、**骨干网络**、**解码头**和**辅助头**组成。 + +### 数据预处理器(Data preprocessor) + +**数据预处理器**是将数据复制到目标设备并将数据预处理为模型输入格式的部分。 + +### 主干网络(Backbone) + +**主干网络**是将图像转换为特征图的部分,例如没有最后全连接层的 **ResNet-50**。 + +### 颈部(Neck) + +**颈部**是连接主干网络和头的部分。它对主干网络生成的原始特征图进行一些改进或重新配置。例如 **Feature Pyramid Network(FPN)**。 + +### 解码头(Decode head) + +**解码头**是将特征图转换为分割掩膜的部分,例如 **PSPNet**。 + +### 辅助头(Auxiliary head) + +**辅助头**是一个可选组件,它将特征图转换为仅用于计算辅助损失的分割掩膜。 + +## 基本接口 + +MMSegmentation 封装 `BaseModel` 并实现了 [BaseSegmentor](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/models/segmentors/base.py#L15) 类,主要提供 `forward`、`train_step`、`val_step` 和 `test_step` 接口。接下来将详细介绍这些接口。 + +### forward + +
+ +
编码器解码器数据流
+
+ +
+
+
级联编码器解码器数据流
+
+ +`forward` 方法返回训练、验证、测试和简单推理过程的损失或预测。 + +该方法应接受三种模式:“tensor”、“predict” 和 “loss”: + +- “tensor”:前向推理整个网络并返回张量或张量数组,无需任何后处理,与常见的 `nn.Module` 相同。 +- “predict”:前向推理并返回预测值,这些预测值将被完全处理到 `SegDataSample` 列表中。 +- “loss”:前向推理并根据给定的输入和数据样本返回损失的`字典`。 + +**注:**[SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py) 是 MMSegmentation 的数据结构接口,用作不同组件之间的接口。`SegDataSample` 实现了抽象数据元素 `mmengine.structures.BaseDataElement`,请参阅 [MMMEngine](https://github.com/open-mmlab/mmengine) 中的 [SegDataSample 文档](https://mmsegmentation.readthedocs.io/zh_CN/1.x/advanced_guides/structures.html)和[数据元素文档](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/data_element.html)了解更多信息。 + +注意,此方法不处理在 `train_step` 方法中完成的反向传播或优化器更新。 + +参数: + +- inputs(torch.Tensor)- 通常为形状是(N, C, ...) 的输入张量。 +- data_sample(list\[[SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py)\]) - 分割数据样本。它通常包括 `metainfo` 和 `gt_sem_seg` 等信息。默认值为 None。 +- mode (str) - 返回什么类型的值。默认为 'tensor'。 + +返回值: + +- `dict` 或 `list`: + - 如果 `mode == "loss"`,则返回用于反向过程和日志记录的损失张量`字典`。 + - 如果 `mode == "predict"`,则返回 `SegDataSample` 的`列表`,推理结果将被递增地添加到传递给 forward 方法的 `data_sample` 参数中,每个 `SegDataSeample` 包含以下关键词: + - pred_sm_seg (`PixelData`):语义分割的预测。 + - seg_logits (`PixelData`):标准化前语义分割的预测指标。 + - 如果 `mode == "tensor"`,则返回`张量`或`张量数组`的`字典`以供自定义使用。 + +### 预测模式 + +我们在[配置文档](../user_guides/1_config.md)中简要描述了模型配置的字段,这里我们详细介绍 `model.test_cfg` 字段。`model.test_cfg` 用于控制前向行为,`"predict"` 模式下的 `forward` 方法可以在两种模式下运行: + +- `whole_inference`:如果 `cfg.model.test_cfg.mode == 'whole'`,则模型将使用完整图像进行推理。 + + `whole_inference` 模式的一个示例配置: + + ```python + model = dict( + type='EncoderDecoder' + ... + test_cfg=dict(mode='whole') + ) + ``` + +- `slide_inference`:如果 `cfg.model.test_cfg.mode == ‘slide’`,则模型将通过滑动窗口进行推理。**注意:** 如果选择 `slide` 模式,还应指定 `cfg.model.test_cfg.stride` 和 `cfg.model.test_cfg.crop_size`。 + + `slide_inference` 模式的一个示例配置: + + ```python + model = dict( + type='EncoderDecoder' + ... + test_cfg=dict(mode='slide', crop_size=256, stride=170) + ) + ``` + +### train_step + +`train_step` 方法调用 `loss` 模式的前向接口以获得损失`字典`。`BaseModel` 类实现默认的模型训练过程,包括预处理、模型前向传播、损失计算、优化和反向传播。 + +参数: + +- data (dict or tuple or list) - 从数据集采样的数据。在 MMSegmentation 中,数据字典包含 `inputs` 和 `data_samples` 两个字段。 +- optim_wrapper (OptimWrapper) - 用于更新模型参数的 OptimWrapper 实例。 + +**注:**[OptimWrapper](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/optimizer_wrapper.py#L17) 提供了一个用于更新参数的通用接口,请参阅 [MMMEngine](https://github.com/open-mmlab/mmengine) 中的优化器封装[文档](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/optim_wrapper.html)了解更多信息。 + +返回值: + +-Dict\[str, `torch.Tensor`\]:用于记录日志的张量的`字典`。 + +
+ +
train_step 数据流
+
+ +### val_step + +`val_step` 方法调用 `predict` 模式的前向接口并返回预测结果,预测结果将进一步被传递给评测器的进程接口和钩子的 `after_val_inter` 接口。 + +参数: + +- data (`dict` or `tuple` or `list`) - 从数据集中采样的数据。在 MMSegmentation 中,数据字典包含 `inputs` 和 `data_samples` 两个字段。 + +返回值: + +- `list` - 给定数据的预测结果。 + +
+ +
test_step/val_step 数据流
+
+ +### test_step + +`BaseModel` 中 `test_step` 与 `val_step` 的实现相同。 + +## 数据预处理器(Data Preprocessor) + +MMSegmentation 实现的 [SegDataPreProcessor](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/models/data_preprocessor.py#L13) 继承自由 [MMEngine](https://github.com/open-mmlab/mmengine) 实现的 [BaseDataPreprocessor](https://github.com/open-mmlab/mmengine/blob/main/mmengine/model/base_model/data_preprocessor.py#L18),提供数据预处理和将数据复制到目标设备的功能。 + +Runner 在构建阶段将模型传送到指定的设备,而 [SegDataPreProcessor](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/models/data_preprocessor.py#L13) 在 `train_step`、`val_step` 和 `test_step` 中将数据传送到指定设备,之后处理后的数据将被进一步传递给模型。 + +`SegDataPreProcessor` 构造函数的参数: + +- mean (Sequence\[Number\], 可选) - R、G、B 通道的像素平均值。默认为 None。 +- std (Sequence\[Number\], 可选) - R、G、B 通道的像素标准差。默认为 None。 +- size (tuple, 可选) - 固定的填充大小。 +- size_divisor (int, 可选) - 填充大小的除法因子。 +- pad_val (float, 可选) - 填充值。默认值:0。 +- seg_pad_val (float, 可选) - 分割图的填充值。默认值:255。 +- bgr_to_rgb (bool) - 是否将图像从 BGR 转换为 RGB。默认为 False。 +- rgb_to_bgr (bool) - 是否将图像从 RGB 转换为 BGR。默认为 False。 +- batch_augments (list\[dict\], 可选) - 批量化的数据增强。默认值为 None。 + +数据将按如下方式处理: + +- 收集数据并将其移动到目标设备。 +- 用定义的 `pad_val` 将输入填充到输入大小,并用定义的 `seg_Pad_val` 填充分割图。 +- 将输入堆栈到 batch_input。 +- 如果输入的形状为 (3, H, W),则将输入从 BGR 转换为 RGB。 +- 使用定义的标准差和平均值标准化图像。 +- 在训练期间进行如 Mixup 和 Cutmix 的批量化数据增强。 + +`forward` 方法的参数: + +- data (dict) - 从数据加载器采样的数据。 +- training (bool) - 是否启用训练时数据增强。 + +`forward` 方法的返回值: + +- Dict:与模型输入格式相同的数据。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/structures.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/structures.md new file mode 100644 index 0000000..958e011 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/structures.md @@ -0,0 +1,102 @@ +# 数据结构 + +为了统一模型和各功能模块之间的输入和输出的接口, 在 OpenMMLab 2.0 MMEngine 中定义了一套抽象数据结构, 实现了基础的增/删/查/改功能, 支持不同设备间的数据迁移, 也支持了如 +`.cpu()`, `.cuda()`, `.get()` 和 `.detach()` 的类字典和张量的操作。具体可以参考 [MMEngine 文档](https://github.com/open-mmlab/mmengine/blob/main/docs/en/advanced_tutorials/data_element.md)。 + +同样的, MMSegmentation 亦遵循了 OpenMMLab 2.0 各模块间的接口协议, 定义了 `SegDataSample` 用来封装语义分割任务所需要的数据。 + +## 语义分割数据 SegDataSample + +[SegDataSample](mmseg.structures.SegDataSample) 包括了三个主要数据字段 `gt_sem_seg`, `pred_sem_seg` 和 `seg_logits`, 分别用来存放标注信息, 预测结果和预测的未归一化前的 logits 值。 + +| 字段 | 类型 | 描述 | +| -------------- | ------------------------- | ------------------------------- | +| gt_sem_seg | [`PixelData`](#pixeldata) | 图像标注信息. | +| pred_instances | [`PixelData`](#pixeldata) | 图像预测结果. | +| seg_logits | [`PixelData`](#pixeldata) | 模型预测未归一化前的 logits 值. | + +以下示例代码展示了 `SegDataSample` 的使用方法: + +```python +import torch +from mmengine.structures import PixelData +from mmseg.structures import SegDataSample + +img_meta = dict(img_shape=(4, 4, 3), + pad_shape=(4, 4, 3)) +data_sample = SegDataSample() +# 定义 gt_segmentations 用于封装模型的输出信息 +gt_segmentations = PixelData(metainfo=img_meta) +gt_segmentations.data = torch.randint(0, 2, (1, 4, 4)) + +# 增加和处理 SegDataSample 中的属性 +data_sample.gt_sem_seg = gt_segmentations +assert 'gt_sem_seg' in data_sample +assert 'data' in data_sample.gt_sem_seg +assert 'img_shape' in data_sample.gt_sem_seg.metainfo_keys() +print(data_sample.gt_sem_seg.shape) +''' +(4, 4) +''' +print(data_sample) +''' + +) at 0x1c2aae44d60> +''' + +# 删除和修改 SegDataSample 中的属性 +data_sample = SegDataSample() +gt_segmentations = PixelData(metainfo=img_meta) +gt_segmentations.data = torch.randint(0, 2, (1, 4, 4)) +data_sample.gt_sem_seg = gt_segmentations +data_sample.gt_sem_seg.set_metainfo(dict(img_shape=(4,4,9), pad_shape=(4,4,9))) +del data_sample.gt_sem_seg.img_shape + +# 类张量的操作 +data_sample = SegDataSample() +gt_segmentations = PixelData(metainfo=img_meta) +gt_segmentations.data = torch.randint(0, 2, (1, 4, 4)) +cuda_gt_segmentations = gt_segmentations.cuda() +cuda_gt_segmentations = gt_segmentations.to('cuda:0') +cpu_gt_segmentations = cuda_gt_segmentations.cpu() +cpu_gt_segmentations = cuda_gt_segmentations.to('cpu') +``` + +## 在 SegDataSample 中自定义新的属性 + +如果你想在 `SegDataSample` 中自定义新的属性,你可以参考下面的 [SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py) 示例: + +```python +class SegDataSample(BaseDataElement): + ... + + @property + def xxx_property(self) -> xxxData: + return self._xxx_property + + @xxx_property.setter + def xxx_property(self, value: xxxData) -> None: + self.set_field(value, '_xxx_property', dtype=xxxData) + + @xxx_property.deleter + def xxx_property(self) -> None: + del self._xxx_property +``` + +这样一个新的属性 `xxx_property` 就将被增加到 `SegDataSample` 里面了。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/training_tricks.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/training_tricks.md new file mode 100644 index 0000000..e5b8e4d --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/training_tricks.md @@ -0,0 +1,74 @@ +# 训练技巧 + +MMSegmentation 支持如下训练技巧: + +## 主干网络和解码头组件使用不同的学习率 (Learning Rate, LR) + +在语义分割里,一些方法会让解码头组件的学习率大于主干网络的学习率,这样可以获得更好的表现或更快的收敛。 + +在 MMSegmentation 里面,您也可以在配置文件里添加如下行来让解码头组件的学习率是主干组件的10倍。 + +```python +optim_wrapper=dict( + paramwise_cfg = dict( + custom_keys={ + 'head': dict(lr_mult=10.)})) +``` + +通过这种修改,任何被分组到 `'head'` 的参数的学习率都将乘以10。您也可以参照 [MMEngine 文档](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/optim_wrapper.html#id6) 获取更详细的信息。 + +## 在线难样本挖掘 (Online Hard Example Mining, OHEM) + +MMSegmentation 中实现了像素采样器,训练时可以对特定像素进行采样,例如 OHEM(Online Hard Example Mining),可以解决样本不平衡问题, +如下例子是使用 PSPNet 训练并采用 OHEM 策略的配置: + +```python +_base_ = './pspnet_r50-d8_512x1024_40k_cityscapes.py' +model=dict( + decode_head=dict( + sampler=dict(type='OHEMPixelSampler', thresh=0.7, min_kept=100000)) ) +``` + +通过这种方式,只有置信分数在0.7以下的像素值点会被拿来训练。在训练时我们至少要保留100000个像素值点。如果 `thresh` 并未被指定,前 `min_kept` +个损失的像素值点才会被选择。 + +## 类别平衡损失 (Class Balanced Loss) + +对于不平衡类别分布的数据集,您也许可以改变每个类别的损失权重。这里以 cityscapes 数据集为例: + +```python +_base_ = './pspnet_r50-d8_512x1024_40k_cityscapes.py' +model=dict( + decode_head=dict( + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0, + # DeepLab 对 cityscapes 使用这种权重 + class_weight=[0.8373, 0.9180, 0.8660, 1.0345, 1.0166, 0.9969, 0.9754, + 1.0489, 0.8786, 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, + 1.0865, 1.0955, 1.0865, 1.1529, 1.0507]))) +``` + +`class_weight` 将被作为 `weight` 参数,传递给 `CrossEntropyLoss`。详细信息请参照 [PyTorch 文档](https://pytorch.org/docs/stable/nn.html?highlight=crossentropy#torch.nn.CrossEntropyLoss) 。 + +## 同时使用多种损失函数 (Multiple Losses) + +对于训练时损失函数的计算,我们目前支持多个损失函数同时使用。 以 `unet` 使用 `DRIVE` 数据集训练为例, +使用 `CrossEntropyLoss` 和 `DiceLoss` 的 `1:3` 的加权和作为损失函数。配置文件写为: + +```python +_base_ = './fcn_unet_s5-d16_64x64_40k_drive.py' +model = dict( + decode_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ]), + auxiliary_head=dict(loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_ce', loss_weight=1.0), + dict(type='DiceLoss', loss_name='loss_dice', loss_weight=3.0) + ]), +) +``` + +通过这种方式,确定训练过程中损失函数的权重 `loss_weight` 和在训练日志里的名字 `loss_name`。 + +注意: `loss_name` 的名字必须带有 `loss_` 前缀,这样它才能被包括在计算图里。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/transforms.md b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/transforms.md new file mode 100644 index 0000000..e5f3beb --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/advanced_guides/transforms.md @@ -0,0 +1,119 @@ +# 数据增强变化 + +在本教程中,我们将介绍 MMSegmentation 中数据增强变化流程的设计。 + +本指南的结构如下: + +- [数据增强变化](#数据增强变化) + - [数据增强变化流程设计](#数据增强变化流程设计) + - [数据加载](#数据加载) + - [预处理](#预处理) + - [格式修改](#格式修改) + +## 数据增强变化流程设计 + +按照惯例,我们使用 `Dataset` 和 `DataLoader` 多进程地加载数据。`Dataset` 返回与模型 forward 方法的参数相对应的数据项的字典。由于语义分割中的数据可能大小不同,我们在 MMCV 中引入了一种新的 `DataContainer` 类型,以帮助收集和分发不同大小的数据。参见[此处](https://github.com/open-mmlab/mmcv/blob/master/mmcv/parallel/data_container.py)了解更多详情。 + +在 MMSegmentation 的 1.x 版本中,所有数据转换都继承自 [`BaseTransform`](https://github.com/open-mmlab/mmcv/blob/2.x/mmcv/transforms/base.py#L6). + +转换的输入和输出类型都是字典。一个简单的示例如下: + +```python +>>> from mmseg.datasets.transforms import LoadAnnotations +>>> transforms = LoadAnnotations() +>>> img_path = './data/cityscapes/leftImg8bit/train/aachen/aachen_000000_000019_leftImg8bit.png.png' +>>> gt_path = './data/cityscapes/gtFine/train/aachen/aachen_000015_000019_gtFine_instanceTrainIds.png' +>>> results = dict( +>>> img_path=img_path, +>>> seg_map_path=gt_path, +>>> reduce_zero_label=False, +>>> seg_fields=[]) +>>> data_dict = transforms(results) +>>> print(data_dict.keys()) +dict_keys(['img_path', 'seg_map_path', 'reduce_zero_label', 'seg_fields', 'gt_seg_map']) +``` + +数据准备流程和数据集是解耦的。通常,数据集定义如何处理标注,数据流程定义准备数据字典的所有步骤。流程由一系列操作组成。每个操作都将字典作为输入,并为接下来的转换输出字典。 + +操作分为数据加载、预处理、格式修改和测试数据增强。 + +这里是 PSPNet 的流程示例: + +```python +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +``` + +对于每个操作,我们列出了 `添加`/`更新`/`删除` 相关的字典字段。在流程前,我们可以从数据集直接获得的信息是 `img_path` 和 `seg_map_path`。 + +### 数据加载 + +`LoadImageFromFile`:从文件加载图像。 + +- 添加:`img`,`img_shape`,`ori_shape` + +`LoadAnnotations`:加载数据集提供的语义分割图。 + +- 添加:`seg_fields`,`gt_seg_map` + +### 预处理 + +`RandomResize`:随机调整图像和分割图大小。 + +-添加:`scale`,`scale_factor`,`keep_ratio` +-更新:`img`,`img_shape`,`gt_seg_map` + +`Resize`:调整图像和分割图的大小。 + +-添加:`scale`,`scale_factor`,`keep_ratio` +-更新:`img`,`gt_seg_map`,`img_shape` + +`RandomCrop`:随机裁剪图像和分割图。 + +-更新:`img`,`gt_seg_map`,`img_shape` + +`RandomFlip`:翻转图像和分割图。 + +-添加:`flip`,`flip_direction` +-更新:`img`,`gt_seg_map` + +`PhotoMetricDistortion`:按顺序对图像应用光度失真,每个变换的应用概率为 0.5。随机对比度的位置是第二或倒数第二(分别为下面的模式 0 或 1)。 + +``` +1.随机亮度 +2.随机对比度(模式 0) +3.将颜色从 BGR 转换为 HSV +4.随机饱和度 +5.随机色调 +6.将颜色从 HSV 转换为 BGR +7.随机对比度(模式 1) +``` + +- 更新:`img` + +### 格式修改 + +`PackSegInputs`:为语义分段打包输入数据。 + +- 添加:`inputs`,`data_sample` +- 删除:由 `meta_keys` 指定的 keys(合并到 data_sample 的 metainfo 中),所有其他 keys diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/api.rst b/Seg_All_In_One_MMSeg/docs/zh_cn/api.rst new file mode 100644 index 0000000..3478aa9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/api.rst @@ -0,0 +1,95 @@ +mmseg.apis +-------------- +.. automodule:: mmseg.apis + :members: + +mmseg.datasets +-------------- + +datasets +^^^^^^^^^^ +.. automodule:: mmseg.datasets + :members: + +transforms +^^^^^^^^^^^^ +.. automodule:: mmseg.datasets.transforms + :members: + +mmseg.engine +-------------- + +hooks +^^^^^^^^^^ +.. automodule:: mmseg.engine.hooks + :members: + +optimizers +^^^^^^^^^^^^^^^ +.. automodule:: mmseg.engine.optimizers + :members: + +mmseg.evaluation +-------------- + +metrics +^^^^^^^^^^ +.. automodule:: mmseg.evaluation.metrics + :members: + +mmseg.models +-------------- + +backbones +^^^^^^^^^^^^^^^^^^ +.. automodule:: mmseg.models.backbones + :members: + +decode_heads +^^^^^^^^^^^^^^^ +.. automodule:: mmseg.models.decode_heads + :members: + +segmentors +^^^^^^^^^^ +.. automodule:: mmseg.models.segmentors + :members: + +losses +^^^^^^^^^^ +.. automodule:: mmseg.models.losses + :members: + +necks +^^^^^^^^^^^^ +.. automodule:: mmseg.models.necks + :members: + +utils +^^^^^^^^^^ +.. automodule:: mmseg.models.utils + :members: + + +mmseg.structures +-------------------- + +structures +^^^^^^^^^^^^^^^^^ +.. automodule:: mmseg.structures + :members: + +sampler +^^^^^^^^^^ +.. automodule:: mmseg.structures.sampler + :members: + +mmseg.visualization +-------------------- +.. automodule:: mmseg.visualization + :members: + +mmseg.utils +-------------- +.. automodule:: mmseg.utils + :members: diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/conf.py b/Seg_All_In_One_MMSeg/docs/zh_cn/conf.py new file mode 100644 index 0000000..1842055 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/conf.py @@ -0,0 +1,133 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import subprocess +import sys + +import pytorch_sphinx_theme + +sys.path.insert(0, os.path.abspath('../../')) + +# -- Project information ----------------------------------------------------- + +project = 'MMSegmentation' +copyright = '2020-2021, OpenMMLab' +author = 'MMSegmentation Authors' +version_file = '../../mmseg/version.py' + + +def get_version(): + with open(version_file) as f: + exec(compile(f.read(), version_file, 'exec')) + return locals()['__version__'] + + +# The full version, including alpha/beta/rc tags +release = get_version() + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.viewcode', + 'sphinx_markdown_tables', 'sphinx_copybutton', 'myst_parser' +] + +autodoc_mock_imports = [ + 'matplotlib', 'pycocotools', 'mmseg.version', 'mmcv.ops' +] + +# Ignore >>> when copying code +copybutton_prompt_text = r'>>> |\.\.\. ' +copybutton_prompt_is_regexp = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +# The master toctree document. +master_doc = 'index' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# html_theme = 'sphinx_rtd_theme' +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] +html_theme_options = { + 'logo_url': + 'https://mmsegmentation.readthedocs.io/zh-CN/latest/', + 'menu': [ + { + 'name': + '教程', + 'url': + 'https://github.com/open-mmlab/mmsegmentation/blob/master/' + 'demo/MMSegmentation_Tutorial.ipynb' + }, + { + 'name': 'GitHub', + 'url': 'https://github.com/open-mmlab/mmsegmentation' + }, + { + 'name': + '上游库', + 'children': [ + { + 'name': 'MMCV', + 'url': 'https://github.com/open-mmlab/mmcv', + 'description': '基础视觉库' + }, + ] + }, + ], + # Specify the language of shared menu + 'menu_lang': + 'cn', +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = ['css/readthedocs.css'] + +# Enable ::: for my_st +myst_enable_extensions = ['colon_fence'] + +language = 'zh-CN' + + +def builder_inited_handler(app): + subprocess.run(['./stat.py']) + + +def setup(app): + app.connect('builder-inited', builder_inited_handler) diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/device/npu.md b/Seg_All_In_One_MMSeg/docs/zh_cn/device/npu.md new file mode 100644 index 0000000..d50439d --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/device/npu.md @@ -0,0 +1,39 @@ +# NPU (华为 昇腾) + +## 使用方法 + +请参考 [MMCV 的安装文档](https://mmcv.readthedocs.io/en/latest/get_started/build.html#build-mmcv-full-on-ascend-npu-machine) 来安装 NPU 版本的 MMCV。 + +以下展示单机四卡场景的运行指令: + +```shell +bash tools/dist_train.sh configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py 4 +``` + +以下展示单机单卡下的运行指令: + +```shell +python tools/train.py configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py +``` + +## 模型验证结果 + +| Model | mIoU | Config | Download | +| :-----------------: | :---: | :----------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | +| [deeplabv3](<>) | 78.85 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024_20230115_205626.json) | +| [deeplabv3plus](<>) | 79.23 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/deeplabv3plus/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/deeplabv3plus_r50-d8_4xb2-40k_cityscapes-512x1024_20230116_043450.json) | +| [hrnet](<>) | 78.1 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/hrnet/fcn_hr18_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/fcn_hr18_4xb2-40k_cityscapes-512x1024_20230116_215821.json) | +| [fcn](<>) | 74.15 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/fcn_r50-d8_4xb2-40k_cityscapes-512x1024_20230111_083014.json) | +| [icnet](<>) | 69.25 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/icnet/icnet_r50-d8_4xb2-80k_cityscapes-832x832.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/icnet_r50-d8_4xb2-80k_cityscapes-832x832_20230119_002929.json) | +| [pspnet](<>) | 77.21 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/pspnet/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/pspnet_r50b-d8_4xb2-80k_cityscapes-512x1024_20230114_042721.json) | +| [unet](<>) | 68.86 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/unet/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024_20230129_224750.json) | +| [upernet](<>) | 77.81 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/upernet_r50_4xb2-40k_cityscapes-512x1024_20230129_014634.json) | +| [apcnet](<>) | 78.02 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/apcnet/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/apcnet_r50-d8_4xb2-40k_cityscapes-512x1024_20230209_212545.json) | +| [bisenetv1](<>) | 76.04 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/bisenetv1/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/bisenetv1_r50-d32_4xb4-160k_cityscapes-1024x1024_20230201_023946.json) | +| [bisenetv2](<>) | 72.44 | [config](https://github.com/open-mmlab/mmsegmentation/tree/1.x/configs/bisenetv2/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024.py) | [log](https://download.openmmlab.com/mmsegmentation/v0.5/device/npu/bisenetv2_fcn_4xb4-amp-160k_cityscapes-1024x1024_20230205_215606.json) | + +**注意:** + +- 如果没有特别标记,NPU 上的使用混合精度训练的结果与使用 FP32 的 GPU 上的结果相同。 + +**以上模型结果由华为昇腾团队提供** diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/get_started.md b/Seg_All_In_One_MMSeg/docs/zh_cn/get_started.md new file mode 100644 index 0000000..ca375f3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/get_started.md @@ -0,0 +1,209 @@ +# 开始:安装和运行 MMSeg + +## 预备知识 + +本教程中,我们将会演示如何使用 PyTorch 准备环境。 + +MMSegmentation 可以在 Linux, Windows 和 macOS 系统上运行,并且需要安装 Python 3.7+, CUDA 10.2+ 和 PyTorch 1.8+ + +**注意:** +如果您已经安装了 PyTorch, 可以跳过该部分,直接到[下一小节](##安装)。否则,您可以按照以下步骤操作。 + +**步骤 0.** 从[官方网站](https://docs.conda.io/en/latest/miniconda.html)下载并安装 Miniconda + +**步骤 1.** 创建一个 conda 环境,并激活 + +```shell +conda create --name openmmlab python=3.8 -y +conda activate openmmlab +``` + +**Step 2.** 参考 [official instructions](https://pytorch.org/get-started/locally/) 安装 PyTorch + +在 GPU 平台上: + +```shell +conda install pytorch torchvision -c pytorch +``` + +在 CPU 平台上 + +```shell +conda install pytorch torchvision cpuonly -c pytorch +``` + +## 安装 + +我们建议用户遵循我们的最佳实践来安装 MMSegmentation 。但是整个过程是高度自定义的。更多信息请参见[自定义安装](##自定义安装)部分。 + +### 最佳实践 + +**步骤 0.** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMCV](https://github.com/open-mmlab/mmcv) + +```shell +pip install -U openmim +mim install mmengine +mim install "mmcv>=2.0.0" +``` + +**步骤 1.** 安装 MMSegmentation + +情况 a: 如果您想立刻开发和运行 mmsegmentation,您可通过源码安装: + +```shell +git clone -b main https://github.com/open-mmlab/mmsegmentation.git +cd mmsegmentation +pip install -v -e . +# '-v' 表示详细模式,更多的输出 +# '-e' 表示以可编辑模式安装工程, +# 因此对代码所做的任何修改都生效,无需重新安装 +``` + +情况 b: 如果您把 mmsegmentation 作为依赖库或者第三方库,可以通过 pip 安装: + +```shell +pip install "mmsegmentation>=1.0.0" +``` + +### 验证是否安装成功 + +为了验证 MMSegmentation 是否正确安装,我们提供了一些示例代码来运行一个推理 demo 。 + +**步骤 1.** 下载配置文件和模型文件 + +```shell +mim download mmsegmentation --config pspnet_r50-d8_4xb2-40k_cityscapes-512x1024 --dest . +``` + +该下载过程可能需要花费几分钟,这取决于您的网络环境。当下载结束,您将看到以下两个文件在您当前工作目录:`pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py` 和 `pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth` + +**步骤 2.** 验证推理 demo + +选项 (a). 如果您通过源码安装了 mmsegmentation,运行以下命令即可: + +```shell +python demo/image_demo.py demo/demo.png configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth --device cuda:0 --out-file result.jpg +``` + +您将在当前文件夹中看到一个新图像 `result.jpg`,其中所有目标都覆盖了分割 mask + +选项 (b). 如果您通过 pip 安装 mmsegmentation, 打开您的 python 解释器,复制粘贴以下代码: + +```python +from mmseg.apis import inference_model, init_model, show_result_pyplot +import mmcv + +config_file = 'pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +checkpoint_file = 'pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth' + +# 根据配置文件和模型文件建立模型 +model = init_model(config_file, checkpoint_file, device='cuda:0') + +# 在单张图像上测试并可视化 +img = 'demo/demo.png' # or img = mmcv.imread(img), 这样仅需下载一次 +result = inference_model(model, img) +# 在新的窗口可视化结果 +show_result_pyplot(model, img, result, show=True) +# 或者将可视化结果保存到图像文件夹中 +# 您可以修改分割 map 的透明度 (0, 1]. +show_result_pyplot(model, img, result, show=True, out_file='result.jpg', opacity=0.5) +# 在一段视频上测试并可视化分割结果 +video = mmcv.VideoReader('video.mp4') +for frame in video: + result = inference_model(model, frame) + show_result_pyplot(model, frame, result, wait_time=1) +``` + +您可以修改上面的代码来测试单个图像或视频,这两个选项都可以验证安装是否成功。 + +### 自定义安装 + +#### CUDA 版本 + +当安装 PyTorch 的时候,您需要指定 CUDA 的版本, 如果您不确定选择哪个版本,请遵循我们的建议: + +- 对于基于 Ampere 的 NVIDIA GPUs, 例如 GeForce 30 系列和 NVIDIA A100, 必须要求是 CUDA 11. +- 对于更老的 NVIDIA GPUs, CUDA 11 is backward compatible, but CUDA 10.2 提供了更好的兼容性,以及更加的轻量化 + +请确保 GPU 驱动满足最小的版本需求。详情请参考这个[表格](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions) + +**注意:** +如果您按照我们的最佳实践,安装 CUDA 运行库就足够了,因为不需要 CUDA 代码在本地编译。 但是如果您希望从源码编译 MMCV 或者需要开发其他的 CUDA 算子,您需要从 NVIDIA 的[官网](https://developer.nvidia.com/cuda-downloads)安装完整的 CUDA 工具,同时它的版本需要与 PyTorch 的 CUDA 版本匹配。即 `conda install` 命令中指定的 cudatoolkit 版本。 + +#### 不使用 MIM 安装 MMCV + +MMCV 包含 C++ 和 CUDA 扩展,因此与 PyTorch 的依赖方式比较复杂。MIM 自动解决了这种依赖关系,使安装更容易。然而,MIM 也并不是必须的。 + +为了使用 pip 而不是 MIM 安装 MMCV, 请参考 [MMCV 安装指南](https://mmcv.readthedocs.io/en/latest/get_started/installation.html). 这需要手动指定一个基于 PyTorch 版本及其 CUDA 版本的 find-url. + +例如,以下命令可为 PyTorch 1.10.x and CUDA 11.3 安装 mmcv==2.0.0 + +```shell +pip install mmcv==2.0.0 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html +``` + +#### 在仅有 CPU 的平台安装 + +MMSegmentation 可以在仅有 CPU 的版本上运行。在 CPU 模式,您可以训练(需要 MMCV 版本 >= 2.0.0),测试和推理模型。 + +#### 在 Google Colab 上安装 + +[Google Colab](https://research.google.com/) 通常已经安装了 PyTorch,因此我们仅需要通过以下命令安装 MMCV 和 MMSegmentation。 + +**步骤 1.** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMCV](https://github.com/open-mmlab/mmcv) + +```shell +!pip3 install openmim +!mim install mmengine +!mim install "mmcv>=2.0.0" +``` + +**Step 2.** 通过源码安装 MMSegmentation + +```shell +!git clone https://github.com/open-mmlab/mmsegmentation.git +%cd mmsegmentation +!git checkout main +!pip install -e . +``` + +**Step 3.** 验证 + +```python +import mmseg +print(mmseg.__version__) +# 示例输出: 1.0.0 +``` + +**注意:** +在 Jupyter 中, 感叹号 `!` 用于调用外部可执行命令,`%cd` 是一个 [magic command](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-cd) 可以改变当前 python 的工作目录。 + +### 通过 Docker 使用 MMSegmentation + +我们提供了一个 [Dockerfile](https://github.com/open-mmlab/mmsegmentation/blob/master/docker/Dockerfile) 来建立映像。确保您的 [docker 版本](https://docs.docker.com/engine/install/) >=19.03. + +```shell +# 通过 PyTorch 1.11, CUDA 11.3 建立映像 +# 如果您使用其他版本,修改 Dockerfile 即可 +docker build -t mmsegmentation docker/ +``` + +运行: + +```shell +docker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmsegmentation/data mmsegmentation +``` + +### 可选依赖 + +#### 安装 GDAL + +[GDAL](https://gdal.org/) 是一个用于栅格和矢量地理空间数据格式的转换库。安装 GDAL 可以读取复杂格式和极大的遥感图像。 + +```shell +conda install GDAL +``` + +## 问题解答 + +如果您在安装过程中遇到了其他问题,请第一时间查阅 [FAQ](notes/faq.md) 文件。如果没有找到答案,您也可以在 GitHub 上提出 [issue](https://github.com/open-mmlab/mmsegmentation/issues/new/choose) diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/imgs/zhihu_qrcode.jpg b/Seg_All_In_One_MMSeg/docs/zh_cn/imgs/zhihu_qrcode.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c745fb027f06564d41794e9a40069b06c34e2bb5 GIT binary patch literal 397245 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�z}kBy(lpECAp?2$3?2Ce?C@=QTLjJV^{=f7xK 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 `>)#!#P|5Ci-PrOOx&3?kH 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

BleOu!O-<4iCWZ(G;i3}JnJvDT7{oZRkt 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

1*>=a5&H={)y-VSw4<4g%(qQgPf)0Dvi<^JD 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ֽUZ#?zv6#s zxF9y&I$jL~Pv-fV0z)k=x7=i3_)-P>SeuM`fWN78;nv{DWsm*adxXLtLqOJ2X9gW_ zUB>iY`rTzJaYFwtw%NXlyZwJ0orPOdkK4wFq_hIk3<&`#0qGb9EueG=6Da}d8Zb%e z1_?F#E9ZlEw=+xI=c_b=E5*SXGlp69;r&n?;OGE8{Y?DBPG;79Bs3BzUK zvcDbyIl`THuhXOBk^k_2AR-(cTE>~g=##;64K$A_Wxn{KnYOQ~^jaI;YsQ|Rgdn`E z#?dc#%9C{gl!ucCw-m7**}xbOwLH%q#x*q5!w!0=#&s>1QM}zrbD%&1oE)|%XwAr2 zc{GB~oQ2fof_dLsXO-EHE3#zaLqlC9nfHvxptc&TgtI-EV+bE_tYq-hO@_;hHNIl>9VTk zz6%E%r;s}sEP4A~8xqn}Trk!4tY(B@IYv9WT z;aZE#r_BxBE9v{Uv)}nV!0^2I20}9;e0zKw{Qtrud+DuB_Lnky315Y@2RT=`ZGO{F z;l@w0`&@rq+k7CN&r2G<;Q!>z9jw>bZ1?DXhJ?|sh~wowX=bC}yKBK-XN&_%5{%Z3 zsnVxAp9&&V5J?hG502c*%AUQtCrZK|0)3}bC};8;=-oileqN~48ayRFX#^euTn)0h z26a71(!W>F66|0`Xlzx}$RO)KcY(WAbm`U$)sYrVBJRLVL8?`Honx;i$_^6xx8ffu zJyfC0vtwJaOBHTOlQ<5ws7s;zA%n{9)_2$rSPH?BQk28|k4CySScSM|{}x+&M)!#Z z4EBHX6OEP`_&ORCCTR&HmwxODt!r`oms4)(Tzq&DSzfWNww3atcaWXDOe`drL>#l! zw+PI~c~GI#PrZbRfK5s<&Z}&*2Kz1;nIus7!k0Xi6bQw-|RqwyG$FScovh;-{~EK zDXlbKyH3Ukrw>yJ*10g?H0|lHm&;EF3W`@XyuCd+HRLX(eYm`iMhCf)p=v$mO!|H5 zQdu)=zyIwXVf};!u~gs)^mZz%jI`fg?xhEG&)2ZOGd6(QUkXS<&;qji+q>7h@FqbE zB3X@3L0l@dK2o40wU49Px`(?_sKSRV2XNNS^M`1_a@!IR0%!iXoKsu;XUfz> zdAO|qU3R4H(;rXyMWi?Jp-lkz#P)=2izkz5oozazQKL{)%gh@`NRvVljb{ohunu8+Kg}u zdK3EaHO{1go`;<69fm{eyFnM010pE`OHyRrmCG9d46N6mcuEj1y``x@A*NLu`!S5n zgCy@QA>*Kc3z5-I&Rz?_%Qm3zH~f_GP7@8j@|_&bK!~rmd*fxknVx#joFy?f#;X$% z<$tB+hxbPu&1RRgm!%Y?x3;#?N?3E_?}$@5Ve~f)8bVfR4GHet{s%IP_d(G|K8M^t zxR$w&6#g+&Ciw1Gs$V3*r;;DZL-H1BPH&*cPc3I0S3FY_9a3uQr3N;sM$ddk znt8IwDfzqU?g1s>TU3;vKJ2m>SbaMZ%%2L##86GZ;$!zCwc-db25oLTFTky%jGP(& zAYY+`a$iu5eJHeLR|`Cis);OfKu1sj@rV3UEe0euX&u>hucH*39$*2XgIiTMq6XuH za(itk3(yr zCw`mIrQ2c6mp;T17!5D#iK4Y`1|xM_GW)M0kv=MgzxSvujdtc2X zpKIesjtzN5{o`B9~d0f%m~*fi^IlAm%lCa2tOc8sR&- zT<~X|Jtj16eOZDlVk;7)uRkv8b^=-*Rr2ZYt2J9y^K|DNdpwz~U0J;^vd zNlh+!XfJ_H1loZdB>GVEiLc*X|M*F#&>u{={5IUcT9Q%0J#CEvK|eM$%nC&Uq8Q>1>MTesU!*>;)B(WL25*F-!YTfU}&LKAEmcO=!ZyV$Hw z2OuC@N15X6>?miV5|xV2eHQw>wcurJea555@PiiWE+SCua3Onu_~u?+ohH-(xnAi| z`AIU_?l$(@PX~ugZ;=t2n3z&ZAA*p@;1`<<-6(DREiogRs`vGsnVpsY+>b>m`TDI- zb=wdBY5#>(7%Uoc=2cE?EZ1JKPx^~7!tFMR>j5rWG)J&5*GuZuma;5b8aj5RC*D-g z`JMhyb^SqU1GVR*kL_R?-a-CyVoE-t!Bh`a*mS(tLg`)EZ;V3XMTqX>^Czido%sQR z7mX=J269ha+MLEzA=bb2b`ooo0w?kg_2pA(1o-B!ZUj-@AsAiYC;R9X=up42B~nV3 zn$~>x@z0yKWoSYVC9!4MGXtKIl z8|q3D@Y;t0owMG0r* zBEZl~g>>w9z%6ES9ct5nv>jBln)9%+j@mHS2isRGy)8*2iIO`ABR+@BQE6h`pmNIGbt%!wP04me%IP(Loc5##FHuKS!KVl4H&>LGy4fXC%>bHiJGmF1H zJ<5M{%{uUpl^L5iQQ@$3Q1G(tU?d~6mRy-F`;&|(-HZZl3vP1Ssk63z#8{x7kYxbN z;3mmWNg{QsZyoq&CH>$2Wo22%vo157+8YkKLdsVi4mS{A&Qu|WN-AeMjmxwX?EOa8rT@iC_jCsG>DJm7r*RYQ7yvo<6ZlU8Alz198-_4ahwT&q{DNdE8e` zmH3S^sHsG!ZC4)=AgI>Oa2)0Ru_{TOqbW{;tFC0x+(or$yZO`j$gc~v@yYuZQFR|0 zlVE3DaG4lTK!J==j0RUlcaR*p2QAajf>{c=#2SUkuFEVdeZSz9~I-~OZ z+S)j%a$4 z@>CcN5Okwv2TE#w&UZWWajNxicZ*83LK*U7e|Zv>O6qEgsSai=W!LbGc`-8%Mkk+)ue(NP$yl90@}7B=TH4o{ege@JLnK1QVqO zG`s;NrwmL@H2b`7#B$zLzs9tfU8_X{f(fobe=#3I&_lIS7X}Lh&5@+Jn?$YX4n+~eUK@F{r0%P=amSN?a*Oq~nlc0VBltT6mdXE$5=tsqik#KchUp(V zZ!{k32)I5(eRv)v1%ayH8cRBWDWulEd#l@8>7T!Sv7gE=LRxq@T{kMySJIczb(y@L zYS!#N6QHY4L;o=BCA|%fL&N|iTEvOgvLh$8G5|2+p#2cEZnSEk5(%DB?AU6&`jV`D zOd+!N<>YEFm$$^UoY>24;B=AVVZB4$n+Vl_%c9p9L=OC=58JD6hz#U}G_tuzUG&z%0J3H19ISG;=#Cet^+R+(dqQ|e1^t0@$xhy9CJ!KoZz z=pSe^Bt4;w5iR>g-Sw9}CirPTFvSN%v;Zd3v;4Zqj-_8tvL~yUsp8`ukuBE+yPGc} zP)nHMfDC=~UsTbj#(PRE8Z;EOFaHCrdB_#tF6{bLC`ERu|D!_y@c&%PYc25h8zBd+ zziWka0cq}~oZ=VpfX1-IyyZO@?Xafuww?(R^(2f2oi6obrg$0H|EGsAyqwgIvG8V@w5DwzvcaV83q@O$V;Bvn7v);Z(i z-cpZzPO)Q)Df2HE)%$#U97^5fF(q9wo5cnSQu0*0(0Onl1b|7Rv$sq)h|mL*P&@A< zg2bItem-*Gn{KF#_bu+LA>cJV)x4vGm2S!RPsA!P_q$fY6qC88lJ_?3mI$i9f#m+E zykp<>eRiOI{+@4}o5d}Tnf1(I8Ms$LcQzb5IZ^9eo5PEIe()!C|AFZK1A#`cnGmO3 zRyb`O6$!fj3Cb|^fo0VCx^a)0-2tP@*%OzQe7!%^%_|A5CL*ogXSsp?Phd3L{s|f9 zf&W_N?WYiN$_~c*Il|AK$OL>gXyRU|=mT|-cmz~CCqX=83v`E_q#eSr{msV6@Mt-F zSVP(vndxW^&v$ZU$Njk1Ttv))7OT`0cKvK@7VZpf%I4qrJ#ogqKqOVPhE23GNIi~! zqj0U=urLv;466n9L)3Vr>sBSbUBhz2caK(^y4DyCnyVq*RWJD$JBEmfQ+ePvfNnvi zi)}#4;Zb8WU<@^5f~fWH zqRIEkOVp=3#+*7U1~2G63H!5W8@v5gN<31CH@LrtQMSSv?hlCBS2@+dZvr62NBf=S-ZHzaKq59wuU_AGyLnF4RqHK}z zaes#dgg@ToJ9iSGz^z<5@hOvcY?QzseZDF@U?i~4QU$MnI4>NK=83*m#RMp9l^GZd z*&S=1{0Ew--hZx({;^4(aYxK=58v#aZa3VL?5+f3KB43b!{9W(#~|6FgRbx?>6+U2 zkAz*HmI^q8N=*o;zDrW9EDo^VSak2tW&Ev83xA=}Nr9FF>GJJv*sCLOX4k^Q1BmZqnUUA{pJmmw#e7jbx}dn z>mrHd9{`e(6nR(k*cTTVU2G*f{57&7)_a@kfq8eKT?2L7wWOgCXB01j@cZ|8MZl^$ z;Jlm+REAVIq#2qMqp3WvSXsCH{Kot&Oq;Xg#H?1rQ#0M9_y7)-9+n z(|%slFdHW>bfT?D_ks&Ycl+f|1TbJI)H~A(r2)wibFY+wRv#4Fa~oDEWQoIfyGtpb z&3WVZCRB^rmK_UZq5TK^AD|yN6GzrhW=zDI)3EmMA-J=v1hfPnOp&IVwSEu!9CMEt z+K~B)QNHwYT?qB$HwXH2@x`F6Q00_|x!*0=p%-&sD?|U1&c`F(SvM0|`4uC!`Q_nC zuL9-tsfXi}QOQJGYtmj)=Z_~ZdGQ5CjR9}RlOL$_SPTTY(S2JPC#Ng+qTt}_)ZOJk zQktwwj}cT`P5L82@<9@@`1GK=qnOOAr>axgWwc>WzaU{4dkWn*Gyh8O|7|yzXI_#0 zCNkmd$}~n^WRO}w*>B+6>NMqh{E>Qb`a8p?>PTAj{ zy1}lY?=D3SM3NxzPFu*5?AHFt<%b2C_W{c|j^>w*GIPr+SE0uDd=get-hK?M!LQ}Y zDI0aTxdK`eoq;bNQJWfllt1{$&1f~a15QwDvNrHFk+n~;C{bE!h`<)Oz_vnb!T)>W zn<~{F8Dhn<$)U0YAr`Oq44;l291Zuy7(rkYgOQZC4#%7k7ZrrV# zwZJFJH;s1<&8PhD0EhC`#?k(=)iW?RT=>oiw`?NW#RVlrn=G?B4@ll`>*j-4RmaDu zxz>f*IB&%ovF!u}`k!?HP1~qDNsJ^m&!f~7%(60?<_k+ckqOO=ICC6rTE%-DlJ``! zjO6T@PvxHrbu=k#QCl=5MX1=ned_j!qXxa)^LbK(+}y*Z-0`#0J4Lv~9F7C9GcD)# zU87*%{qpvDyz0kW6IR9vQY_82@B8DqU%)MCar_t%&vSc*-Fu6u6y8^*i zPj(i=qb%pxtrj%H7xVFg_H_;ZfaoDF-&=4FvVuk~KRUVClk9GFVPR%s^p2Nh+C__( zn8k1)w=V586Ud7_*FAXjS^S*7MLqE_wX95FSHvkyW7^2awU%zvdda=c6`HUqicyxR z>D~IUD>)Tl0xm#^)Va)N+h4qkI{QM0;@l1h27rNX$NdU2bL=w$f**bG62rgqL2qoy zErkVQR7oR#B`Nm%Q{twf4HI$xa)Xj#b+6NU*{D8D4Hfn)+?;n=hP5W%3;qRyrwsFr z8>7W9qNAi zP?PG?o?y`u&mU0ej;uM?+r>!KxI_!+1y%z{KiFX?kUW8oa_$Zlrs%+nDXJRN-GN#}WE2u9J

;)nHSEMCOR2qY<5vCck_q;_FvVeJ^-1MZ4|kL)BtQ z5B!h#% z$ohR+xBKl8C56x#Kihd)QnZt#3Nk#Q|Nok;)5fZ#!GZrkZ1Cu-BpNP%MzZwn#Omsb z--i58=Egtjue1`hQl)mX{aS0}?2S;m|?vvBpavj9~{VjHO zc8cYy8SZ}fKv_csz?VRip>+!wT7}`E&Mg`=z8w8b(y?cy8bj)$vfk%AhE>T9H}9Gh z1_p{8`S)oY{d+0cFiQF_P^G=4?hKp7&mHmged=pzh6nNUpDG-G`*21nd4)Ep((YS+ zU}5o)V&5)tkWon97JPdCYC-UE=gfJYpI@oz*N3m3ivnc~60d-35+@y|7sH4B<^R7* zfs}=4Imz-d&nJb?XO0Y7vXa(}H#A-2MSZp}={f!9^tEb-eQl=(R|Qp@5S?q9_x;kp*ztABDaX;Da@ z__K{x$tfU72YCf3D9e>s_%mBoZRj8*Q&<0U{yj6a6KK39=3<_tL^WZ5FCzOZRkGb5 zXI@|n+04apqDBg%{Kb|0w#Mj~ubEVJor_mno(+9d4gd2sPz8U~=|>SLcQvdxD%x6~ zQhvi-OtwwAjcym@LA=I8ct1ESMaQK{K-9OiR!KtYFF7Uq1#}d$S5*~)?f36FF4j%<(b9Sn`5!#(> z<9nvM@UlK7Mn}N8-DIl^DjoeHEAU59P=A1u`7@--SIn2ZiLFk?LxYy_OcxSAho)3L z@)vIp7+8~dE$bJ^f9*VgMuO{k0JUE`Y-$a*vc>e?A%`(YZX}xi$rtshcaj>9?=uM` z#QqA-cZ(xI@SMSA`mH=lFA({6d)OtlyAA_0aHaw@_SsUlx?xY}+CPneTn~TO$J-g7 z@MgEd{>Rtn_*HrEtrZ+C&A&wdJX`zSl(?*!tx8MA!0oVJ&N9%2qWem~N>oNBLH}D! zFr|`zhyN&0kT>7iG6Tm$ZEXhS!bM=e)eIOSZ{XkB;y>y0q)6fgW7js})8N4ro$joA z`u>-eK{Le%GEt;q$A2rcCSZPeS-Ku5kycYRXYg_DR9sj(j!bUYmR`UhObcN$c z`GC87S+C)=jPX9cTh(?a`TrL974SeO$Z2b`-dPrkbdWrMe*I~KQ?q^%Rqv>!yH zH>-cSHtVtrq2XMnXS=<7U5)H6Dw4C0wK@o>SV9?hsxg$nv zHQnXEtrJ#~_60z^T@8j_i~%qlHQXKTjw&2>t^J(Nk+W@Gq|`PmVDzqf`sbn5Vv7P|dec;luX(A>ip6{jjX&63@&Og$d6? zoXWrOqQxb9FTmlOPhe=!=qe}HD;gfBy2EVDs!a~?H`#u6J_VdnXdd(E(!o-o7L!Z& z&8(MvUk(dNv9)CCkCU3k)}M)0i?Pw1VxSI_xG&5=QADep7|Kjgjt=$W^e+`Xc&+yt zb}j6ed+38lJR|9G)z!UjZxa(B%3k-O-@U{QgDC<&35xul z+r$l{SVUACGUhTkCYp*!8}Y%N5p=qB3B!0K!(_Hd9t5-t648k#r`*AY>W{eCC!aS7 ztrlHc`-wD>WT#eE)(^D!Uz^Q&Pn&1R*pH&wYksdUU%*h!2_SSE>dNTgY1ppa>8v}z zo1c^ZW5;mq{f$PH%mN@F6TxZ+mrpyxF!aCH9Ar{P2;_zT|TsO`tUWniIB271;y`4b{tj<(0 z(*FF*MKy0fw;t1xVkzc=WU?-NJUv3z@b@^seslH@7Y)e2f->!uKY?!?U{9#tI-~B} zlPG`ZHgmo)c>}rf0uGe^Fsd#IBszS%PhwqgVU<5xEuZSQ@q^%d%1f68#AWe=nnk}J zZsgxE?EUb0$R!FH&gV|g0uj6U8SZ+Y0!&Lnyzm~w1qD1bt2^OdrGr4~a!&bkHplvE zve<^c?3&>+pO1{RF&1%(7=;I8uy|IR)qOPgE$2TKY<9vHBoz{Ey9lWRpK2h_uf9y_ z?3;Mad1iiRVs_*hP#qiir+K%(+SN-k9K2{VQbBte-#rl*P?y`c5ij%WnvJhBX+zZ~ z36NKol}*W<1z%QBC%0dw3hgu)A@E7%=ym7n&HVJ5=EX-=VwG{HqqS>h!!z3ajRrmd z_4muzr%y~o_1m!@Cwg%#)ww#emAgic<$?)A0yUDLr}6vKKygD3X}8s1T(xnr-Z4+j z-_zsf8wjkjK^oK?7pC}_HkBPDs&*Iz_3|Its8)$mkc3pQ zOh?01&xI#>Ew4Tt(2q#tj}I{t5K2Ai=*Ff6e4ya((WVCqTue>vZ>y}6MwF#8hSyk0 zEWDYEjdyOEbT9Z=?{|ZXsb41aB=p_;3Ho0@E(-|M1)>Zz9CLM!v?n!sQ}`w8KhO=` zEp}}pZ|jWk+jO6eX@Ky}l@wLj2ALcgGPBnOZP;a^)RAKOfO4CzcY^orZ@b*Gq?#ha z^dwjFC?Y<)ibEzwt`8;o+>u=%?o?~AQ%ZE2Qk?V4Y_UiC=D0MR-jBYy#O2M0n%3cR|t z0D#&Eakh?#$_N|}3cJ~samqv(u%<{`_TaVeSNu|sn|Np;{tSlGmZgKKn$%^N(!W{! zGNrWjC1bJ!B7uJc!|5p?BcA|D;Uu7JclaxVCYAauQ}Du=^J2(GA0-uTURU|VQ_OS( zv}#FGq=1swM!fCiSS@7<{1LmT!+_W*c;9a6^@jZIlNVZU+Vj|nH{sQ_$s`yO6i}HO`Aqz_3H7&M?Zu5+Gi1wD*`eZ+|`bUZ?qm4mygp>N?03AgqGtbJ}QJb)-+%pyNoaHN=WXIHlof4;-7Imw~Xr-Gp^ zNyLU{cA-4g+RaV>2$Q&PDKe$jO_j6fx1`zMd+v2)M;*hI&!i3}?9kRqd3K}5wJ^uK zWXwh^K0+xa{#HQn@@dTN_ODB|aT$Hjmmx>)hLgcw9$+5d(PsUW`TN<1aus-EMzUX< zk9gC&T{=vu8NRF!J`em{@|ZO78T3u!#J>KTt(3s3_upc8(~{w-M%tU899JD`QiR-F z`-tHmM5?F+QLj0EzXs9ftat(J08gUfJPVQDzDvJBlU~Eyh$wv532BHr!QAlWGr@K$ zw)*cb+>WCNX^_y7&j)du zsSJro;8)Jv%^?g;Jjb$PDOMe6qg>vhKlq1dU)mA*zW#+$zn(gJG*w%*{kF+d zp_xHEOmkb`$Zcj6G1~dv`Did3=B{4Wd@x@GlzgOD(?rPsx zHM}3pe+;Ivt@MUbDAio1Ud(`9~|V?-jMBPUg-Qs<}J3A?|tjHoRnX}vxriz?3lY|M{bvUO&hra_g*|He?^{pg&KRB!o#_D#Q&phX8V&e z=jf&d$%zB)p?4YW>Km=mq^9uzrga{f0!O38t?w`Un9LvB+uLg5;4jImm|7AJN zr964{ywjFjNQ^xpxczGTyXZ&JO}4yf+rQ==!?k|7+Y@KYg zz*#~_heB-Jc;T+KJPAkf2|oktp?=jfO`y;EX?M@h7Mmuxl06-ry{NrLi9!|H8b?tj{ScaK3+L1kXMIE3=IGT5u0l>kP?x1LivmNo z_dkWco!N&gFI>VH5yqUZVJdhj;d#U%dVgarH5UXv&U$2~pPd#k8gqOY{?c@QYVrkL z8T#$LS$!~vW+)O|fRj2|twib{pNJkY|Q2aJ_0zH;mZG6n|pW;v1lav=F zD~F}I)x?4NNhkrjbZaoeNsb$lE#b&%SWhy&yy__MNRiQq)&|HBxs@ryj!3}s{jl7D zi9jf&f*jhe4T6ZKxKL&-t0!~cT{j*bsZ*m3sgU{h;m_jDw2nBh5XZr3Pg@?Lo*KjS_V--Ga%fV}*ryK+8v z39zrS`Z@UK&|-fGrE1W}a@H+agiw(cGPJ1Pcv^Ele0oNxwdrjIx42WmjVQ=sbHsZZ zr$$QAHk*4R@53He0TaWBY@le>8%T>OCDHB~{hqiB44m!X8#m&X!_hTR@GTi_)d*eC zn;1jFPI|ffjn@>a{UFE9SXWp;pNw#{hpg}ewA+O&R_tAnkfW0YaGXf6HuB))EaGHG zD#{tsptLvld(UHV%+6%+7t=Fu>(;p<=VElbIsSescq0RTOG$D>5ugxIdwpsP1jruc zlQVr8Z5a#;N|?xdKdRJ9PrMfy)f^N8Us9Cx_|O}L-9gjcy}~GA<1381D2PPkmU!W=>MPw)q4SVl)JcCeGBo(ibS4 z7p^P}7{%D5XYTMY=1BS|L=D>05)%DaQ{DTK_X8h0!7A>kehNyNR9 zphdhuLI8ZLz09*U*yTv6@gIoZ9?-q)hMc+PW%L5%=a#fU`e0WLYRLxr3egw%I|0k? zI8wZ=4UqUoFpf&?QR6nPkK?&GySMS-_>T{LCX!LR7pm+JBlNqpXIs~5_%$S!}$aMXAuh_1IUGle|{@~62%x2$IlAWT?+tmd)#{yk`Ybw zaddJD_b>I%r+{FpH^Tc=4dskjn}j~HhgeP_5`Q&L3#gjKGp=(0v#Fqs*@HCsDQy} z4abkSO2Sm(JMRR`t6T$}RAp_F3HC&p#5Z>uwHGem9P@3Ba((*s^YpBrEAtZAVJ60r z(oPoHAEke;whsMOkimWjayYqc7>Bl*HtlUGtI0MhQNivGx1ZZMv1@}l>Bpw6RrsuU z<0@y`J@Fx(R5)_97ZnAJUL$UpVfLnxz`41}J$OQ(P@pd{%l6X=q3@U2^752Ag9evh z)ICv&gG`~XUo(&b$Uh=br4YIoRoVr>=Nc)zl{wu5K1cKIl|lYg1a87(}Db$|NjnY04y&`M_6_rvSk*sv5!g@R&j*7EW? z?dLP<-8xc$E}WhN0$DBWK>nAYB&5hLY(jbiv{Xzob3Y`K^z0)P|54(fbe%RFDrktk zh+zmwizHwx{2${cf#u91*DHJr2L93!P0;btC-A|ZCa<1dE&+nmKE8!W4f6CgXw!w* z9!vAJO(K;4&kNl{5E90QF{tU1RFaylH zkG|4}rJQTZY=Z0R`bCg|F~aNd1;d5EMOHAC_?W?Yiq9sUp~^+7Wn0|+ z&7_KAL}w5mt((~W++Nb0_ZBX41GR38ZJgU{A2@U{L(kf zaQNk!&B#Q|mF`*D~e_{Wr&U(W>@G^}N#3{p!xi|4?853)LU#w%w(B%_?(A3XKlBaL! zLA?74{x7)5*`wW$3)!^D>d*ma_Ll`AXP z-os=!#Yc=kozfXFUN*e8}edw30pPVNj$dzjWwZZ|q?qIxv!uwducb6E+EjqK2(h=vjb(tC)e%Km< zi%$6NM3i6X*%IPfPJxPds|{S|6>K_$(Fw@(58s)qHqP;j*lkTdX@KUTPFyDBXx3hl z$5WJq=e$<7iZt%C_{B~gWfmN~NEpF5bT5A;=FadRCvxS=AFYdQ?OsqIMve!4YH_@E z>@=QSZ9lZQWZX$_{d1p(bo6B$2xPbmILF-Yh#ebIyMEw@eN*`#cYo7|OXHN_d zzp)8)!=tUg^{ksj|8DlMP^ZiMA+Ly0>jKBw@1^~QI+$eV#QuJ5+t%3>HNVcTC@e7g zfcBvx<40mHQtTQw1M*>l7$aaROpj5y177nwQOW(x${QbNcCBSo#VsSh!|tglz91;6 zaM*3ikE&`Gr#(KFhVua)oFTS(&4g`tnYM2pQEOKaBAzfm(@005Z5U6Oa+FblPLc`- zi)c+1p%L4Um>I+J099}uDGxQHh_3^sE=Yx&!k zxFJiMx=isw_TAIpk z9f9B6?>~bZ-lzhHV;19}5Y>%13j1z(RhaGKpPM?WCqV&X0+S?lB zh*_5SkNoG5rOc~+R9bhC_2xNCSq>-Cx6YM)6KYrOB%49pb@`2GBIPlT`ayEm{vZPj zf+omyxB$mhmuwEamVHfeM3~3XT~qyqvF5QS@f*jcrVjj{PCsv{K8|oyMZAs3pXBYr zKQy;P86qTmCqkGW0{Rbi{inZ+JhpMm0*pdJzmx=Yco+6Q5{a%pO_F?&n48cc70~?x z#{dKOOll%~&Q|(c&hkO2BnxAvrg{mil`dYT&IImMBkVZAAHe~%xW{lk)CR!mRojjS zAJy8YjY|}}H}EFkB`PZzP5Sa;#{4yU+1L>7_x;}6dmP@JVP$=5{|a3G4`b+NAt>$h zxpVXy&=j?=hJbl%(C;|*UVp9<5>D^TUGPmn$1mcFVRlU>HSJ?Kta-7ks z$J5~#M4iQuh+F~%28D^AN&1r4@-R^7Gj#DYkLi$VM)0rgi$7K|h1nl}3&i)6gX~fV{{QIH^6T1qUbu70^ z{1C1+kIAETQ&%%Et!dH|Oz91zmlg~f``@~Ue8&pzy(df#C(PL}n*F{*zg&hAsSAEq zpzqjgIhN2ozAXW2a~fAE_8);%gF^w3n7I&Ycm)>HultIIHx-1mOWI5`l6*wI8fevZ zI7pmRcomP0QecC-Bx%ze+UKyMi~{?dPq!&3CSO}Ht-iWeBiHjOk?RU^QUy_smu|zL zcdnW*TKIvGB@oA=sC3R99$cF2MG;@UicY%fyb*7AcE}AefG~9M8KFl3V@^L)E{2Q3 zO=zc0Qn*FU+qt*Gi*^F(xDc8t_6!EeGRMb;0Q+eEHYb|1&*XInb9K#wp&|!KUhW#zEL$w&)YY1*FM$fP z_69HiRqrO`1cBVR@}CS#+dSqJ#2ozm4`k5V3;3dvl=r$w0+Z{h3Grula^2%a?0~x+Ra+muyS20kDAXlA?kkK*8wqL z5lTJo-XO<~Z3{mfm|j_36^OlZl+e3TlZwsHQTkl0h_Ji|{ylSJVEApjmg$({Yxwir zt)pA;Pee5u&dOu>KtUh-*&pzleljlDwwqpk*{GAR!9qBzK9z^}T~{_LSdojIe6B$$ zpnWXhsbMZthu|uD1NZ3lI{%z$U&*-c+dGZ2q6#&U-_!T{$K|T-;a!{#(%t^Md*Z+D z3fGyd+Xw}0tO8?&5xe#G9ZHohUlhN8!7SJDSv5*<$NJ@QOg>BHuvgd}`E_s1pkS|n z9=I;bFr{PSrV`S1lT{k~%xHOnN_=FgJ@-N$%NQ zUnq;*&4O-pWVYaatcn6u(r9AfKhKR~-iE|Dm18{UdvR=2Lgp*)G8lifG-YfHJZku= z#91Om@W}3&9<@Xg>|elg?$v*wg?jAE<1QewF3i;XN?*k6II&d4+r!wa&Ba;Y&r?Tr z9h8um!tROo+dLnfhy?bNx9i+Urvi5R&yc#$`_7_dJ8R8fOO|dXq!KI^02FC%%T`%u zWbw`%UrCgxos_!+SG)7?%qQ7x@!!U)eeg%~b(4;S*Ikz|ElXF8qqyicK_uB0_$&NT z4&Dr0%t9#X@w4H^w_KN6RNMtAv#kxj58b2&p7CD{Uc~?#8q6VwDb<-4Lt$M1<$2Bd zq?OA;!ab-*q9$j_5L0Z3B^;}@Cuay)*~lTzxxpR6NHTQzKuZ}L!@~1pXW4ht1w!M1 zWcMiC`Bn>akB$sz>_#L*(*pqBcrl6$Q9d>v`cnfE*2c(j6R_k`vx=`(UVhz_&))<-@w3y$ z8O>96i>*PoB6jQhL)*e6YbFtCBN;{$qb7&>(lKn$qPnXS77wA-GF0Jp5VFIB0~}jb z<4oD6lImtdtfe9?Aqy{~6v*llI`JRqWb4iieYaVci;PFtnZas%1$X)>+7_gms+2y< znzjX9NaS#QEKYc4$hB|??T=qM-<$}B-?!a{5UmexuR#sMqy_%A8ozdXSwmiH&StVP zf=?tWH@ySOb32|GkcLoiyA!InA3edYn_ghomn;QYj`-Q?V ziYFQ)wngIT1lKNJIjn}7bz3?uP|}YzH@mXDt8_C?r&5%b0%aV)ToYFfFS=3g2!Ki> z0jojuvx&~yCTjL-T7KlVbo`J-ph^>}Swdt4GOpR*`>4GHsa3(y68D?qWl)h|%>61K ze$t(weP^$u*>bKKPN=~;Y_k2#Mb&n-gmQJ0FQ&H#GZ04jk#9)dt2JK#;C5wqM~~PQZN>kh*LSOTof7M2A{C^==P;3X>3^{euMuXBahQHYQvCY+y5)CyLND?yJ zk2RP?vRB{Qr@37H&vuJi1t`;>nU0F8GF z=w$+uCRyRnkGDL(c-~JrT{VWUO`ANmhS|bJ@y<9QZQ5ZuVGAXDm-%+7+q{*Qo9K4t z!#s>V1;hsD?)P2ig(;R0R{dGaA~EZ2clf+m@vNHSdo%+z=0*YeGy_rw7Bc`LG)L$T zwb@D*{|t;`jn|Io><1}**wXtKV%N;+A5K*2&MHyXpj(W|u!eS#2U!nT@o!X)XokHgzDR)TjlzkxJ%T6(!} z=*bXZa(4q+_MZbY9i=6IGEW-M>aP}`&)zP7_HuD0(ybVHrddZZuuB zf?7N5t{(EuD{^R4|Gju*@XdH-_>QGH$vVm7upGdY;emGfvLooWm|~a)^JV9EW*hcA z@T9n<)Py)~<3+U8q+G=52_=}l?-OppPmRG$Fh*RE)K;(CHa~T%mNTjD?h?=^ZSS8E z_K@h{w_yo!hA{k4HlzXiyEr6Erm}qzYf_n$arOIs!Wa5CgN(kc9?V8F1o>!2ewqsF z0+PR{rE*z~B=(NeFO2M3^XkpCe?3vw;NSjbeJYIvC<@~`3*EwZLw-Q~y9E;-5~8SK z**`KG8at(8aUh`QwN~Vn+Qu&sBK-=Q0MRX?kj!%#f%Ez4)zjK7gf# z%bE}9LCfImAGuLIv>WlRRiGev+pM{eO0u%6i?E6yTbIIW*sLzgc4A(M6`wLJcWaKi21foWsUn+z?B8i`Vc2Og-XD1}e%i)+C zA}9HbFtnM#{57;6MK^cW&?z`3K+#$BM~y(>)iqu`;8`1h8)m_}s%7tZ z>R>ayBG@T0^UBGe;vS5%5KQAH^vajXHE@Lpo+ncqy|;B|<#1O%M>7l$8)l0uQzlt= z*LKI<0)}9iv>XQ|n0gChoC8dx*NUT7m zua$X!wIedLLT~~vuM`|z?Oh3{9E9pW6BE2I(5%w^{Oa?iWa55GgQ}J|!6JQKvXG9c znaK!*@>H2fqqxhJw|;*1L-f;MgN-rj9UP`A#cwMkRje(&(!C7zF3Y2yvm zoP5$gy@(_jOa@?9eU1%uRb~&;fAy?@NH-ar7r>Ml$}&mgBBuC_Y$VO<OoU)XQl%j0Gk|FF_hYUVu3e7`(v_^wICx%p{m|un#kV+(ic>=hay7I-u zZ^odYpmJImt{r_46#b#e^mQzQ0@ON4uJ|p@phZe|I4zKVkotdsfQ0A;=)%Ei7nABW zLvP!Rshs#bgf znHg`7b$KQ9!8ZztLd(GabtP4DjR#qgwKeTpV|!hkAfU%I=y-g*)@{I7iL-ix4+!Vs zz5r{chH<+Quu>2s11wz~tTgAtc>&DO*(D~`seDXSgiWh{Kh0NDdWCr z%({;^n<@Pw|ANLwcGK%xZ56zM0;;tmfx(>$fhBz>;=o?})VQ}zyhwN@W<5_{8dO&O#yg=}9*+U=p}3BJ()?PR;^G1{jmy!3q&Md~fglfxY~uvGuz;$7 zZDo6GiXNbN8N2uA+ZwF_oA`e!#M?S5792Ec_Uuw#L(=8tBX_(tq|bsE?<#aYD&aj- z#%TKjo-O+mUl&gP<(p8V0o-1O#pHE3VSr;Z3EnTXEhpLG4;D>T=5P{J+t#Lmq7-BO z584r~V+v7CeFjmrlc-8nu~n2t+zu*NKp74{(zF!d>dCYU)39Bb=ix+k7;ZB?w@jEO z(tONH4;lO(b4Lb`%s(EmA?Sb!r4EKfZ8F8>u&3}Yxzsz9z60cs>3{}UeWe*fUW`Cu z#E3Bp?DLn8dEbp0Hn)ZEqnoU@@~8dzLC{HidVYEOJfp`(skgQ4W>cKeqo zZ~&dWhl_iJa#2!S1-C89Q^1L361WiuNw$C0pA>|yosy^o71Pj~f{6|Um)?`tOSjik zW@F&o+24heBrURr{!1iNYijq@U>UOW$uJXcKyxbo(R{jpO(jrxdR(FQC-V^X-tCm{ zy8R?cOe^Z_yvp`-akK&_t0zmZ0ou9AIMf)-y~9L-z_e_FYeqDxB{3N^OsD%eub5#U zR}$*a+$mydx;s6PJQa4^$VP+dt|yg@=1#Z%g6stwzBy5z)Tnzz;omlx;eP*$r*a?8 z8kvJ+Mzq5$w{d!jNV}tuAtfU1xj#)8d!3{fhvB_^0Ov5$T){hPX7C{>CgJk*7F?6- z!dDX#w9v$TEXmQN}#0QVmxc)#*Srl7&d4v>|?p$#h6>JG6@{EBWue}!4K02ZADuTmh% z)rT#=fj2ELk_D+Rrrc$S-wLVz6;9()OahMd>T9dj3!n#vDy-L-IsURlb-Azkj7AiX zj?BVHSkzEy8)NBgkrr^3_ZSR zBED=>C=rbPs|?fO(Jy~)rMkb~*DM_g96A(!cvL}Wfc6Y6d{GU+epgU@$NR!a&%V7$ z)!%3hd zO8zoG2fo*-Xw)E_F=fwr&>RQkj6+49i26lkmo1p*dA-Nqp3vl9+D7o6yH|9*tH_kb z_H@!`u+=*3%?M}tGQxZfT!e1hnujyV)Y z22x^=NBUG~Rvy>AtD(!jjbKsyWb{vqtDIYkxE%02eZ9LxGrs$IC~ffPS?p9ekz$Mv zNg-S3^h0}v`<`(}b{65!ro_!lnc{AMtV7C@(*T;nRov_fHEepwin%d9P!~K@P4Hb{*Y5FBggq~gJla!cg#{&&sJ*YJBp+(WqY-s0koXf zKK{FjXWr;XGN7-(96>k46@tS^VAVL%zmHXxLs;9ZQ`#;MLnu|i)|ai-hWqt4di?G=jkQj*7O;s?cLp>w;S0|Z05sSIn{T?M4~Jn(+oBp!C$GZUYGg9SLwGF zPhA~0Tk1MQSV*guPPk%r;O%XbSR7Mc>B=N)fs7~iV-y#XpZ_wv^>w%VSxNHcGIwdS zb2g>IY7w0i03Fb_69+|LFr3^!3XNUyF%EHe8N1Q&qA$XQZh5d}G4nsJ>~;7$oOf3z zF-;XmJ>nn(=DRqX`}0hOq$L+boZ>|09?xV?+n2QaS@xBBUIAlh?!))HokB5DQ=xz- zFj9;$Yb!|qr+)s6!ucCNg8B^=>RxO7mN=DzaruUj07!vo@~WZ4Ha~^GQ{OjvuWc8T zah2$OD>w3HD9K9Uhn{cYBV+i)xBxG68Ys=DfDT|x>9aW-eDc%b6Sj-MUMkQC8n6LU$jxxh zRCq4q&d_bwB@k=|>+aRWGDBPpZa6s6`6|Ent!bDA)#a6T1efCOcztVSwr9me-i6M9 z8<_^eQu0|kB=b(&{GtgGZh7(66-{lEc}@e~%69%Z;F%oq*8)&K@Dm=|`*@^~#gsW3 z-sM)$h3L19PrzChs*TkMN~gG&dDH3~!cy-pPOJWhTd18QWuiw0G(~FX_}gUm%aKw= zyhMcDDY&z`1MsP>BiY?u0Z-=jHFa|?EH!gg+vu>0LOivV9KFYH0(4st<1IZv9MUtr z1in3j$MoJ8pxtT`ZXlH{654)JS5k|kEdc&56|=CFBIx(qqX-E|n+#gc+AA+Z?XTOP zTGE#=05@nYT^X_Bq)+X&-qRaq>&X=1%^QBvx~J^ycd(rp6ANF;DTz%0i}gW!vi6vy zPL92ICA!q3#yhK+Jb332KcB*?QffkZfLP05kP zHw?uDczRfV*=TS(3)q=X2^?JeN;>`!{6Xm&qtKH8yedqnuN+3)@dni+=Yiw{OMv z#bQ5&DKP*{5){DeWPr$!ZyH4Cx!?lNRW@!^_nQhXcn*w~=4@}b;wV!1DG*F2Sn-EE#_G{=HW z(QJZX%ZN!o&o5_HDiY7usm3Zeb|!!xr%Gu2c86~NBC<-d3kmMaOw;Vn%DX$Anqn9g zN)(kC0=E=)qNq^z-$NI=n>~@zEgc9qSU#ueKxqMAqiJlD>0%sN;17fMuL2|(I%=;7 zGN~bh)5K0Wka&^t3o=Cfxw*XlKX#{|m^Ms)=m&}L#fAT>!HHt+xh#fiCJmyhaKJz4svX2@%LVZSndCAxiDAy#GVHJ(po z+W?&mEgAA{x)YoI!LyYX@C+0Me0NIv#`q^DJ07;mAy+{LeYlk2^)yWW)&gM+iVL_6x|&I(Ze8G8@3`47MY#h(_rVm}jl1R!*W-OUl>TUec4U$zyD zS<}^ZnCIM#@&?W@iB;}mxt`U!{rE@_h|nazeE6|ZF-0i6GOKxGPKNfwf*z1q$t5wj zGmbi@gJED@*kX8p{A|OFNX6vqJM;S5mY*7P@1ClUfS;mY?qXfhzbwmzr*WV2FKOO# zFLVhW*CA4db(BgFY9CrNXZP`gN_RWjTfa8Akv0&bR0|;!6Y^qMGP>!Hky}hrOoSa2 zTk@L_LjNGmhQH<>(!ZJ&lQ(niiV28vhB4VLom|=AOgNbaM$W<^f7+G~+biyxSMAf> zU4aZlPWqQhCE3VAsSC@j(pO#cPd~a{H0h8yPL_C1eVWAnJmN{t=6&NOz*c^?dS{;1 z+#lI!*{|R8+$Z%%i_pG&`o9f%n%m$$rKZ1I4T-CkhiLdlXXd|A`=+#alwz*r?yB+%ge_|J#B^lzx5UiEI zv`b0SHK@k%CuCbZQ$&Hzz0g-objB!+d%}j!EBVQ3PKFBI4Za&bJyy0Ao-K3pqjl>Dx^*qdj`=@3z8wv4Nd#oTds(zEA2xj4TuNfvJBQOiZ{d z&ZqwpEz#XxFP!?&@kfJcKeaCm75cc@7dgTQ>dfBFqE3}Osff7FSK^-4N2quz0dK#3 z3|Er3B>o=_!4l29e3yf2rI%rWJC|ZwnkhnF=HrUj8_=YH?53wY<_WpaT83sQyK%OW z7KU-dW7UR)MxBphYkaX!K{0O@AE*@wf+75a!J}`mtPm*!v`SIJXhJfn(_B?`Uh&GP zkB`u^_a5)Xgg)BlOX01R--+Jd8C!u4`M((K>zC3W5YqEl)$v{)`CQg%P2cf+_ghl=@{l9 zf>8ZOwMWCyo(Ii%mR3Y0Oaq^nOfOlYWL~&1mgoGdM+7aQfjBFh#_1MylZqK>7!}_c zzwr1;p6I6Pv=VEwj}l4zwhcQ z*`ha&nsC@Y`=ORCt!oc*9fiOUNKD_T+=a_Kj@82|y-M&zo4Ya{`FL5Ld}c&aP;xW!N&LvN>VNrCa%o^QBgInzU#t)73L*;lwA`1P zMtUqh-|A`jOu=B)$QAQBG+|rDICqC7m~Oww#ft7qR?G#OwHXMaNE$!XPa8lVc=IZ| zs}DL+bN>NqHokTV#B~^Na-0QA@6fOXRI{<9&1pNx+~t?j*|vAN2^jLIY^3?|Z&Af{ zNR=a`1|(y?-XLZf)Kv4%Tz8TcGHh~WXygmH7NvV%bu0Y`aI{=G7zpK=uMTE_y+efn z`{6v_%#PuZjDR5Kk77L=q15gr4qLgR$_zOBJSnVo9dlCugPZPp8m}$xW&hZ%CmUtm z%~uZ$W!m%#eocZttE#Pq5m*j%S}M3??Rt8+0(l`O2#Sl4;-)a>Wv(DKhYP-fy85{N zmKL9Q>O!4ar|xgn^m{*Ov5MeDS5siCiv;1?k0+O;>IV)Cqe*Ce(iuzsI9HPem)CYk?w1 z9){Fgf}bzdz4X%B)UMoqi`Nm-5A5p%x9OU8U&`kGY1E zU+(_3()4icIeHL}e#4pKWif6NoKo-{`DNnyzTV}qjjq}IR)z08CnNiYVfcaKBQAkp z)e{y9Js4&JaX-rW^B|i0DWOeZ`yVUjash<>_FSK6Wl3 z_u6pJPG869;)W^AKXPE50al#v<3{F~8K{s}BkvI+X*$?)O}EP>7$RhnDV7#5!y=B&i+wG>Q+2hBacA8MbEh{}^427oRzeY`>vCN|J;bdhGC=T8s`+H#4xfxomxWNU=&Y< z8gj@-z0*PIMpUsq8>KPr;MaUFJAm|(+5#Dvi|M3vzPwn0BB#;7hfhoh4)yAC=MH@M zx?W@V;x`jSIDvv6=g0D9<#I|YE~N{c^Th~_i7yr&k2>h%!@FvvA|D6`_R9EF`10K< zB9VzN=qqqvP0x;(megZE4>CLgV5zvQ8p-hZ!1*P=uQ?@VNpso5%6$NvV#ydQhE|3J_iPIad=BV*DQ(nKeyArFvxx+HXl4tPdBKA%aon zFDI9{<1W82+SbC|;7N)~5+B-UChLI|p~tI>!^5)l)n_23>Z;+G4(d8g-VgMYnixFYE?*v+jK zXWgL@85{(KM2`Q0waA8|9aKFwCu{fo9j7A*C9;$~h*n7S#( z5^mH47fbL{;<6BS<~@xMS6wM&`f>66KCvLS2P0s5Rr!Z?;C`{icMr?(edB6&CS&NJ zW{&2=U*|5RQzL?IqR8K+TRV17u3|;{-Sa%&6)OBUG&@brQEDnYl@xkJI!;qEZ0L?i zSl<&br12z0@TetXh?I8QKw@|toFTrK!lXMG`Vpy)_`8jUPnaYoRjfW`sgRyG&b{*5 zVFS9_pSq5lqac^+#gaXhX))Wa+NNOpv{lB*m(w|R@W z_$aeBJ!)&k9_*jv-rS1h`MsPtza#mRuUaxvODq_ao->>?d*vG9hlgkgvjCA3*Zn14vw0^f65hkzC_O~D??$4Lsgm4PXG6QM2 z_96N&t=Z=3w=QC3*CJlhe*JexSNrFyj}^47{TkyDk?e5pD(ZQGCnq%2wZF7 zkT+hFrMr&6@7*rcJUD*;cKcankcm$y?Z6q|Q=91dcJx6<++~xgO-VT1!|a=b`-?%$ zJ@dj}rD`c}Fw&=`KR>HeO&vLENc8U6xhhWEx4#BhS4mQV1Tbg3i(N39qNaj&wO%W{ z@HDE$xfC8{BKRu`cnI?S>^C}HeqP4)>$1=I^fv~yijJU1& z(k33MQ=B~R+_>$1IN+L`?DYe2$3eh=^90cc>tf)P)`}>}JZNVT#{e4yzkZ3U|E!V^ogtMLz zmKR7K<`Ejv@o@IN;q#hU$rVJQhkE!Iq`yP4yGiLZl&M17k2-M9-BER5USvh;nT_x2 z4niT+{)Gfn>E8mKiUyKOWxzReh`JYTN*qfw8&8=3u?+n1rRQDSC#G0ct0GTPf@KneGg~?$sd-li{S8br1l9vB9i5E9RpfzL)`JQGi{@ zLP1Saw4nX-Gz~vSGz|O3K)0AOqdQYJ6-k!Iy{~_PoJB5~)tDAN>e=Mum)>_3SDPPs zj4~d46SgHSJ66zY@6`5Xep5#CI4n^e%`L9VHW2e1q9~3jvLP`jttp}Fk~{Incp$iC z1wSBYq3yOcye0Ne1$f?Q(Fc9IDCuU<%EfpKlIu|jV*&v zq^w-5m^|RJlGO(H*Aq#LJJ9%GgUVgeZGmYDYk#%=22oE>&AHD_9;+P9`W*qdCVXc(@_;=7M*i%kU4{eX%6djCIl7Ap0uE_T1To zgn4VF*4SzJLEbR{nV*(|3b6_s7n36-*jBfiwb6F5()QaM#&Enf)v008*Sv!b#@2W~ z$6RGN@djgllOG~IcMWwGVe_s7&z*+@%M*2TYIOJ67YaVBLfD0S2T~O`C!_^b-;&VZ zlxKfqXyJZx_$FnhXbZ$Jf=GLE5KpKblz;gyv6h~w-z*o7^HxBAZ(~XMuHb!gIYzJU zRLDzGe`_LaG(o^)1JmfO@UxYjphXrjVJDBTTr#cJ<(W@3;H5N zuq6>|@5`iun{+R$U?P!R{N{w#mOZVPn3IHT%#nX$NSD#qKzH9cu@H5mss(mjyBkxFqh z+~1uTXc@Q8KxcM&z4qxtcvQESr;`7VJ8560A3@N42`Ii{FTWEGnlaz0xh8I(oeOc{ON@JM@1@o4O_nqu>lmL3)0t)5x*BvD2#^)W5Z@^#rd75k= zPdBdL!@~Eqij>~-d91RtT?Q_`r4MRyqoyPRMJk%TLqzu-+F|GNxR-6*)o%|Zq~!Sd zOrFVs!7H@|Ym=yZEK+6VSy#8f&cqJQ*JRQnHP*`IzoP;F0ZeXW!Z7<~<%LTX5;yTd2Vm-{D@oTqC+?%oKZO$^ErVm12tv?imqSR0K8G z^drMdFXH%{#Zy(dK0Sl72o*v4A=2S<<}pU_g~ENd9FVWCX#ZG!lakA19oxNMEv8p) zJ(iXVp1`%=X)ih(TECfVuucr`@M8qRf*r>RRP{EDGTG|!N5&YX*Q|Z<1Xb%U>E^HhShW$eGYNRYT^2&T{-G|^)scO@;k=k&yK0O-dq!Ou|XJcnw-|7e;q?7;H(mSx?&# zQbc2sv(6o6DLb<-`>A{7Vh~JFTo20{^lO14*<9=yGSH%ClCByO08lDU-h)`Xp+a;I z$1^=C=C;2lj2OuP=kNFJ;zQ?WDa(p1qA%?e1xfWXSke>>6`zMJXa((HccRAf)WE9k^k8tWj7uiNB<)c2zY;U*#*AMVq~m z9vP7}9n7cSf>)dv+ZBqnJqli*A|Fuf6Sg3`*xu5=LtURy+{o!;extwM5aJ_46VYae zmp4(@{BB#-W#%Yej1rb4EA!!w$K(7!Txv?#EZN3o+Q3(G%VbK|q^R*A8Gk47Es zFYSqf(R3LqgCX=|)Pc}liBE3X?kGX+=(L2p-?yO^HXIF4^5A7eade1g=B?kpiT-!lS6N_}&3-f9NcmpCj(SB1I!Ir5#e zuvi2-TX!D z?NtMFS$mz!5@ctm6cdz~IazAXbh%p~%r0!@N(J8A2;(0LaJdE(OPMY&9@}uclqug^ zu5-|R{Qj-;R!EG_-{eV>AX%I_9u+uQmuO%d>+kF=VTX_vegN0mE#)&YDH!yt9zC7R zBYU69!`xPw0ORFoAcGpgoPYk<*6F#AX@w52b8*DGbRLhX!fAKy@ALt|(FB_xQ4&nC z`J(AV<8Ydrgw$;oU;eK3syg{ME~`;}XVdyav*JVIkRzs@lGAZm_icD%0*Wk%U0q}9 zb^Ne?qZh-!x>^Fm$21nJJ6nSu-i9)3r~6C91&nF1yB;0cQHnz9^EQsH3D+5Ex(Pea zEf-qZrLkGh)IXCkxS07SWLf8rc087AWfGE~8AAdeTKSqn+94Cjxg=P2hGYVXPVju2 zsfHzYXs71vbT}BM!;AMHK+T10r%wi4YP`}hTDAPnHsZdY{@p?i%V)M_LjruoU4hp! z822l;N$51c7hG;1cF=dw27^SgH@z`)GlJ@N*gv9dV{I?BFrtidN%39T!@KitIDF_y zv7L@T!wA*oDd96QISC7#_x#6eM0tZAp>~FNHOa-xqRnAKE1@q@GaD2#&n(O}VJEt3 zPEL2xYUA8Lbb_4HaYxoG1^-0owNZqWTzC(k$$mxW)xnX07yS(K+mt2P6;`j){+g~W zh9vDe@D&@_D!=~F(|aqkb_oRhsiC*uWLiqNC4^fJ{9KWx3N~y4zlSCCWW6zsAz44B zyVoL*uuj3`nT(L53)~I;hw$3U3h!XwJVY@aN>U^>#}t04TG6~TP-aVhD<7xdA5e7Z zmo;jQ=0_cLF@G|XHEFQHGrLK@E22zm)?3TlP;a=HdDM8>YflcFGDFq`alS@5n48|n z=3Oic-L@c79^bB+If$h$ zl(l*8jB8e&fMcuhQ{v8KI)+~b*XS7Z!mSVTH6IHhv85c$viVX(_~gaf-nZRR(3MKk z&+7Z5Huu=Q3uN(<-meiO`q=-z_F@pf2knroN?jT%%)=}I%`qcVKk>XfUKD~zO%Fsq$lUfTylDNIN=I5*=WxAfMN5dByq3nnKT0jWIkZ|KEenBY$X*B>G>)%?chf^r)5u?5`p)6wUGW{My>J zF8I7%<+BZ*XQ0Fswgm8Y$yzX!w%1sMy87ps=8gTH@X;rlbFRsz^$T?25qM=f`d!if za${TvQ)aG#@+f_d{X`pX`LD6NFC``ug(*w$YQ{OMFM4%3k9&LqTXgq;wbuEnTdF^J zrP<3eg{hI~6h8ol`X5mEYnHZH)GPz^uag*Dp6&Zsz$;@>?tx>)zvIsc{d;9)zahY1 zhpnZf{DVYMR3S_d`@8&gx1Jxl=&|lQi@&@NB1fypIT|UgXKM5W1eZ=h<}knbU;&5X zV@8%)H{lZ(YfK+2-U)SDvp&MvznPfKjGaS@fr%(6YOK2k5{+V5IkF^1u@63#Y$G<# z1dIa`c#|S4(#bgAjSK&L*R}0iCXVM$+jWA7fz9=XQ3P}PpeMIiZ z+}TFKf9%SS)jZHg$Zbx7bo#d7j+u;v?wacv{{gNupqQO~6Nz$M>D}tXbuvF@HIiK50DVb=xw<$kyMq9UFW+3FXXSjSq1=ev|krwLieU zzcr2dSt)PYZsZlfcOD)_Y)f6)MwWy2jo<0asvX#mKiq!m;7zwD7nm!;_2dXP{3n}n z^F)-`?I?@jFsQOCHA7c5bEzbq7t+P{-RhMz*bI+yJ-vF8Eg?-K$lAGnbhIukwaA7pyR>Be+A!Y)2JCjEQ;cW%MVu}{{2qlbhS=X zs%=hwN0jwU-26X)HyN<_^t$H+d0!Dje+QZ6U(&sW!P&!I8_*j~q)E$9eOMiHV||BF z{*HE|7pe^RdBY4^-D6&?Fw)EQrYj}ZIMgG)Pkis{X|40HiuH-fQghm7auaD`Do=)5 zV+ul3(6f$1kCV!~Rqs=7Bc3X{s8Q1kmY<-X(skeju_xTEX6 zKQL>J5)DVX8)MFHyteQ&5q_}1^`HpM)HrCg=mL%$)Xy!!$Sz#HBWiMdC`Cg+yt0k z`tMMjg3+7ndbB&TaVmAo+#|VrGvVz=7l}*(9~ZK;!Lwo(qq^FA8()t8T{>KXZhn8@ z)?I=OtA8{RzQ2(APrdo-0ObO+O~RtZE$xuSFU#HNcA8!HqI@5i0QeUf2A5oQTo?Ib zurHjVV`JTQg$n-1jICtNeg4lJ{#@PW%#l>8ho6<7&i6Jo#s&vMCV3&lMe zYH4mI{aNoxFAenQEkW7M`Fz{ozPUvqjT4xvT31)Y9IYdVQ(NJ|H>)9eQ&w@`+ejn! zQpz!OweZ|?+Tvh`|BM%XG&ydc-D!uH^yoXBery(8_4irYYa@vAbdGyI{(W^_?afC+ zk}RIu5GHRSMGr*KB?v@(Og~bRL zrRYU5Inl{BYH;2vQr*5-;ohVwzJQ3TJ zVFBvwh0wC8>`HswKNf+F|61C)iXcW~C5lF)It6a97c@iiL56u`UD`9FE%Pjyh$FaLvS^}MUeX`1W2eo2!-ZApsuFkIc@)JnFn6&j zL%@YrrSBa!hi$g3uUEsR#=53z4ZE!KxtzD1kl!kXbzYn z1&l2n24PxQc)3*^t)BNT@ApYAn_-9A^wN1v1<`h~C2!h74m2V@^D*IdV(djr{MIH2 zJE3z8Bo?xiqRf$YRlX5u#4Yp*^2Ooqxqj7AF(5V9+CRBmTw)Gc{3rnY8hBC-H?YmgFa!H|03`!W%YX} zwO1SOIvhtLY2~i6U&(dOjB0!gcCmpO`n`(&{ey&9D9-R6>Jd;XXHV^5n(KXG=31HP zBBz^FO{i2_?HiQM_|80mNyp1F5%KMNt==rka{lR}FSkhqmcga@r~AG0#tGDmjGnc8 z_xi->lW5EMh{n!*{tXYE1fQS+P?J2RNqWF<$L$I-b+4m3eD3ee!|6aSTv=QA3~5rFiY#x>Ar-Fpzx*& zf%3c6#7?xjuI7#s1t{m2!G=1Fpu zR`R``%$HY+zV8JxX~!RZ4Jh(;S1@)|Au}CHdY)(<`z2ynLUxVf2yLipHrG&gF_Hw? z?s`A>g@|^ykoHj;k?n#q7}NvB`c9P70@(3J28~LAnCWIqg=#NZ{p@5fIBu6Oi5TA7 z&J*YVm<&X{3`mJTd)@gD7_mfah~C!~rO5Pk+2D!~8K%pm2%24oWHf_j0|+=9{n-;2 zx?@1HRVPq&!aT<-EPxPGh8|eQX$d3-`*I*);WTF@k4^k4gH1`?E8pwP;;O_!(l^z) zDaWP57tR-IG|w?w{h>e3N+k#+Q+0F7Qxb`eD>e)sQPiCWut$ZyJQ5{UDmy9XtG>OW z=}l5Oo68M?7COn0!cdKNxA-d@=78lNT6jA%w^h8Brn3!nVjcfnw;SW+q>9{s?bJ-` zTE0f0ZBvQg19JQ#W{BlSM_-vWV~9}o7?tfg+ai=0rYE>#pt)zWRG_A@Sd>=Bc#%)xjAb&Z?#!y zyg6uOB@QTi5Pw*mT7V}+V*}lj7>0^Ggb+vvQWnK!31{iTD~F2klJ(#G-Ah;^SJu7U zDGAhfNi18O6%XE1MkKS?dxkYFs~s>x&i3-9yyCwL$7W~IU4gY9bX#sX zlyH^|#{{V)rZg=aIYHS6%B)Fon(M5ENr{rgOIW5e@%ieL>_2wNW66*%BFJ&C+_%Tx@mk;@b1x^OcQfW)m2IW#_A~JwokojV2Yekx9(L@7M z;^ZsfXky-HwlX1@rszr3T&?8({{UV=p}u*q%?ohSLPMTwqwuGTyhGr9OYPntocT`5 zZY-o@O3pr9a?YLW?I8!*6Y;$9?oeG zO7kxOd{pq?k6>u*WH#E2ITKjUK$&sDWgo{MqYe+&y)^x6^Gf)7l&0y_R%rdF4T`0R zrmE4BY4!|p#eDhuNql_K{{Ux?hm!ciQ?;J-;qMw~w;mkvR-0?9X?I`=?Jpu(p_6G0xkdzJFa#WMMr%wI=)$}u zh>tWA)opYvT{_dFDA1QI?W20?e=*+}{yqFi_~WnLYhE(dCHpMpJe?{DWEy?4$DM*W zOBE$@eqsv}NhZ8W%y6N38C6tdvk(9tq@FAE{{Z%f{{Vt@_@~1?Pq}g*KI)xq0c6c&!_y6NlJrO(CV!dbQ}8!C98oSVA0 zm-%!*4m?-jKOJ}$JvMI{XcyWvTErh|v3tmjYdlAIEnm3?oj z>30&^URp}CL2(0^<%&c(Rz_2mV0i>|uj<>!zYV+-@jt`zcxT62MYgf0xn@}I5HX5E zr38^T-ik#GcDfMDo(QkT&xk+pO^+XGUl26SBjNU^4gUaz^;um8p?$mU8h@7?$lq#M zqu9#9Hpc6;G>j01$R`|!#GD2;5~rBjoWA(A{(fD51E)6Nd}bC(rVHO*S~Pm!?0fri zYyKeDbl-&E71HZeyS*YEH&=owE^efei!*((RFknr0FhVa{w_Hmu1ZN9@n1B20{xi& z9ee`O;EO`FivIv!F_pa6Cxu1a<+)&|lN@>bs0qO8O?@l7Z&Uf#<2*@bZbg8UG$_Ptd1CFmw{h}-?GQ;=i@&HU)X#; z_@5pB0E+Z`tB)$$_C5Dny|YV&NYz=haS(os{;@+HrGQkgZT+#oZcTr|_qwmbFNU^S zhKa01!Yi$A@*{03#70T8ibW1X@~dz_1ZAS!Hb{Kh{0*7Y%vN2?cJ_TO-@nS&I_dF- z0$3-ElWN^9t4{@hQa~oa6qyv_OF>o+JAog^9BJD2p>5(R9LV~lS28W*xxgi+1Ubk69Fvj3&3@s2#~-$4 zrSZCT@DGi&Yl!?qX>QVgYFJymntbu6LpuWBW|e?Ex9;7P;gy+VK9>`Cd5p?vtVATI z1@Uxi<=gQ&GEOqX<~3a!k>qb~xBmcwbl`CB%jQ00_Bk-z2;olb#G~(jkHApoJ zhYOFk0Hl$X`|F0}lboFVh5J5#!8!a#KZs7V@#$l;(rjic2A69vl1ulA$lWZ8K1+50 zGKC@7ADAy9ziMfODLFl>#ldloYlgwR4LYxt?S0py=z3N7Q!TBESgF(Zmrjq>9RC1@ zd?VqH1?ceT9ud;5G<`bXK3%n(D-1xMrwRs1?oTJZL*ow__+P*t9=h>QjCCu0CrYvk zXS%s(b%F@Mau*a$11tcyORoe_wU@&s7Sw83)3_o{; zETe`n7nPwOKZ;NDZ2X#aGc5BL-cRmG`G4UDz)y+)030<@ruY|0d0xX*tu>;036rvm+blbD*QS41E!fYhz6;zOjymV-a{YSg18xqG6(MB2RH+g z42}&anF%bm){(4eU9q{yQOh<4N#xevwXW#i9Mfmkd_j9} zr&wC9`R=ZzSYe4rcXuIFeNBEN#awfY&R?*kxha1t-d?}uvHGS#!gzeTKH971so%G! z`JXC&(Lb@b!+(!9jj6{Jejo8neq+b0S;BzaGbzGdpaafL_+r@-4tB7~ufh+D-vquR zd=AxN(f%Q6O=j0mB3j)>M7DQEAf>#SMhjpt;gwHbI5qu7Xxi41;w>`UTF|c`zp%WG zWrF6~MV2XJag}9sC0G!02sO`m-{IGR{v>IC*q%Gk?6unL5y z_z$LA>Ux}Zz9I34nA1M9CAeR+7gC;GqOoHV1Y)JLx$;Ti?H{3WKN#@tFD|u6dr3)X zwrTmj<8Q~%_^gYDGn_U%_fq6bchRp-r_EosH|?$CF9qM+{4ekZmw%w$-Pp#{>eu$~ zZF3i%izaM{W=8@x+RxP_1a(^cZ`Ji}TVK>)Ro3tBbt~C$9^&q2o>s>LF66Pt*1xFV zjK2c@9{h9g-2NQ##+@9@(8&$1qa11W`x7BjUfNey*x6Mi5LK~;1aV)Ici-?#--$8& zMsE-4n$>~utCpTEUS@M~9k_4=Zvc!dTwwjxV1y0Zmpez9z+6v|;c(8CX!|OOYTmrx zJ^n|j#d#KIUlHvf+~D?0r_1#}KKvQre-->f@hz`}d>y9GscN^J#{?)_Y61gD=NL%W zBXgbxKqej#(cAS=;WmP9F$WetI0OGyHaz7gR&Mfg8 z6OXz`1AWrd^7!@^m#6P zS$wnW656%SpQAu?b$Q8E!NFMLEC?>#01g8YgYysVm-}ODUk9%4ei!&FNx6$%zOfR2 zZQUzJZ*G(2b7}?*vZ%@5xGZgs80Q~{-YD@Oh`eE}&*BdiYIl0RrE!lo>hflqSK2T# z%t_!3R-7Z@-Au|c#p7kmN$Qf`U*x|Z%&sKkIAEsmxK(mBrFRSY>;t`U{``RzzZ<|4oN5Zn!DlO4{M$&)vdG- z4cJ_2dX3}kg4WVDmRT6|3aSbGdJgy%`%U{W{{X>1G;ao!@ps1rwb%7mi4DGpRcF)e zi>_PF+zQ-Gr{&1~+TiWqkk$IeX`SVKJA+V)O5Y3?@A~T5`Q8UF$LDybH>-Qe?2n7T zX8!=#YsWts=3RTnt9Ri~3V|YtWQ@hAT*|DWL{Q9}L`LFdVc2a19FN@}hu;gl3-G%@ zgTsCW(e1RWNKsMD5rvXsx%uUAH%P>iNn&{>yZB*>-Xw+;SmTXCs;I)Mfq($)0IpwJ z@#lj4H>&Bn7Qe1(`gWtNOXO+xmp3uRHPoNoi5-JEpkDnSJ2mxi&}93DOuvkF}BT{}MZ>*#goSX^a%E2V*-EWfV359RjvQ`EJ4 z`;Ajqx|;U#>HNv(xRIrnM?7Vn$!0hJa(WC`Ti`#3-ZA)};yZr|co$2TRn%uIxgz0V zLcx5=U!q2&^2e~?6UBY?WB&jI%J`=zkIspx>pEm!7mg&C`)V-lB!J`?lGA?iDzM2d zAdhJb2|0{@w)`RZ8Sv-ej)=N1fb^Jc8KXqHyeb;*?UO$|(w~{mNyj)H2NnBfb>dC~ z2SF^l)00{yqF%q`epQxmMmrT<%NV(0o~`cv51Rh~W&Z%!f5CqTwCFYe03Tb~_?N}* z(a-105SH6ckra)Vp~pP2CPo<~0x^M)y*#x8Xy8+jQ;!^1;8~_+h|DX>zA9If`jtDjQ)B9t7!}H!<)p}c}sqx%vmgDNs=fg|) zYk2JaeAkiUzq2px$>V>7GTQi4$CA(DF9>UHlO0HX7(hxu!aT~T3w`WH%91jw&GGScm2iw4C8tGW8#m&KZhR}^aJ7l z037JHw}Loc8%wV(e$`{VV|}!Q6B{oCby7gw2;!qZ%=mVuZyM2{Uy>F{;Yp7J~8pPj68ed7V%e%b&JhkR$u_Tx|NjbEYL13HP@N#Z!T^Hwd8iz;a)>6lM?RuED%D@9H}kkjN|oJ z_ILf2J{|l7(Cr1at+uh^bXGSy^w!9?EY2{?5j%`g0l-ynQgTVJtm3X9;rZo|ry6Q^ zUhGxVj_UX6s{a6heC|P$=Mcxbu5LQ}&AX@NYwCSP9D+p$A9!SaE0*zx!|w!qLDN66 z{BNPyYnEen*sq{Tf^Np`rGY9=21=2fS6Xnm#c_H+#IJ|`A@LpWgfvePX_wmOoEY8d z*7tEqbpTQoi^Qy1c9H?e;G7!#I-j&s=c1mP-E{f%KUk^k`BgfW*G+n#jNczW;GLc| z@W+WX`Thx`>)Ix*bZyPG%z-D<8b)STl0rUvbxbLKTo5uFc1PWRvrp_v;2(n;Y&zeI zZLNGu;_1>kCb_wZWwx@BdD&==4lr?<2;4}>$3gckQq*-D9VWu#RMh8=*3!}^o_VEU zk;fchvokT^1p_44(_;9`HpO8U3Z+MGw%%T^`oB}>@VqCM;js@D2y5rE^gJWur|jAA z)8p2PYw)&i5MXs@F8ZG_0hEmG1Fguli+Eua!ayhTbtsDLcxABha;zXVv)~_t> zd?^&7R@UL0Gf8IhI-rUjoLuZcT^r<0!{%;9e&hIK#hw}Qu8{|bd?Tva>pEr5OgDBj zCBzbKI6GYl1cER}V_g9q>N0*U&2p&9jS1O3EcLVQliB|ORyE9ccQ4EwYSoR}>bm-# zFYssJKf}*~`T}U)4AY{D;p0TS)Lu0gccwhyrgZ~2$-o!`(!HE0pwg~u=W!T{xQed5 zc{xe$-siPL4^D+OPLi@iv5F|9Cl!gXw9?8t)E75)_Ez#;UC6OT5|?*!6;)JZl1~5u z-o6z0)B9J&YWIE__+aZam@!GL{pgUNz4md@$o}~|bDRU#y&ShI#AB+eIC$NjZfBce za1>p9Osu~X-u^HAd+@Kq+jg-MCZn&&fuPf(Z<#}Md5HUo`kuqM{K)u=@iWCA5+rmosF%IGx^FV#=2tHxx$0Q9N&M>; z{wd^&NRaGP&reGIr-%4E50|AW7r$_TAvYs25M3H1JF@iDD zu16XVl;8@&(e5RZ*A7{BF-@nZ2e0GSwb@aelPLonr)v8}1r?$35`50=#MG^=Zf0b| zz*PM6#sMs&wtH5tlMS2OW+UbWBq@Ga52>zXP3B)W*~`SChep~6?~r+}y^2HtjsYMY zNaGY(hOR+p4Xjgn5E9S0kYADs^d`H#BK}*gL+pwH@`elKmgDrUCu>|4F`$Tp_kqc; zPqBi|7zMYR95T&=@{BtibYq@#?OYh$Qt~|f98)`szm%0q<TE=8 z#4cS69DW@IUK-)IvALCyP2>UriAmhUpuo*^(tQqTJ0b;Cf<|la-YmFOTxaI)bJo7G z@GF(@wx1FyAyo9|iunV~yq+6rk~cqWU!6h7PLyJ)VXF-(Eb_9HcPbf_dQIvy)n$)|KgprowNC-D!2Ueq<_oC`?YT-%| z!Q<;+N2xi(epSZn{uH*pSGlnsT;n9D`>p;J>fy2W(xW&k;rkvs*#?{St9O={7IJx4 zF_dpYDw!D->cVPD=;3P4%!%7AfO=Bzk)%*C5CflF)in-61VXS*`aKMg{SLJBp`p?j#vv z7p+evviYkrg$xD{9Fb5=A>PahE6hqHn%CI3r zb}Rd*^sj@_{{V!lkG!AcO)A2Q ztbX%%^);5n#z{EOZgat@uC6}NK14eQAor>zmQjQ!Bd%-UIv=vZX{jtCX=CzsuOQ%O zo+(N`?Tx`B`Bg@Fp_E9h6aj_={{UKOmp2LXU=U7o+Ltk>DXhh1D{Ub-#&8ZY1v6)m zBX!A)w<-^%Q+2tLOu*>8V?FCdW{%nfOO^yLQax(ouG2G=ENBa{IV%dNk#WNu3dV02 zY0>J@XfjHTs5+Mjhbk0x$sOy$z99TkyRp8TMeyqza-$N=cyp1^dVOn#_+>5C*NtuE z4$V8qCLHoX$6EfW%ToHCEB^p2e!6US(++ zwm>9p6tel!clI^vzYebEp89)#D{7=;2R}Dn!o2dpSkJotIT}cibCto*^{$WM-;Cq% z-nDgmCZFcVa6l@<=1{ribJIEIxN}uGVWsSq+B&3jW5HreSC{Qq z>V&Kje8YEO&Dy^!sSI{1zpzn!-H+H|TQ0)kmL<({chma0>NCp?x-^n7MM08DzyiK^ z_<{RD=$;=*bpHSkFWc|AUEqNnXW!I{`HRON75rP{Mrf|Cn%>VmTflZM0m$4(E%?`! zcW{}WMUFVbe8q{zF=RrK^HHNaSDvRvxcx7wVq zad2min6b|}u6nt3TD4t8v5ic9D8<4|`m-&Z8kKq)l1--tMg>LExLxg`#E{dwJOPSA zsxVmKlYle*D0vw|h3dx@9I@_nIQf?*oJT7qVbT^D+A=FRTSpwJ%~_ip94xUB77Lu5vBz3iOE2!`V$Iz4s|=n{1QOrQ ztVUKv=*#TG6cR-7C*1%5d8o?uUezS%_$4{O;Aa&TxOZRP4Xutwb3hbsoW~O}A9RdT zpjJRndWuVVo_2XdDxNXBEA_`}S*KfUAq9(l-M`khi<3tr^m>j`QLw!#CWcvZB9O+% z_^5%I#^wZPps8S#I}zzktL{bJ^egNdRm2yz_bqLA0|$y|+iRnag-`PbgV2mu)SnLi zJwZ9Q(R^~t6!O294xob&Nt|QOAM(kG<3Duq(>Sk|-sLVXji;1ZqsCd+0|z|`>(Z;* zFWTBzfKmY}LBSQ{=GhJt9ZmaHY4mGz>iBnw@p)B6>|NmZi`e~3j##FRWr@|%fFYFR z5Pb;iPsp!?ejRxC#9t3v?Dx_e%@x>Y+21V)>6nT8ynhcvj@9;8hWu5b__I%4OGCL? zqAjt3`CT9Ttf!EFL)$g^7HN~#z|`fQtGd|!g2?mw8J!hUmXhlpsOa5G3O-#7^l%f5n5+hVR?45gi^&&ZexL*068S~1Fe1_{@0(j-^7he#agGs9|A?Fcsob5 z2HNUth-9+W?qx?IofwsSc$feb038#64op-@;Z-N?h1GHQu0s#?@HZ0S4sBVoQ}g4E^Ey^YH88r-A+z_;TCB z9tF~%(`>Dy5sivG*)U1nESTLRWS&@)U#;;ci8x#>K3uX&bst-475cv~y!_iE;B00h zT=?b51paQ{q4V$T+4~^;Jopo)TWYi0L#%kk?bR;zX@{Dq#fvi9EQ9w++jcjw9G=zb z9~%DvXHSKH9W=ML@xG4~nu^3@w$-2lEBkT-gxfF$nGu|@jrT{m?%SSg*>!ziOVBjS zooiaUips{$C!XTsW(gcoMx~wEj{quyNvz)sc*nxtInZr6f`$hvI!96 z1A~rL#&F2MGM*R%kzb0{&9e&FmzdP4CsO|a7iaoOqb9Er=B=Vn`*phGw+M|U4abBZ=oC{BQlWzhdtT=$4mX z6@DCQI$wru;fh%`4J%N%7S{7j(t@pKTlaAQ9D^9zfCUN%O8cAkko~Ou3HvkjJz6Myo#)gl>w)IOhJQXeo|N|>A|nS&y0T@Jah5S#Ye?HH@e>>ahNS_Vai)u zLFPNEB4-4R`%9mlg0A9lEA<}^@*L(^g_vQb?CE!U^LbmZuS4_xGR^YkN?2-eCG zyzlxSuHUjp>`mY=25DMf#D5!0r}(p9nPI)Mdue?83ldN=>?(Fn<8V8e6;c6QoHc%w zVR3*livDE20DjZo5x-}>aL=I5bu9|%9MQ@p*A{{Zmk;4g-x@ehr!u_^g&bnARcd37lt zD#spp2fMZvM)-tTDoU-4}D9P^5? zIh>vzish%ah>>y&~v@ytg0%Nlf9>o*fBNK^frJ^EIt$8mEUf zd(8u0wU)zC)8~@f=Gt+#NT!L3y0G>m85sw$rO|vtuXtBgxA6ytH4B|DQM8aeS2nV= zGOjv}$U(sz5zvkg6~7bkHWN69*C{m#^lNE(yKm}ak?}?|E1%WsUQz0v>(jCQb20ev@BT1q``;w;$TxCv1LV!(v ztR#sdtDy|60R$X^4{&Sw?>EV@nRRbri;cTJkI|p9WqGa}A4}Td-TSVq)RP#dmCI(7 zkOogm_}liI{jod^@FPo;Nz)~RT=8|nvfJs=NUoOb0Qs*96478VV22FDrySQd2QROV zuN*cSJkos*jBYlD5{s{hm7# zh^~st^2<`Sj`qT6?rr5=(aRHuM;@xn!5>QgRK6(uUh((FKNU~nza45)UEE69js<30 zTW}OEA%`U+`BVZx1+kNpUfKIQ{@0%oz5w1^>5$JRvEc@tUhhVo^RY-F&8&@+103$a z`51wb*1t@|cvXy!586|xrB|$adMDr1_-sFjSZU(DoZ-t)q`y{=-`|R#2tFVDWbo_w zYsdOrSN7mJf@}r3wUNdQkWGTow@l-PzzhiDzYhK+e#9Ol@W;m;8{uDvyh)?aZ{mNn zTxpWuU8FY=K?97(JZB8k`D__PYJ9*PpgR~3r@l1*0KrQ%r|{Hv9|P=X8pYf}rN7l7 zUoOeG$|N~qD`P(~Ax1hL-;#Qti@Z~>Ytne9S=DcEHHjw|Ci+{4Yk?}9t1Ow^r7$*a zBWME{#d`b;!@2ev6PG`;gp;yr`t)|Xw!eAj@n&0ii2NB#xY-tAF|Kw_pJD8 z>es-Z8!n{OG*Yo!S!)G%+*v9qRTJQEomlM!*zQruBxG0Z7LlvzTJE0?t)kxBYBpC3 zz8jlKQfOsdk(6{StTG75Jw<+P#W`MMg2lI)UCHasx_>{@q4wN8nP4$kb@dx0w_R8D z-1x8dw*9as{hwoh3+TE9-R8f4VfKqHLgEs-R4nXJZ+wF*E(zGZ@Cg~O!M_)H>&5>7 z5cS9LH;uK6&0|z&F1HsmGZ=R^{jjb=!hyL}j4F=8zp9@Tcvr)oG12ZcFBEB(x~`ve zjFQ{k#Ir_2sZ+xPgM-v^I~x2${i1(i{VU; zyAkjV@hofx=Gs93Gm`2n?mRxua*RA(3ee@M{?pd$ucniFD5pTKqWpwQ+yq8+~4DTP-J0NW}M6(y?U4!C&7_j9EtWy_b^5Xe55s zp)4{*e5Lz1{>nZA_ywgc-Kr+D;%k&owd(W6x!$?r-A+s?^AJLup5)in#jrDq_>L^b z<+!YTs^yU7hhEM1OQH4LIfTaH@R6sEpESC8FJ7QPt{cZc68r)1&c+Q-$J+IlgKKjr zf+?@2Sdru)oC1A^C!nvAKWZP_gTg-pWxMcahM--0#!Ct}k{o@G%*-ESPU=e*U8=?) z6qR6dEAYGGN5x+r{9N%Az9{&Cs>`b1Ny2VBVS#V~NMau(N^z1D44zGUd@bSrFEmQN zB3{mu+1pp>yLp~wdB#|56x|9Q{ZGMqv--BOzO>YBA-=GgC%3pok||?g$mqi=%0~nd z$Re#|0nL9npR;f6jpN^dHz!KD^E^Z0dCGa3ZPe~w4WU#utc*yD2r;t-KiSR*`qTD= z{{VuICH9uS9{33q(8+fJ`(CrCL*>hESdp^YEyQoY<=Z&j@_`Qgpzjs#w2gB^Hl+cON9HtQS%Gyb%e?>@Ach(1KV;9?x4|C_wB0|){wkWs#a<`6hC8ckxG_GT zXp*R9Ho%ri9C8Hz0L9gIgNpkz;t#_g1N>3&?A{mg#*=TU*vgF>i4b{^?#4_qV|-1( z`H+LfepAF5?n75Nje2-~XvMx~w>QhJyzO)D@@$_srT_2ZeMU>%a8FZ{t2JsyMG+VAHwZM&VLHuTmsL3 zD=p5O3qA^!!TU#+pD;U=x7|Ga$MkdH2kgJ_=imm76goYWI)=R9HRiW-61CKlZNU+- z1j87~!XX`b$t2g(aaR)X<|{6ZIK$dWuXRV=aDKMZ-L<*k@b*KVVR6n^agF`!>YwAU z;C^;~*Z%;wSBf<~QvU$qZ^EIdcy89l;(JX`Q;{Ch`ryRDO52>Qupo6{Ux#dbqz+H! zr-!^%;vWlmj@!ll9n`LDHLFR@%s0_W#&Iz@W-P?+-I5(!D(XtGDo8)kr|lK{DEvD8 zp>&09N+r@fLuirBZpzNxMFUroEzF11}Q)6TcOirsw0 zLXq(e%K}g*++qO-FSLGke$zj-&%_Up8r9a74A*)OgcP8IL<}QlzyzDJhcdA^I~bH) z0tRwx`V{adfP5qH-@|sE6!2b~2AQVXBBDnM0M1ke-y>smjzHy@mOagUt@}^@#Xbl4 z@ofGf(0rc__`(R7M|}hdRq7{sYuPK|b%a>DOjSbot8# z8{P$x7tH||Y=DOV0iC%A`X>FKzB%as0JH~yA@R16r(Wq~NY?jO_mMKHPP~y6xY;rl zD&dfkh5+HR2f#nGxBL@+4J!Bk5>JnB73`MR0j)GkIgFOBu|7lHNwrLY@IQ6{U8IK2 zc>QIt)9iH1NNhCgc&#mM(PN4j5o3-tQGlw70;mIk0OG5U^PHm_QAY&})K6Kh6}wxn zujWSvWqFnkoBGSN9?^QgTl6jfDb!LEftvi$A9yWsUO4ghhJGIClIy-Exe!2{Bx*NH zEB08>a>w)?O?jWi{{W5Je}HW#(zH)6Q1J;*-P@9Us0)49l>2qqIX>0-m*X!Hc(cY@ z)VkM^lDdxDMZ3kPUbXv{s;Ri_8Oz3VU5c3?%^| zuTzYSQ{lMD+(5wqsrAi%*_CBfGWuS^gl*^5pMhri#w#$Z?W@VZE{o6+nb&j&%kQ+F zYFI8IodA3@at?UrtIIS3UHot1bM>sdyHrNp&9IY^&sz0QF*}ytc(!y<#GmzNAY;~$ zM=F6SWs!)_CmeoNg$p&Lb2*GaX8YXl9C!R{R>DMUb22}gM>!)H{3`FD6))`#)*b+e z;DSn=jy-DH$1mB+VTp|HTphVT;ZOQsJSV3aCd{5*`rhlK!k&6^;`kawoPP4!+V6~5QUR1dt9tih8(!JUN z6qbT}ISi;HJJnk%SPp~UxN+Mf(98bU$huNei7xU4e>OWX#c}q!-K3M;SkE&}cN=A$ zRaG`IJ4YUaf@@<%y|+y&IV6fy^CT!^=0;LZIUR*mmr2nhyO#RO1YmMqp}{4GVg-57 zTArM?3FWyMF%T5?>}xhV75f~LY0|8C4APKtH~LnfSanNRMON~NDsrW9T<`XZ-Wjd! z-dlBryp$3TEq5sO{vq#OYSz%?Y}(PDMAM0yK`qV+01N^2_4?P=J^|d`6w~IE;IbV4 zb@67Rn$DcMT+oNPW_L0;JAlqO$6;S*_zGDi@Lrm3kVhj%OD1whC-ScoJfH3~?s_;C z?`U;58Ip2$7F-N|6fj6)EXV1)6y>&bB4pgk2Oyv2Rpo%(5G723)0+9n$4j2fmqt_6 z8YQy_a`}uh6miGUS0~%0bXvA!0Z8Zh3d6^>chJ!sHFvNMl#B;@QAoooMQeL%V=LIK z>-)gK^cd?-f+q8%X8WWJRAp5dZex?$q%w%myD(K9Njy|0?{Z@FGPSJ^%T|mf!T`(n zTy?J**0dY#B6bdep&W-&Pagi2?~KvN7v}(uah|o0cr9)rc%&j#ZmKyr`d6ig#!3%E znxzEwK71<{3xG0xI@D~e>-YYE^sc{L@T{?1eXmOep=IS5V5zPaQp(#i&Pc#Moom{q zUNcuXCrgxUQJZOGmPhj%PtXc*n7jFEhXiLMu&CpTM_}09f(=-e?*xO&QL%^4bAwxn zS*sO)d-kw{{a!ga=xJG_-I#V9edE;U@u;t0X#+TRQ^3NUQ{s{B;y}@69CG0D57WH^ zCtSkcK0bb*g;SC>`%4BmB=c4-OU`51rAKD=sn=kCz#IGq*c@9jpRop z3~l4|uRh1QuIF5~wp5soHm*n3xNYg{Tva|JWPktB{Ry^+pxv}CdSe{bhIZZ*DaWlg zIN2fEI}8ev*fPczPFuZx&ik|Qgl6i1i*tdR;Q>3&?BgcA4^v#)xL>`FKDFmJQall| zD-j{?EAej-Zn5!S-CwEwKj6eIlhpqJ8~l-AN)fj9runfmm6dV`#~d2X^T|a8cNXps zJXBFG*55M~FUqO(uY$WjVNukzC990IELn5FZgEkn%Oo4-E09X<3yz-RoYP4Xoz6C% zz+l#7mn`F8JZChm)Th}N?XJbCWefBalif*ndSH507{Jas`kIlxQI)MJy=-GsW8~i* z5BRF@f8G_<{286#@#VaVrT+kNGT?U%Fl&_Y!Ox1XbLuOt_%UqPemu9gR@hqJW4qI6 z2EVED`7f$cU)_)7e+Fl8<;T}g`Q`bu?C-NkB=aOjjDj|Y?VkN{Rp!v_q7qJIb!mQP zZZccg5sK5eg5DUjJhDj2xZ|FK>sS`{@#MON3vE*4AdVI0NY~eTy zfyl*6bWkuckaO1+R!tTeBfz>UpmMSgrc)TDWCPq?iv z?$r@FqZD(FE75E2Gv`ZCJaJA39cwh-&prC++8vylzM~+>wY3H09;AWL4|@7@;cx8Y zuU_3IweccxW{fi)l@SH{f~?LM_Q)d@$C~CCe7=jTikgpQ)%>?QvfQ^7kF2R-VC8+^ zMe5I!^`_0p3&<50I)k)INwI2pjrdhd$78EJLn?PAUYkXof6vK^&}8za`Y? z9@PMyd$OFAncwr8+T(Jze z$3Q z9G_ab3jY9S!t&fN?m+a;N$HBQ_S;q2v9|-y1EqP@Yc`#aZk7kx`_c)mZtzuPGK1Nb zaZ>neR`DK>sxGOg$Sfqczzm;uP!HUZGlo(){zM*W?Wn}5D-~?Qp!ECOvw%HM70p9& z1g|B?l2&;>@;h+9LE5~`RT_2ax^y!<)%;d$Zc;?lfX zG`g0W&xF5Yx_f0k1b}lQXZXJG?~&8sb8Z}+8`i_?9>?qX4;RA`YB;?m-=X$>6`!YS zmKwFJciL>pac?NfOBvj(J&3FM74pwVW_`MPTv{lifrw59Yrwube$ihJ{s3r6s`%34 zSGl_mwmN;Hi7sxP04x*|6#&S~NXov1o-4%v0BmpC(^LJPY^`(;4(Jy*TIRWI?_;Um zNB;m4t9DbhgB*sIR3A5%xZ@dM4SogqtHk~!@xHrt;(r%vGwQmv>u#Rv;fp&Ak`xY# zi=UNL1zy6xN#M=^shZmtikB@PE#LVc4aB@(3>%gg3$0GRU*>yv?Fsu%d`11BHJKY% zx|2`v;z%0LLWV+Zp#T*RCr>s~80FhI+(Ll31DW_M@$=$OzKi5Dkp44nBskECjP?x$^`=$4Y->Nk?CHrBT2WR_W6;Z;;*l1+48 zI{1C#Z-$!Z{3D(_(XO<6@}6SFa~WSTGmY`L-F46LDI&je{>jTJ&{<^T9$RS+{&wHy zeo*mQhqe0bXDhF@`hUPbqF>qj_O$fgd#xV%L{lB02_&~|%(KE6 zkO(`#!F61W{YXi#Vy|__b;GOGj_K{XW#SO+Q7`?lo;%<|{ip zNZMKFox(*FYBJ2sZ~z06PfGlj{ilE6q0mOM_&4@`xml!Tc<($y@#SMAjiSUc!yInR zuDBT+27Y1jkL@x0Tl`1(^{LCGNM-Q%h3zMqZS;s4E-n~NsOnPz_M7R(aPCI|P#v+L;TP45Gkq5n@#YgJg2UkwonFgj>u$bV zo*qTQxV+V>mHpU%H&%ZzSGKoWb>!N9qXd?>cG5IcK{^=aibObMbYK}-PXrN=YAgfD zujsS(mi>*q5Boq_uBm8Y@b8M?W|4JEdt@f>DYQp-w+$ljxP0zVIxynD3O+G>6!G`K zzZ1>j&l=cC6k_4wv(yn7S>G&R67V|#xNTraATZA(t#)v~9APtbsr)s1#r}`4=3&Gf zEs4upe-%IG{{Soc6{onA4#X;jjdDUL90SfzHT=dB9A!=qt$qIh z`z`+6o+S7?9lwHpBQ4K}d?v1m1i{){?Kut?3B8Y*0xmHSIZ!Z}&f4=lLdf%6!h-sY zuLZmojrrfNr|Na^hHHSq*S0D;y`|gw?tZ)cbN!mW8vK3Gj+x@CF>9#ARjoCPL`A-~ zAd*x$2Xt;3gCk{0JFpp5m+(vD=j_YzBldmPB)9Q3t6pexPGZyb%XrV(E|&$AHVYVL z+74oKoU3dui@g5;NZtzZw}k#7Xcqn`@NTJXt7(@x^6o8U5-vwoIQy(SgUIx)t!GBk zb^R{uMb~X&y|BKC;JCJ(cf~An0<5g3f(Sh;@=hP)9DYqdtI2ayPguU5ouA--iJb7x z6EvgkD85V49apr`(t2n;Q* zJD6CTsNA~kvm6N+6y%Z>M||KMaJU?!hOmxJsK>0Eyr1g*68RBd7nx^xl^J`_ve7T= z%=x3jo(u6`jvzi(lN);rces=!YFUX?1oR(?;AG$&38HA2$0#VuNGijS zN3DNS-v@pOd_VXl;g#^$f;7mWEQq(4?uu`3oim5HlmO0h%s~6Ale7x(AKHiZF7S89 zzdOY`G^?Qa(mQsJJHS8DFG_*st>OS%E3_z;K4nva90d#V7(W)Uv&Xs>Y4Xr+%3Xb0 z{{V-v^%$QIaWKOwv}C1Ita^Nz{P1l9kwyh~zY+cz_~YR>h#SK`JkqDq?#YnbnUsXN ziZ?0d!r#P&5>75cR2iN8^oJ>sr+2Ictl#42%Xz3V`s4x%p8*RU7~Su2_z= z+VS!Z63eor-V#3Z-MwF@lJrNdn(-DlGKyGgdDfmZoY2hD;^IYq` z4rUR??8aNoG#miY6Z1(5_lTYcDTDI2W&2P5-<~1y(_VNd;MCISz8qqUF0zJ5bsOaX zmsHNvZwoO40C|q9$yIhr{L8>2nwaBm4mhrQoDG#!$NvCm%9`h0KFjmBa_Mm-L(|5L}5ruq<69rNcQj||ZP(*U+|2bZ4bxO}G#^{iFx z(JfnNUR9P>$KnHstKvST;7i|$b{-zSpTjzI$qmk=u^%NGPK0EJbW?!t ztF;MG#1&Vhnd9E*2SN8&{RsPt3$*n*8f2 z@m7u_5h_^tdpJLZuAWl=05$&r1p7?S!|Jr@drUOBq_Oz-XDXX9)28ra?nn>;9W^3XtDOI3CgAQ zp*;lff=cI{Ff5Iam5vTSm@RWa*E}b!U3hcEwo~bvrIQ7@v${hZ(K9G;8BZfB!H2O1 zw>%N!zZHBo@jZu(d>eZ$mZzu49yUfHBus+QN~7+vrEVKaDrYYNmV z%T(9HFJE5k@jRS^i!oVb{;!BmRMWFhc=~^l{bW}y;{N~{_)oxECC7<8XR6w0+Fh>S zHtOEcg_Lhr)97eO1wuL)b-KuDp-kQ{AT!k%O~=YXw)kJ=-L$+&j_;Zi9> zXMJ3~S5xxmh|IH$Z#)}v>20I_UZ?1n>~;HE-28ab*TViM(r=T*`dlOJnw7n=Z8GTq z`DJM%8*S!t4(P}TSY;2*v3~If0=Qp?{{RoXA@Iw?mfjNZK9>fQrdt3+P_7J;Z3@yz z-+DO*!{~bp@^f0jXYHOq`HU)Iim#A zfgLN%{v-TZ@Q1@4HLo@MwKq(p+t|jy+)mwssXPx>I0C%*!cumMyE`X1!Y)#FM{}#{ z`gVn6M20eJMo<1e~Tk%)LtHUm{y3$)Sfh~|2x49;=HKS>9WPV_d zP7vccC$|}_3r$XUf;i=sq2v;C^JBW2=^3peNbRj~BW!%Cx%rQCU!viyLfneUADCj4 z>-*Im+2>lGn=Py>D8eZrUw9=>YNW;!h@I2{@~Grz>5BC^jlIU7D1EmGxebBbcKX+z z$!1nVG#0l25DK9Ls5$G5SGPkO34BL~iNQ@P#TnmZj#*+~o6Jw29hfhzP-~q+Nq>JF zaplUQxXEu!R@Lm+5vVaRS5i0prH?g?Ce+_kM7WJ7w+cyMHvWg4iu9G5Jk+hDA2Xzu zj+ZeV(jA~DVmtS!bjVCkOg4)(>$;x0#CP6uy#^WnMbJ&Lo*bE3eO+FiQ5Y;%WpILY-Ur@quyD4>r~wFVc# zmOwaPrrv74rM_KVZ5fZ2v2IiY?=>-Q-rhyDpHJQ#a#-Z!-nyZ&$oDj))c)CJB$|7} z_GrOeo!K}YF^c+^;i{y54%0R%8>7o|TPHrIzE0Gx5#koQe#=KWGA1$z`d8K84jx#1 zDW|-JWJZhSz`(B$IBVFuo`wabBhX#Pm*xfCgN$OcB)Rfs`#C2rODkg_dm6BlGr$*zD@e1k1<%U4R{?gBf?&C41=()aC8;aYLF$oM{8~* zWE0l8dhMk2IB`1w7^O6v_US?CS{O<#D;Y9Ly@}il0zgsJRN#4b>}EqJA9Y4^Q3;>S zlHNFWkU!QD$3BO>7N^N?Do5N;ZuM#xS0hw5NsuVUcOHrdHDca1WduT39mpL1R8P6k zsxVRRGgbb_D<~pWUt}2k>XyN&BF1Kwhv{B>;%^A9sLgq&ffdYo1~33wKE9uYcMWNH zv5_2~Ic^z2;Cu7USB}##i-pSP?v4rkYdRUl)zdPDQ5*fnISN4N zC?-UnL^jDinnJ-y!Ek+h^U|u=tQO4D$k+hjG31Y>OK_7|YUvr-?XGbi)e6{S)SBk9{=cPg)qe5NkN?vB z6^X==i4f&mk&s98=ANvi@Im@iBh7UolrPZcrh*Wwl7SmM;O8R0Ws>TC6&PKVwL;5h zBdOXz>*<>FbEiy&r+IwfNhdYvx~rA2n;@KE;8&Wgv%r%_ytHlebNn^<$B1_R(O=`2rYoj-qzk_l8C*6hd6{-gLh)cRj`bsqCmm{}LmleF6O4-9oVGBiv}eiQ zH~#>UruY5fU6;XEjeK zpL(tf4s+I*Gu!;O#jrRS9@NyDR*ZeCiFwU$&TwPZsMw1C0RB9r{{VKpYEhPAJxzNp z{{Z}X$?hw~?p(%LjP*6^Woqz}UC$M_eReGP4@7t~Di&;_y3pD;#c_B|#jJPfQHg z&F6XMCZ1)NZ0qXd)=S0O>iwRu$jdf039sDz5A29|f) z03A(IdoeVtGdqH~!iFJu^&KkWV9N{$Gkr$PAS-oVG9p>=w{HE5&$q9=X64bH4)QeRxMA{+m73Gr zM8S^T(wqXLIX?L5_|y^FEC5Jdm<~=`bxU%vM;iH1z9r7$owM#b8q1Ylk)=1uNL`%T~JT2k9t$f0Ke zy$^Bt*2+Fc6Ri3jCxiT9uXrlrDYPqswyh{@n@2f21L(wBLUOgejr!E%wdjb?Z)rkA!Pw_pbt~n)2$@9vAJ8@X&&BtX#>S9 zWUH<@1QE@A_HDzIaVgZItuyR+e~c^Oq^Q?!RNvv~ewYKATKSLR-^ZJ4*%L$YlX+J= zl_S3^vMEawfgl42M;!2{+rQGV&XXv3*fO4kSLZmq4Qw>sI?Bx-vtaSmaa7e>61rzI z;~xZgN5}d*=pHoDthK#8+i|zPg=Rq5BXaHsYhaPMVBnF^n*50PoBsd=*6}m^MThK&`_wm=d(f#MlU$f`zwea)cCV^!hoo8>Uc$p(2^In=2`&806A^fs(Vvh_| zDxbT-CcQJ_f5GpE{{R>KKK>o??voCoZc)}Lh}&~+WX9%#Ve`bt10_#58O|%zX}tPZ z1#Hh3R~7Zzl^dzv?*9NT-y^D?U514Zt40fxw?py6O8)?YUg~jtL{`~21Wu^SiT(=~_;3X+xVQc>YR`fn0{iOc@Vy^@I za-&0=u%L4XDPlUfz;^?^V<1(8{#wTiMlXT`c;aj6byR>>1+kg_jo|E%;v) z-XZX;v$K7YHEDGXHa6u|$0m4`85?MeFrX+AoZ=f@ugVz<(KK{L&Bd!`@V+3FHF2QiYtEu}08BreA-^KChL{fC5n zN0e=Z%_Zcg+x%bW_xC?A;ywuEZxxp>SpNW*`5%)X34S>INBAkN+v^?%)vhhz7g5~Z z>83^&SHeCDPZ&5Mm*p%lz+(r2kJ5h^f5A_*`@aRr7leE#b!p;Dl$te?>U5IY&9)CJ zP(p(-+)vAe#?W$ef%!S&j|=#N!J5_AhkR9GK9{LkOz=%@aJyVQWUFBGUYo0#-}>l$b{jp+viQo4DY&PlyLxsypBH%7#r{6j{{XhU zYpvgFm*q-}i}~7T3QlmjI6>^74{FBJG)-T`8jKn~gJW^2U)++g+SRP)yZB!5Ofvv=&h@U!5a zkp{WpxM0=1L3wDA+-uAUl4#flA|+>#LXr>*Di9lL=QtSU;*KiA@b(rEr%zY#q^z|3 zE%=*cIW8wTuKA^+_@woIhshtaC;Su2&q$Wf#{U2w65mpuNETQ;I5N>$%vo7Y%!=D4 zWk6M<89>DH*ap8=thDc%Rd4_Uu&K6e85Q#X0PR`v z^IZ4~;3U@kJ>jeUZue3%TETY$gKMYFv*eQG?U403RN09zM&+;PtY&4HXSmp@zG=zm zwoB8ZKW4&UaCtrz%9`bh-SvLGPigVj#V?273+y7)z94JXdL6yPh~aCe!%D@`Fo*$m z+Ff@Q1SmV3025tak*#R{BGWDPjSlwTP_VmCGT!FeMV1**@wr_IRvF`i)MCGs{{V>} z5`1a#Ys8ayyT@9LnvSD53zqpKi3dcAG4jTv(Lnd+y|eaf{ir@Bdu}M5&&u_e7v@+v-P2VaEcUQ4n@)`e^*2koiA}lv>W!mm-HeR?06-oS*JklQhV<_c zTxr)@OqzwXcMWBF5L`j#4Z&mx%%OnooQ{Mk$Q=vBlEzX+46FztxdeI|_}qhvvDr0u z>|Nv5FK_q{%^ypf@a_jRqNdfQ(o27zbNK^C$j(9ZBau(9*uUF*_D1+`rRX}h#$OET zcAgm25?L=b3p=?;x74Ja*KmMkjC1ZR%8|z2Yq10ZcMr!*spWba{*d9!`i5CQ0HnZc1Pi#40w;mdd;_n{3WK`>e{8*A{$#oGDz6! za5z;R*pQ>S75g#!F@M27G*1aM9y9pe4W_xPJWW4PBXuxiJ!dc7{n7BqfR3gpYGyOF2l8$j1t!B>M{f zed6yC@mRS_3zoi0TVH8E@?VeSe#ygp8A}Zq;&FDA`*dID`571f7x3n*qPK?iEiTUM zNhqvk+vP2klM*=yW78#ptib}@p zM>zur9>NG=kzW~<=XsV*T~?td7WHZEB>Shm`=3dV%P={e7fz+6Z7#Q8KhXX`@2s!% zoBNF~R=1YU^3}{!+s=&~u}Hm9(ST)P+!31ePlewXejxlD*MH$6_oDq{>zQ45R?2+LQ+5=9TRfMjW;~Q&1ZKrB%WH73%zHgT_D#2Y? zmR;mCF74QDp-;;H0JEq36P6zh>Tlvdj}2w3&21?A4wqvruWSkwVKlPlKQJ-erV}19 zhTM7&*rDPMO^nN>EPQ?bu(j{$<@q0&z6{JT*x0HsOX)7&zGvEhvj@lNzi4j)w~H=3 zJ9VH*1c?h7etTQCDtAk3F9p+bkU<$v0VLPbn5wq6&{@Q=TEvjVg9?$TRZ-{w;8A~d zC6$H6f2}eh-bQPOWK9ggIi~iubpb+4}Yt z6)DkCtv+cj-*e_4+C%ni_*?Ol!n(cZh^@5!7h6kXEM6utB28)JfJ4nDQ+z>J4jHkq zV;})voIkS%?2quT#{U2kwIA(|h#EhOth_IH-|;8YmPpO|CN@nWmm96gBr?UgWDLF- z1LEJcSM7E2JL3+$sCY(OdHgfsIA)Dw(PfWi%#8t7^Df zt@u}0v+);(?`G5W3ki~VCxN!QJZB^7GC2o>(2Q5^yf?ym#B!-nmMPPem%AP9r+wA- z(doZ)@|^EI%PM9nOAe%>^>=Fj04~2X`o_{cHK6DkJYF8rH0w(pG8Aai&fXM?0F9l) z%TO1bobk?iuYmsmX&=}R!e0|EA@L`|!DHbqWm;CewMaJDUwMSS?=CW;DT5R9uM7?t zaJcI~VvpKG;xETf4&GY$>%+GTE{upD#Au5Jo%FI6A|V#l3o<6s!ZRQ_+`=HpAMVWV z=D#C&jK?C(r4>6>i`LHR`t*16{Lj&Sh6^>otH$)?wCLBTWAlIZW&MFXG4SL5DvyiN zSa_peaU0uOG-gXH9n15fNeM~cIK#3GVBm9KqsbaZijjb-qY9&d0O$v`I1JLByw{zY zXE@BtyRVF!ll&K==yc_|ZW{qh*Q;xePdg? zj?&r%EYBgw&;iwn=m6x`!5<56;-nP674l zUjl2B-*}HypIg_blJ@%GhLTyBxi}<%Gr$8U0B|YqY($MHC0u|r)YD;0lqbyj$M>^? z`B&>XFAQPuDO7&+&&Tu5Da7OIa?^aY*H7~~`><`}X#*7t)QZlE;botClbxfIdskU? zXl^1gg$x^mlZ;n0eWFVO?@~qvPTp~j)$U54RCp?{HsZWUV=3x$`P6NCU>iiw76uCr z0LQ5!gniL(-ndcCM|j9>%xpJq#}$>Cvg~f?nv^n@g=ENVoMVDVf30emjl`?uOz24< z4nR2eHRo%oT4_$PPQoW6A)A3#Zgsep>N}}qx_68fi^)>R3wnTXMId_}B$`{!WL9;C z;4#U^1a_`M=ECyU>RXAIV-QtX6UhEm*IG$;1P!d}O}x#83I+fJryi!Zq_$m3*evU? z9&p?ceQVIeV<#4ro`;o)!YL-wJfUUOV~xwZ6~=cS-oR8*{kBlxNbe+Im2QWc-qh@6 zvW?}FGDeTJwsIHKHASuFxa#kY#+`Lo>Q%e&=1V9q_p zaaZjA&v>CO6tgj4Q#t#-ovJ^x!tb<^mgIZXvcn~&>0!$48CFra`;2$3E@asm8&RQz z$pe5&p=_KA^<5&(?3UM2xGZEY2dOQJSGUMSdeBERMyw9bKqImD z`d2j^%cxyXtIc^Oy~VKskdx4ijywJpvlzS5?8Dxa**_>lZQR|4F~K!<@(U|FL34Ww zd5odKOdMy_*6vLslc`GjJKa(UE@1u9g*VRXK@tPdH*sHOd^kaK;9V|qy9-2EuHeCN zdlAKag&mcoX5Rk*O_MQPsau5&m10M6Ut;_|5nA{!O|*(Vh_Mi%PXJ`si=0LK3%=*4 zmn+oN>86f03v$F^a5`kwi(pSkKaml7^wvH87a>*wEj>q1tF=$CL$h$v;XPWRX zne5AB4_4eG0G>TDT-GbC)!>p?mTiSY)LaN#4Amj?TWP4;DW@IdJpG?)8iDR{ge4B_JLiOjRQ8Fxrcb$>2 zC4)9e^b`QMA1U3tP6yogeiE8_vL9O^V>~}L=*jzl(ag`&5`g>OgaXfGmts6c%yk5qX4g>K{Cr_0;M81<^Q(8In+ z=ah!PA(xC+rlD&3bn;o=MdYfUp|=z7SW_%wQqeJGZ(8+ZCAv9Yj>YBsIDLhbz6n-- z_^ioZS!OZB*>2@JVxD4UfhTEH%FIG1P6aU~M{hDHp50%DW;h2P^cZT z)B0t9oq1b_k``ExC!rp-_{WG%Jjq-6SLV;@?*`*+j#vJ7`6D7&*=1$y38w8)(xzo~ zC1v4-RXN?pabE={&)1W^ixK0BiHP4$Qb5i#ie@*Qfl^hK^(;m+`Bk{%`EX8ZqJ{&C zu!Zqk!Wyfb)tX74CwS-nM$6Iu(5}PamA$ps#0zMymva4@;EZrX1>^OuKgS>PHQ#6Z z!}(Wf@HC?QLbrJaRV^-D{{X;KHT_YRul6cm;a`#b8=R}=75@OoFU_B4+{GQevzV2g zP-7iiKGfB*n&Q?7!!lzG*y~o(;yb=#Q-R+VE6H&f9$Z_q*ql@Ln%MKKh~u@E-ZhOU zB1Op=$Itcu0P9v%vs_OmXI;3!z!)4=$yVMeR(2VD=LeDxwKn9C<2fOKBR=)CS?A^| z)ES+9pRq}wO7Ux;#5OwQ*R)^rWhc<~uN#?_ceC)SNhi|1+_FwEZFfF)Hr_|0_zV6O zr-pnzW3Bv6x|_s$!`zk9^xU5<9OD2W!sM|d=ExQHzl^kx5O|-#QQ3G}f9#9d?SuIl zP3-}A*8NMNWMgIVVpHou027pYv*nE;opzQ-bhm&5RUN?O7#?G+uDnfKWSVNVN` zxnn8v!hY;|bd-|bZSMTKb~{hm@50x%-weD#;13+iV!pf7F5$UGmvp8l067Gc$rxZl?pr)ZuI@fV16Ti+7e+p}5PWu%$^0F39Iq@GSsHMMM-KHgZ2!l%1Yr^ua` zH>XwD=7xJEhK?TrPv54k&eh){zg~>%JQb)3rTH??a_c0Fd$}Z&UCsQ4TX_Ur ze5ac8--j`I&3uc<0yER=UW2GcEv@7&!tFJ_F3S?*9hHwMRJ{&nak1^-Rg4O$4jDt| ztxP4s+luU4A31xwfsIQRBA5v$K9w^z2TbOoW)a3rY60oet|l$$K)c+LxTyA}iru2i zJgq6tM&LLel?o*8zb_}fNovv%!azX>1RA3xhsafi-~c_nGw)QOx>av30)1dD=!XP)lj%&jgAAD%|nfq0TFFXws8qiCC<8k8bbwx>OO_>UUAGVIB`pO0)f& zVX&l*%t^?{v8_)mGlOtdwXk0mKgr>j(FqWlT?kx>7V5C zS`6bVa{2eANa8YLE&l*UInUuxUffw}dvhA5@_7Rs)(cHp()nZXnvFDQo8(+)zB#UP zOY20sc%s}HhECvlA6k}6^|(+}@OlreWnEiIE5<{V2-zbad)l*7>}$zwM%}|D(z+=C zHa79o6bL-!5v#K7;kM(NyBsdSEPR@l#@b74nUs=PmhLgynvXNF9MOlo%FP-=(Lk!l z%{zJIel@uSf-qoQF;UJdEr3g-BgpEHz(L3~`0Zt~l1X5F{$b7};{(#Ra7xE4>Nlz- zihnueRouIH&OeoAZ+I@^vvx9hnSU{{kOyx}RsHuS>0AXU%ZE|e`ijfdG}X9~B$n&T zWj`+%{5YC+ofg zXf0({ztI5!?XW!9Gsftck@Ao39z6)@UU_8#La#J(F&q*5mfuaZD6?Hk2g@3?gevYmSo9p=;}vj&oRnj`CUa1WoVG@` zud8X=C4}0Zt#b{XqzYPD=aFNL6nAD~0IxRqTk+4~f5tmo`@e_U5^Iw>s zvR~}6@OR<|kM%h<{{S25zAcZ!_wspKC7g0w{{U!3Zsk_yQ7V(;5b)oI0yFb z8RG1oj!`Ik9!SA^(Mi5${Px$+@;@ohczZIdnMaD1Xdn{plH4s(%{j&Jz;OA z*js`kwzP?1g*Ot%YA66@i6_*PTLj=|(!UkWaa1uFx!1$fRN&r`T|V>mEDaj8C`OfO zb4e{(;GZ6UXFrD@A9N#Q;|oz1rD+7Irs}a|Ph!#)Vs?gAl`^9!`54a9q?2FHhsF?Uzza|R{(v{`d9CM9b}n~4+yK*ti6=g z)Hk*I->0Jd&&RmiF~Q<-9JFcLYk6&dm+HV|8v391b^WfsB!15?Hj$>m7QNxSxjfl4 zo4Fb>9J@;Ix0nYpWNqBLSq|P55=rv!0(=qpx$!$nzSn#M;cM+@PSb(&?qE={mtIOn zT(6qoa#b=%exp84+m+cQLX3=2gK$GB!3VD(j2in~b{j6tV=NXS#&+H})jyW0_Ue4j z8n!b6<6f2G-p`}|05kfW_(Acr;qS)J4#(mT0_u@nA1zwy&6q9Ko1UpOS;~-m^dkfu z5ni%YB~p$9DHtGtLHsNEFz}C!{A2L{09#)W_#auk(KQuNpJ=m{qh^nAZH%RWw`YX@DkYbLej~J62a6dai7q5ymO7Ng)BIz3b>h#1cQENXrS0gN z=SiF_dU;uh5;P?g7LlY28 za7DR-1A)1TEZ)G7e#5{&GWV=ad&E8;@TQwKx8iM1(@E3q_W9RWvn9MKyzNlVPy0 zm387ZXNuxo$Ca}jG--i@BYe%C0)dnHNq2L5s9jBceKeOhHx6Z*X_(0*j@?!nlIrauXL5YsCJr{^uUhWDCVVdNC&gb3-FRciI$f>( zw9rc&a?2W-ty)o!HU}dy$QT9;2<1j=`PUc4Syc1y?GLMSMSPomC2gu8PP=ALW zKhtjY$t>V9MdCPP7*MB(?{=!7Y<<^WTOTmbC*-{s_FMP|b@BS^z&9Tkw9B6i#cq?u zwotb-Nibq9mhgX~7mws(>e*o4SY6L?zR?2eWB5Bquk)BgY$yd$eW!v6pZX?m}N^yhUi;vjaNQalD!w3!AXT<#JL z-eCX&6;D5wMJQW9CqHT6Z=)K3spM+N4Kab&z{wmXB z@f@S&vT49#<0s4A_k7F(b`zWqmFwz5FSUOl(p=ojboa7M?Qruo!^*^&8fR_oA#CKi zZ9afUu;-YdId zQ}>)l;jLT-4dUoj=22eqdVJUUGvjhjI)*P0@b&5Px3#tV`+Dqs=le%~$$t+&XnP}h zsUgyQO$j|>J>~fN_cFBIA5GD$E%dD>+AB*-NY*GIg#t$!D8N+%u&p2nsVX_8ju@Uo$=48UB5ae=y+U?uczBHQVd;6EXy?37CQ@Q1omywC? zS-Ca!`6mij#mCxK_hb2=A)ax492{Lb9`w$S$A1w#OYs9!I+nd~-dH$>);1ytLF}vC z`je7tl333Hg+|Hd$jZtI`68zzNoK`Z0 zjp0>nbJIa_KA&?1=}brt8yVUv9R}Le+8CNP44*DvKQQcS^~4vll0Yj71$Op-L!P3j zJne53VZU%8``nYe9lo{AV`S8ph4^QV#_6qQh%Kap0OgO(?VRGf2(9hqvBjJ3by5_& z9B%ELR|^%@uDGaBGJ($0Fl$)|u|X6BfZf5xAmv0h`i7#nO>2D&r4Pzjs49Oh-&%dm zm)k96dwVx88)I$&+&xBXXGPa+t`XBaWsSD5Zn*W$Y+Tw%>}9u*lQ17EISMi9k6P)W zRnvDz9wL&dqdcC|Mv~g$uWr=FUxkmH56-q;1a&@R9li0^yNm5I-|XIH>oD3lJ#cG+ zju}jBh{i<>aNeT5tUe)3@o^YAQ`F6ZSn^@CLJ`k06V69!^v^2!luZWQ2!J{6JxzI2 ziL6+8Yq?#9c<-O*Ubq)fZ7of#ZM>)fTwu4Ttnu#ZEgEJ20Bht?f+2Bm(D_PajRetT zkV|`Wim!8F{h;UjLECwiSfgWpN2wfR>0KNamP>gD+S?O^#8LFet!L^Mwz@^v+fqH| zXTaXXfKRtTE6;}Kr!9&%b~jIV6n8J1QaHB@l{o0x<$)gDR$iqG>azWU3ysegRg7`~ zKI0W~au2T4j@0|D(Ojcip#^0-%5Yeo%tdU~&PG(zwEN2&sXo;^ zO>8!mLxF?b3|HDd1%bce71Ohvd42uOe1ob^(MGXFZ1T**e%Qb+N47}M=U-L)D|^K7 z9+HI8j`TyQ|g*c^;VesW#PkjFLH75C!Nv*5?~S79w>E$Zn_7i@0Es8D+)@{AxLs zo$&z(st!ot15rs33bHXQ_`yA@l&nfB$>dwj=F1^M%Y{*ZtTEBEO&)VPLkwyVsb}B} zfBNd3{Fk?~iyK(cjAdDxL68UIQrs-Iq)f=tI2%DupnW=Gtvv+dw9 zJW#hKoL`9ay*F5Nw$yF0xZqqV!vXZ}E6X+C4UG~+GnaT-w#LXt)?UQdxn5tH3~*f8 z4ZFBJ^!3e4sOg%1p%ROTm&lLijYk}T(>3T(#JFg4%93ZzQd`}mVb(a(EyQ;$KVJ2f zBC(L*VN_!sje8Eg;Ym~eCR$i|VUc-+uif>P7H5>-VIj?4wMJV3qG~;KX z%jy7&86*G0b6?cicl(tu@UO`q!kNeX zj4S^Dj$fJgY&OjkIy>(NAUAqg?Z80~n8Yta+?-WwaukTAk)B*ZFbVBb=JWyhlluUk3}5k9ytvI=nthACzi+;4G|vcYdQXD<2c?}oF7J}od89zBKLMm8Nsp)FUXOCQvm#Po*LO9*ct2FrEq}FdBt^5c7=6zCAOzrYa!x%fjqw-8 zXz%q4eFwod(%n3hqDwhkfP~sk20@+Nbf~FSMAnua$%rkabDV+i+L;>59ne`Fe6TVZbAo^R)u58b@eP}~>$qU& z=}{YVBY@li&(^qVS8_dCcxc645ty3LMFp5eh;z`B_!^dJqYH@{e9qgB4L#gOSangr z=CLH3&AB7bRv6(5u1DjF%~a`QSD{8{lTXdvvy&lij6*IDrYb9<_ffo;U`Q7QK;r}4 zR2L|Bc`=Yvspp}nJkL7k$b{}HaKj{HiqYzhd1%WJtYoN-NX89Wca7gv$BXCrx146Zhlh83Uwlp%raWeo@M zDf!#;Jk=$4a#CwT0IQz|YdH`O-0EKfF)wo|Kx$#wW5A-o5@l5eJQpY zQ$lVd7lByef)2d(tfvJf%O#;uoS`mgyEEDT7W{kEY?d7(#)~z@u-c1xwT#Kz_ki;1 zJx4wHud=l5Lc>+Fi%`>UBf7SkcSkHt9T<9oIIqpw z>y?{HEgiG4Yl}I*Sc%3tPw zh|_kC{MQHIpNLu~jcpoBGS=5o9kW`@K#>oq!6BF2ab4&+uY;T=2g@a9eyL8RD#cC? z>7N~b(Vwx$!+(zpKD*_%hvLB*^7UIV7wrtFRy4ap2%0rJdE!L_3g8kC&7ZSx{1d-V z@b&yYGWg=q>UY+&$@Xmq77|#mmgQ1u+;cHt1EPGY0r}Lo%D<~5W|{yT*SkZCvsySe zfTvaaH}|F4K8W%vaP|gx#f_XVCHH#UxeOMz_K-(yYZ^lgN)kBG1yxlT00F=NcCRJ) zyYbWEx4|6@>mEJT;IooQJe4g(d)HT>Fm{{YASG5DY2yT2It>rk6f)vnIh zdAJeAx!V+JfFp@AcA|g*B%HBkKKBOjT(gOLdh_L^p0;VpzSimL&m)PrqB&ajG@*Kr zzKhq)`WyC+{jfeIe$ZDbVCVi3uMI#XMba#1lNT!{!~U500(L^bE)`I9+n#IYJp;sA z?}fFCZxDED`r}K~tny&Hw2`5jJ;Q5lbs&s_LG=}oze@Yd_8<5s@N43?jU=1mPl#>q zF0CSVw(z0zE#$g(Vyh!gb_r2epDq?Bb1w1#+TXh3p_gP?T%`(syNcN>x9jWGv+{aR z9h_sM&r|o0X5FpmevtmgzqDt?4~^OsQ25`%b1l7;4Ilg^7QjVgc{~9WqeR4h`SMxJ zM4)a0Ag{W>+&W|0vh?0sr@+-#?VqspZ8io5>+JzE*oBb96b z;g=x(cla;<35}&$d`i8#@t(HYkAiIGj%_;D7ct)6sU(3YA@MhXHH&Rt!caeX1A@`FIY|@mM&N=9umllZ!1b>D7ZGN;Z7JbsRFo6Z zuHEg~oLIb<0gtI1O<1Wlt^4}-M>pY52zY0~8U?q7yeFpH>3U6)?1IwS0z`oFNCT-S zl20V^YvKO@+TZp^_%q{uB7cb&nx&t^{Yu2|ywwUKSlyZARR-=cu(XBSYC1BxFNWMU zf%hki^?g@B@Xn>JXu3@L?e&$r%?6un=^WQiqarkE3aDe-C!T{OfPb3*0NOL-_r*Vr zUNX5pJ@d4y8w-c~4~PC_hQl8&8CU}IWA8}ACnsoCIInYn^1OaOG4|MM+>ScRTfe)l z{PaA|Eyv)nxJ5$|Pn(-at649fPnM_8H&I6g)b{bplcZ9-Vpz#2;TW#v+!W!lk&q5L ziej?^!NxFuDr=HmKvrgRfD{r*{43e-zh{4qdRKu{;!XbmgmoCS1~0nc$MeS$1wM3< zee{L54YComocbF6jjxKs(s5XtiY@4_(!26M6Qzf%SDV>Iw6)Ui$oix9eg6Q0fq3)b zW{`X_FSotfgSL8pr{nDgYYQz( z1e!V1_pwCn)+wEGBb7!sD;_h7@qDXrd-WCS!nHhZ7Ec9NbkdY;?ECgOXG(M`rCu97 zoz?#UQ~H+pH}E^*zrgpMp7HCJ!ARVgw6_x@DBBSp&XHr2>i+YgnwwCjUU_5 zd?bTMyz#Du1-LOZmn$5)L=dYG7HP1J8y%`6l2J^ZWmlX{xU465aCg_>?he6&!{89y z-QC^YAq01K5AFnaXK;e+zZ%q1)mh8y|C9ks__^Lj#UQV2LMFP6Qjw<+@lT`m}wS;L^xInua+z? zqwol{-@F%ygK(5d_&7zA2ImRGN{PJI$T z7p|2*mAusNC*q$KA{MOt8+pU67UH?x%t*1VWrv?MYoB0f_$62p7l%WjhA`yb-{yGb zc^hGbc_E6x^}%sCC}3ax%Gr$Bm49jG^rNx+J9#obn|AcLQ0J+rasYI|>?iC;VGrj= z;=Z0MPpK;*in3-V<-zNGBWwf+q1bsNc7OzOF@7FJ3o(Rw+27X@;y$e95ey}qv}1)j z0F8R%S!LkCpo;vWa9yYIHx zIz$ID&mC9lv|Yo)xF86l)%n{#DLlb{6nzSMfnNQf5A0bkhIa@T4=@tZ`7|>Xzlq46 zWXD7gC14XzvyUF*;|E|t_M!gP#>-6kZDJfn=S^?V;P04GIE9Of<-{q9!0kUe&MAEs zU-=&EA&V5rOJ*nlasJAWcVAdwSALOcUIwqL>X9w@^kv#>S!BzrdS82e^lk-&iyHhHqh3O94hQ?Oz`~*t zevYM^=NTN;;X3o2-><`k#K?YXWMxWemUv$o%K~_I zAxo5*1#5I={`8ZLYVcBF`Wb`q{4WgEv#Cu9sy7Rd{^NTYk2=xKiC`JA?FwM z0YVL3`8Ho z8DsN8eN?$L=|XjrOtDx0Xdmf~QaWw*0b^@R7!;OJPhtWNwpj)kqCTAT57#G%P`^aG z(xf-~b4mgjbg9tq3a&Vj9iE_ruf6L8hj(ai@#0vHmcViz&sglQ;(YOV4N}Aak@T^s zkOBd%Ge6NkT)&q6fVFO-Oa~&YwQ5QcSlfvKHOU&^Mra36r9?spX8hle4Ud441CvTh zS4pyS{U*Oe9_9A3J+R1G@F7@6t@-8grA!OaVH?4fY~~FG_igf1G^L);w97iN2Uf5d z)|EQ5S`3H)%gw$UAAU6_!#sYJ+Jy5|_EQ6?5^qCweE^?54@=V_e0$iJ!m14`8m3B{ z;| z)%_Mx8?IPrPO7=|M%qeU$jboX-=o7re?p$HR!duY^wx4Gi6B{5RHr}&iAMX{Ln6~d zD*)mF%fle|_B7T+FF`P3jLd~zPRw}I5MxOqrR9{l5I|YqO*E<`W><|;CdUfgDK02R zv_^Uo9Qsh{EJfdO`O|vkc1WnAp+%YvPHvq1nHbUhGTIUAtIP535u(%BMqO2HequH) zkbFc^R@tatcX+ABG(-ESBf&pyw-m;Q|g@kJ~D z?8>x%s04LGCpmRIoN#4;2uK_$1GEynV3v5x)tXnx8(L#5+PcLI#^K17=}-zpbuY9d zK2}_MC}UlsZ5|ASJW}PtJAvyb;`adt&OcfUK+j4UvzOi=f&^04d(%2b!#^`ca`p>~ zx%wEh2l(!zws8NHI|X)hN@XV1OEePS`#qFlo-V5wuW)&5>1&gI+fVp4VpM_ zLYJ@9%HK}>><9g6;<=1qeImU9b96TEU^$ouGVk_*;2%D%tQ(?U3ygb=5`f6kxAbEj0 zwA;Vy)dh6W_I17(c?yq(aCNd!VaS^bE-Pse7ew^@_-KtZEU!-&)OsaJO#0;z&@ot+ zrO`Ek!JxHMo+I^5mi#_}p~D1yb-KG?(;ox1qWIEn2 z_k0L_IY);Hcq$&bnG}itay6Zf^8_9cYlOZO3O#Q<)M%Ca^=bCeHnOvRcDY?Rg64}^`d;CCKM*g6oCb!ddQ&U zd02;{A3M49@aXFmpgO=m6ezIdAr(cFp)eAkm4;zikXt?D=eRe>$1A{cdcAhdAYzr} zIV(VToh4_A9uJLrbmC0#meWi;uvw%#FR*o7?NjPD~o5WSDA{rJ*(5C)J|fz z{z6B`z<*^pcu?Rb*H^vK6C)cS-W?*KbpVFkrm1i~abBC>Vh4{3v0fy zN%^Xh8w{m%m_g2Yh&x2ZDRtAYX#Xo22lj}jdlW*OdW%qOyxO;p^c=f5ao~3JLq2zW z)&KM6Q**@+7s6IUw;Ld!B*?vk>aTP;`5aLIGv0}Yb0)sxYeBl&`#Jx8Ti*8AM!%ZR ziSV@o$G06-swq*~7bSLr>bN@(B&kf_JILk;Dq)OssrP_tsgCLi?7*eiaWuQe!n~iB zbTE5wE3oTE+(C#PhlqoXkG1d7pHujLxSHf7#e6V_LR!K;Q7lJZpBF(xiMksRC;52> z#tR0MyoI-iydTb~{{h6g4>|$JtF4BCV{yBWqskvJt5MVVyt}YgUOj34MOxvA2=hzo zE#996WvcNZ^?MkkyW!+;>W7EtiTeDp{addsjcRcL_HqIi%Wj~joc-zg69JIwYse6T z`)->%4c=s1Y*(|(&d0)~Kf!JJ0gNH?Dl(yPn5lMWOQJw%RX3C0m1^PjQ+?8-y^-qb zb*{?twOMDUL*0pJsjtixi97Zue*)J}T}Hn2piA$17}YVHwmz1_Bt!2O;C z7V2pRWsZ8J_nz6^)pHxpc>~FhF7ePUgJb}{KAna8=25h4lAzsr(blX}#IsZqi z6G>Mqdc9QH2rNp&<{@!g25!$!|Jz&6DjJ_SQx~_Y3!8L;=N6nmM(jLRR^uN4 zGZMC5{T4Vg-qcE=(uTVh@VD91B)M00RLA$~gx@HR6jTD8P_Gu!%j-7Jm&2qdb4mv} z&{pXi*c-CvBwT-(7-PCPPf*27Ww_=&j@MlO{SBO#@pO_TMKiSJ(bLXu7u(SWV6)Ij_u-qmfwD-;RzbM;DsR|jD#{$0OYP|05L6mWh8;!$;Hl>K zbY|`~wVBa3lr=Ihtj$upA?L0&Sv>Cv0J2%4?ust)`7tAC33dQ?lIKf-^3LO%NnPtO zkS&^a-0&*{XNWcLgeyLPi@CrDlYx;Qb5FU=nwqHXKnf8%VVi-PE5cCz?pFe?CZ~(C zfA6^g&o_Ij*W+0)@;vf4L_b|K=jm0{b&E@14dv3!DUcNHbM&lpUI4MCJWvYPp$?PI z92UC+ntCr)V`EabC`F2~)hj&aZ&|Ucodg<#QueHDDVFSAVyomm#b)rmM#_Y7`T?I^jd+*k^FH_Bt1e!(T=_F>wb7@Mx*kiQdMU z`LbaznYr;^zzpYMuJ-5FIj@?bMDpt&3WiM7Tro1;D`tYYX)SWoI)>p*YC#RA_*3ga z?P%JO1aEuV7|^U`TSwwaf_AQHoo|R}ru-Z4e*YMw?@6rKZEkJvCT}LnDSpfkw|ieI zGNUfvgFdzJNBE`18Fp`bB4p@$WYX;_+YX8Zsc=uOc2QcvFFQSUn>3{f;W;f`oaap} zbi?L#f1it&IUmmXP17ZZ&(ED)%BhkNK2WVcyz0Y+aBfX?Zm`5l49Kofm?k2^Coshe z%6MnlcWqHRPsOQM*FFhufsqGOXjjaLS1iU8zyXTnNX?&4;$*b}23g12c_cE3jep%w z1OBF4cKq40?mlAzfXKoyUpjl1mL)r z+#!MIkjD?nArYFE6iBjI5u(Ujl}alY&@#yX(`1TY*28BI|EKdL5dM_v?O1KCg{ix< zK12NUjNT|mnL&%35XX;PVumrl`mlL>@nrF0|1M#j0Sw?sIN127lzi0M-(VgC66N|r zi8+3HzDCTEhmoA%`Xcx)_Bkbr*>8EJWbIxp(|Ihd@`{&mP$DPWLm}&cCa{|N@ zl!y(b#l%xm!oxgLd;I+i7TsE%^5%zs0h@*KCq0ZYQ@Pb>J~`*@hSiTEg)hpGqR?-* z?csh%6;?**Ekg&2HVf-`GE41O*2nk;?LF}W$UF~2>@cL-7&j0iLhPsT%kqKIi##~6 zviD=j>aU(u3pLMQPTX}&RBWGua%k@3LwG(ev){))3ASe(pZHS^+R-KrX|LwY;Qpa? zZ>rJZleJ4l8i`hgkwD$&*Mh>E_Ji_a(a=x2#Nk4e)~}b3U!AcVq>&qT=+B57T$=8m z#bH7h`bz_Ycld6Nh8-gW=;Hhwe~Cu4#!J(AKb&kK8ylqSGlEa4Rqz@821sr91J`8s z94}vK!;No^#FxfmdUTNCkV<>js!O(TDZt&F(+-2Oa)qv3-rpp;EgxReq_&f=10?#< zZ?QxMO`VZYB+;h$ge{hH@7f)C??g5B@~W{ShJ3A;ddypZmEODkNr&Tu|BTQ+*T}wz zz`dLdc9kaxxx3r$8(2(k$iy0oMGMWrcqh=KCpiS{A>j+&{z+2wBx~`!GQPQg@?Fqn zmR7BPN!i!2=kk@2&epSLWFFr^RyiT#-ErK9he;eTfwfky%c2?7CQ<%dka&+}Sl%a1jtYfQ*-3bI#ln>6qDd*h}tg(Ed&=O${sq!Ve zN;@?lw%emKP4zA% z(D1mWy)*c#?zi0UbRFshDacA?(@cv)7axTA8S|;+hjwU$<0p74Qg|o|XCRzWT{_*o zIi6Q{J4zC8Q4HOh9t?xYZq$Ygslf`ovUGkI()W`WMf&x*VtS&tHAKPAlB1E)CEzQ& zYBw$|cB+0dpnC6<=Q)J`93St!svfspJiX-Ym*TMU@SZ3d)_h6$XELN@F`;fM=hJiN z6H>pt2?_DT4+cVwm3y81@hL3~qwVjBnLUxgXA>CL?ww|1{iihv>o-vRQw{Zt(8tg_ z*XPWC0Js3mQx6g9o9}HqRLj@oUdOkTQzV1At{9X9e=`!eka>*x|LjK#R=-}{&L#M1 z-ja>*?y%qbgEQYUO6hZS@biO`TbOMBVrU&}62k)A>J4A8-j%>gFR%ebLs}8f(#g%v z#k*W7+6^w<+mu>HEGFn*DDT&f%(NCP{K)cw6UJc)^pmRFhLds+pzX~$X)kS3%Bf0= zrV#<$(2He}r~5n6(tJzFzhJrIZ7YF~G{T5{UyPW81rT=zfvH2)=|G6+hx=>%q2mi} zVrVOCG$F8jZ(GTp^$+Ltf-Rw4B1jRF96w=J0_h9;xXJLV>Gc!zm1wYNOW=@E;s$kB zV(9e-(gl~Kf9Vdk!j!>qkd9^+2PW#On#MbE@INW+20Mt8pZt%7PMw^gTZPqUII(ROV=}Y>BYOOcj-Vm>i6T) zPP|yb10ybh#3Bd~C;!NPX8TK~B~)D@_JZDdcm}~GbP*cm^x7AApp;QZDxf#{qQB|+a^^3Ja$OyK)F9N*t%E+~+p*z)SYYw)-AWAGa=N!zzKO51E_3%hAaY1S zP_SX2R_e;=>|bD_>*&n=BtD|w;)+2c@Fn|Q(%?eQ`PU2bge`!Erk~2Lel4n#&nTc* zKxC-LVRL@Yz4*0!}V8RM{=|!NC!U zOh%?FDEe0!>NxlEF(};(C%G-(dGG{fOJhr8B0|xZQGdS5!Sr4Bt}Y+g&sIa>aP{ z7af>N@DI+xF)FfJ!{(e({3*TA_98y&&sG2r{kEB4q41xFpJ=s0y5o=0&LICv!a%hn3EWC3} z2sTH5P9e`==$!aN>5|C&Q#dn0NotY#W_P0gttQhIowQy^zqgoOYTgHD44{$w;f$g+ zh7D})!pove!cZe%QG`>|c#elcm!rf(3FAmcJvJT^I|J6$#xaiw72zxt6-5q$F@707wTwY8mKPO-g;Ic5uX-L2q$>>uLRx*<*Yd-t0bnlv}wTj#pN z>n(p>^Rpo{d4;*fJYg=aK#?h<2yI-6J)zY?f{H=dzmia8n#xd`Gv_sub2m-HgYEjR z?MMCdCSRRts|R5gnI%hLXtI6*tcQwY(O^^*YPy0ARq>&Qrsv z50(UHHqL^*W9#v-{n(@zSjJ|KiKVWm!d$I1z-h=C|Mbh1Zofr{1uL!GC=j z85J*_mLUCM#bIWAm9N{u;CPHJ+41%e?e-aRCb1({elP=TW9tD~-lc;!FO23o%#PdS zWWVJAdm~+*&*4H7OL5QkNli2YGUA~#3iv{Tt`i7Cu&06A*^t4A3;A+XC~r_>0Yg2k zKTX@W%=o1r^;d!~AdW&39U@008)|<4#vA38K8^drg zFLvmiNu$5{rgD=Kd6bO(o1)M{3z@`w-smcF(PE}q+;qLfF|4rRV(PM$&(mFO< zzNloncnd8zV^YGCTKrnCAW)YUFei$=dNQ(KmC~d1Jwbf2EzRUbUCpH!Lgs| zCT%DqyL8LbS_t+zLyX=-2Fl)yb4&qRf>7_Qem;b!MV$whm}i))*@_$r<4*9=z)J9qrAn7RtjYk~em#g*smGj0qq zLj*HY@+&~0EfG$> zRfL>O)x-#@x#Y+W4kc*1D#ujR0tzcrE#CLuLxnAf>qv~c)TB9R9G zq&Tdvbx*;+zT<_0rTYIAXFeCBEwslmnfjh{UMje9Oi=)%&>rnNPcGDx9Wv$6(q}pF zS&@rL59iKZLXuqGo0Jo&qfiSo`(N_)T_fyAVyjix*(dA31_ z%crSB$Br_qsV8gbiUdX6i6t#a2MTv^uQ2oGAJAja7E#K!mEgVwW9bo8Sv~CRVa9u) zkd=bJH99T6-RwkG@teGXUd{}w=}w6s`<*B=lidK&MC&3xPFdWQ!PB9@Cl8;Se-R=r zETgM($uF4q0ctOwq3}|ZL+RYkv;Pulm$MY}heXW3Yd?$Y9RCZLwpanL>7MX}BQ9e* zxSi~WW-{y{P65BhcP)l2zNe}HX`ijWXl(S-#xI#g%(JfFWto!0Tg1XAw8sF2^0nMO z?F}_d?|LCRUPl@SCzn|X`r|_(>ouKvx;fI@(p$s&ucdyKjbksq%LJD6uE;-5PGH!c zuQX>?H|tX53bbMD!wrK@%DFwB7Le|Eoo?NX2dg7iu;{(DC~r;T1YlpY--^h#$NmEt z)(SD25=HgIQjQXl5wx^FT|GN=&7sp3$61K~2T1&KVU%Yn#IBCwljZIe zAaem099?qufJBWgcZ)@aYy4d${`Sa~ZdnD8H?}Xeytk|i*KR3rVb+NcjqX(=2YC~t zqv^JmodX40({v;91@!fP6cwj%c50K|FIH5WtxiZgpk7EP_NK(87fgdyK1u`k^1J#Y zJ{J#JgTJy=m-p1%y?96ab{jLsA!&(TP>3l{p7>NHLd0QkQ#wtAw|#m1VG02uJ;qWs zW6{Rsz#Re=3~5Av{offs{&%U*VKUS{jzQ;|7egc?#Fsjqu(^uCuXij8#1l_Z=)b%B zpI?6Z-q}9xVBWHnG8%JgdD)dO`#c-^z7^&`y;-ANI@h-gETvO?~1+C)Y(O(|jjI zQD>A}3LddQk~Bo1jTo%w2lZ==9C<6*?I+}EEUZ7jw~Gpw=9)GTzu34s^UNUdbSRa|;me$U2 zfz5KHMczcrtuvEo+k;rDH&`Gr6G<)l{sa2Ah0_1yW+_t`N@8Z{|jjkK0N96y2~#cFfG zgr9|Y!~T-MrrXTuD`OFlWG>0eH^Cn|;wd|t)6K@RItX{8a$-uWWObFq&a}fs8g9_P zNOwZdj3!va{45W4Bj1)psBVDup2g>Edta|;tfp8b?VJ!$!-C}7#K+`6@Rs}c%0xt$ z0`S`->|3;J{fwAMzvn!-d_mYkur_H&x|0jx0S`mZ3TPb>8w{+E@{&Eqa6Tocw$*LL zf_w4CKd>VB+F6H;1L}dqDGI;4GQni(^MQN6$Y4djcsVNPj^*%_TJHA~>QgJ?hT2fc zwyIcYq^Lq&-IZv#T#@hpap{gWg+LT~*>w#e_||A4gBs-nG#ndG-M&({k+QCy^s@Bm zlYi`d#5Zt&OGdH_qw>WP+H4w z{w2Bz&Y-F2C*v#_n?Vz>%a)8W#0BXP1ku5q#9Ry>+`VxJHs377qyzn57=rC$a+WUsUG)vIcn$GW+Wy+Xt}NzhRENByJ=r`$ zTe~EAJkq6!W3QSKAdXLd$M{V3`Ss)0^l&$^N7%Xa<#Y5zn>BD0Wluq;&J> zEQ?bI!5jK$&5&XBTQTzvsZA7nsf<;e3suXb8VBvYIZdNEq#qPwi3r&Sa9DE%B75gp zq#!Xhg+@CF1m6P?^gYO;>tPeNsUV}+ST$++g^_KFQ%QBeGxil;)43n5_sGN9P6igR zEAt<#yP~H`uFh&*X>WIaEN*(N{n5>rJv1>kSQ_#(1)~yUr?iHLpcwNqfbfe2?YL^jqmZsFa0w@0qPsUT%>SUu)i&m@U7)yAUJW>Q9GM_=THdO+c z+m$Y&h-ee5`4bDgP;CcHyBhnKTKmYYqsmU~T$R@-w!>0{b1dix;eeL0k6~jGOU83= z(RhhWm19ih?FqW?CLDnbf2WGFs+}l0G&<4)egCj*5hwb%%2EBg9{?gd!JoaU2{(GL zNVqm9mAHwtG_!!D`1e4i-POo$KTn)-P)3F$mdhKB_$mh;_VzxdW6ORPC}+%dJi_CP zJaT5mk%}wf{|p|xD}ixQKz8zOW;3WxEqD-Tn#~PC=tV*qSdlJfKIG8b7}onaAY=;P zIH%i6oH~a%LQZP;1L={NOlj#0dt*x%%ZYK)$G^;DQB`fwp9KRT{!ryzX4Zmm<>SRf zt>9*?0SA@Wr7B)Z?1YVit8wM`Ox)WmDyGt=@}(n@@Dd+63!l*2A`4S-pzv{_oBSHXu>HwGtB0g7F}to# z^rEqJY8;L8ESiNqL7T;z{?@cv!n6V*3qfh>36M0#3)HO$$v0_xNV-t6rXF`pr3mbM zjaaUqJqgIkl@4bjX5Vp0Sgu*d%tv^e_7bMgw0M|UJ=NWnwD$@8Rn%S?y<90DmujB{ zJIO+d30HKjIY%hBW=PlB<4^u!T>CCpy2p#iQQaSUdO|!)BOaf`z{d6z5Vfyo9JoU@ zwZq@`_uJN_(~qOaibpJL>G29p>Pc-g`}*nt!U_Bv!wmADlcv<+r>M#}_rjNyD=WYb zNzf&Angek{YzwF_;YE{c_a^l0$_GkhDcV{N=jyHCjH+hZH zQ)wq|;Zs&e&`e412S0!che^Q(PN1Dddn>pbsd5@NFL%4EyE*;mwGGTIl zmU5Oo-LPlHW)5~cXYxRUVK_b#mEjb9pdPOfgta6;aCl$k>4}@9YtMucMfFYEzY|oU z+dHM}H^IO?hMux1-!Wn5S#|$<;Wj~`%wUuqnZAUkHTO_~t>8_$ImOV0j*s(Mz7l&S z&NE?C73k4(zXVa_5j`_fB#B1b#YVw3-}esJE6}}HtYK{SRp7_%K5?W8e)lAhS2UU; zPAkctT#V@V$J~wLX5emQZE!eg10x)?*0TNs6zj;I#wQOTBQQVI*d;p{T0IF32|<;7 zyY#nSX>a#ruZcQ>a@y>yUi~{uu9HD|i>i}G>@RFR=1d8*m^Dc9FAGk2GU(^Yg>Glu z=B2M2FWG2*@Rg?hwb29|hcuW?O=Rx@I~6yDfcAX>>z;V;L#~#v?-#>B}v*CYEvbAGFY?HR)|CLcv^MK|5!;$_(JksY2=XoQp0$ZXK$%|a(I?^ z4Dshp=_LoxfeCTJINA+1f@#Me#+-qg}AOt?L1V)gM+^3XJ7K4)8KakTw0M&o1 zA8fxKenXys^+oaZ^2*~hL?Rh@CC&uBLSRatDg(NPfdvLEbnrR0TvE`Bu+9F@o8P27%LKea;aT^?!bGAIgDKqvtMHR5E^hN(=Y$|dr*!E2NGvy3Z!~5;$CH||Kty_d;1ti zOMX$!)@ElAaB`iszN^zP&53o25#j`lT}KEjx08Vv80y#d`GD1!-}rd&*~0> zN&E=%;ex)X0>Kvsd6+%HDQZZl_+J(}dw+cEW+(=r0_ti6mu~Ot_sT&Q|5r?6t9R^y z%wPol7jcDeU+d9@t55HCLk!Dj1s@0&V$@v4U=!e}pGXuv-{NV9t8rJ_dDReV54`ao z5IGGp!uR}@Rn}d73ckbVnK>9=of&4Ay_hU28Vih$m=XXjhztMpY$=%|I;PvqZr~#} zd-_oE4(h^=#$spU&7yAsJPP-rx)>|$)V-sijt(A$jfw2 z;xKM?f`aMHZfuC12ee3#Bs{f)8IT&>`EN{Qxnx{1dEIrRU&M+gjU4l0B$71M<1|t!hR9wI% zssfLmsFEviO?G;L6D4Z^`gMtCWdcO7ymn+tHuM=t*d3<^P{Y1YNJqBKQmuoZ4%>UP zs26V0xmVsLx!~07##Je)sX3p|~Nh$E*6xpWRD z{<^ej%{@n#o^Y$WL>1SaFW5hfv)@nPqpF3LvM%ns=au0o2?&?8d^pG_ZlsFZPV4X2 z&A@QFm$jgp-00>Toby{W3s3X@8^05}Qg=SY`G_zWT^#0Z&pOrVCA6S>CRZlA>MS7m z^BX4C*!+DC>~!jErLzNt=BPqm(pb6Vl`hkGjLS%7(TEH?qQKVg%o%g!1)Br{q&zc^ z%9PD32B8UJ7MEZ>hcB1T>K_S{dk)_WWCu}Fl~VW?ut3m_GUBq$LOp*C((co1e;aid zh(C+!N}`O+g14z6&RRW^U!H3gzrKD>cAK!26Q_t@q}ZCP38s71ZW!bROd=6m~84l^vqOx4Nr)NGKct@Oiwf{y=Wq2_!g?)mQ=F~vN&{xy~3 z{>49)vubSIXqP%%g0BpE=ptDKr&aHvlmEVHFC>!D>-{m<29&jd1 zlYCv*G!+!(7bfSqS3Zs&CS2C}h?LcJc_&aF``615j`q?`6Qoo;V(3jgGoMwB{eFkV z1t*l%0GEU=*4#x|J+GaJejRexJ=++lBz+ZS@wa8vSlnMajyaY-2AKPhl=o_8N;fxk zgdFRE09SGCJEHNZ^w)gtcy2ztVLi?VWRcHjJUP2o?LDcyxV5Q7$%`a^d&drQ-Z=-! zFt|jCHwCSyjk`n1tXeXoP3ijI<$c)>PhA-Ibky{s_VIAbtzSDGyJY5!M8W8 zU@W4cF6Cjy5B1TvLt8_vmK=JwcoFtv{jIvI>6}p5%~rr>lr!kW!*q1$txZ5hiE&}- zC6aAWH658-x|pT#c=O1`w229oqr}#PzMSU6b{AxBtqaWmefIRGlx~lbpI;(}jC(_# z@U1uwRzpe!s;UA2;KRA2yNbX0_vE$;Mq-Wo6om3e7z7w6uyZhxqyE5yKEltNBDgvQJ~^O1 zEAIpp^zKB)j`Ghi;bN@`Ktf>5!prC0@k<_m4HQZ861ls^{}50cG4HS`t(e1xG~!dk zk&|-xEHSPmtFDEKDPRJiQ{?uLwaz>*tKVU5MMMf%3H_{FIv4zes!}$r!?IZFCr&ur zto9hsBJ6IYIgRFZM!iF)*k=0}JnqhSi(XNo8AeRc-x-h$KQQh@2OZZ}4+_V+-(!sc z*5glYeg-zYDc-m$oQxb$FHFZ#qif6K<8(I1k-1)TgChR}-4onJcv)1+{juEgN=Uz# z=1DeOVs3!{v$~RDQd~`ZCQx$+<0dgu2x&C%p$Pb z$~9Q{m!WKnR~lv!m4v3;yZY>7BpY6!B|r?X8iXQxM^sZ+e`91mFs>LW!-TE4Zg zMiDx9>8Vq#Z;en%xmq&?(WlUc1#pj#R#5Yn%GaB8Lz?M zo&Nh7j%)&&;DHYb2sMhCWfVnFIh^^*zF4~`h8r;|h7AB61gH)|^!Ls#bB~|4D37Z+ zw(s`eBzg7FiWnFiLV?Bka7RZ*)J@Pa4dKbLK!`$uz|a8h)NhEdL8Aw zz9Azty$)*Zab&DZC&i?~@h-+}?#|pe4DwgGrtC{jEgz+pX1-{{iLnJlFa8c{`+chw z9h8i+QA}t;{h|y>db}kITjKqJ?^A^p%!Hf-90>ir^a5AmZH`DXuNpi;%bTY<0 z^iME{q)Q_~!b1u~(BwqQ52cB-GFG(io+KWYP>|+vAl^#bd~S8J6M7=|uqhLo%9pd8 zUc^b}6~mm228co8_j@sHEbSOy zqS)+OOLVC_^xLCxv|nd2{qEM5Zpj~|GINvHSDJ?2<;FN*DdX&(hS1j(0mODUOfPe9 zsBvR{Ml$#qfJNR);~aBWAHM0)ocOsgtX|HyB3h|b3vY7dqd@tQ@at=AZ{Ya6EWhbc zfH!~3Mri0NCXnGs~D#2%8FBcE<=S0jsj`<;)O=t@nUv(>&?#^ zBuD7rMBtSMb1lnW{_2k?4ut}NJ3;Vz<>d}YFYB^3YaG+T1S36J+YO#L#0tZK1W$g$ zm^>{N1n(?tTkH`kp=U3!@pn?c7>fh>a&vA*{-;|6S@ZAL?9FT?r~XKShgxr@9k=26 z>^6*6*Elo>DO2GY)5iR#84hLIyKidye?ShL-^PBglrFcWSL=L>SOd$6hcUN65M6@$!$5uF3T^;K&%01&Sn z@^B#95t#2}*|Ov`wX{iBO>e?|>5h9df@mx{QQ;u%geIf$ zWS^_GJd&Fxm^-070SAaNsxVVCb`+0WvynqCUwTvFjCm-#HmKid;ySW@*BWpno#PTy;Y{; zq*TtmV%pxkZR-?A64}%i`KjXJ!q1u#oBdnzIEBkh@Mh%*|H_z>)zW5ob_dWj_0=M0 zi(kiNg&x`Bkn9f|TC3U;rfn3DgsMS@yP6!M9)~_`q)mm|p^zIm-g5K*0pCC%zeJX| zEproNdf=RH3dg=HP%-qz78fJUfaLtGv#&ktQ&qCH5v|3kZR&7&IdslEHmCYk_mPQ)a6e@Kpba_Rz>jj+B}jrQ==S#&3yj=<%;@0#*$q_;X9SNiv~;* z`4~8_oIK%PzFSd7mAubC6Y(Ga(ETsSG^897US1^$y@nX=FjR+2~z&VZGSw!LbJz% zN`Q`)EW-@L(XmZjb@`tSSF{Mh#3;P&;V`7z4M@+f_ucOy9ZdQ)e$XNPHaZ(o0{Iu)PFz{F&K zdz$D<#~Xnph*UE|pt<#|8_hQI>wVNw76=CLImf+pQEycOW>VguV1GL4yewmRr;p55 z3<2BJ@t^UfQn#~@Ix?ODIKSM;@jLI9#%%G3q#yX{uMoi`C>b^OM~gfW9rdizX-l$a z1Q`DSgy;EJm00{aveG7x`%YB3yCfZjepDyhKi0j>w>6-tyY77Te2WtaaNvuX^A}^c9xGyRBX}jt5mBZ~(?DCFVb4xmf{lBYflr z94S5O%xfY}0h7!v#z`6csc+*y+1G9c3dH%q>%po^O-!eA%sfXT{{U-ADh*N6glmQe=hj+cEgl zS{zO;?dE%V$4MjMxa0%sJ*qi_2?D9Xa7g+Jt~}gfj%#mbxpgpgPVY+bh08iw>rD(& zF$$B9mN`E@6}`F|?iE*YR~+{h;p?%{qkJ;3!Od!EdfoPdg#dfoG0&DxW{eJgMn8Ce zvVQ|!P9p~QiaHf^maO&lh%Q2K4>b&y%2bj$to<`yu+<_H*vaOn0G@;&LJvdrtCHQn zo8^d`Z}+puon%ZZcDOPtxk2#G`%K85Ap)pz5eYlfpdGHVN zs&^Lu0P82qHqb~J$owi}b-m51#I6ob?Z9NOYGOp+l?a01}}06NZ21x{j&NUi|el1TNZZi&msyD}gd9+~Ny#Z|b9ea~`|Wc|Q4;C>|LoJ4Ir z!Jo^E;-HrD3u7Et2j-CAW}sJBmCQ$!#(x^CHjbA~EYeEh-3p8z21f_igH;vp zY9`C~OtB~c3?Is<&GxHlf?5P1@>u=cnx`4Rf;epBCUigpO8k?G(tDTriHHq@f(R$_ zt$A!F%t-8YDQ1S|>kvo_1Ld8|f=&-U*sUXGAh(ivq-WZ>IXq&dg|D2m+-(s8Jg`3T z80u=&@W*v(Rz=*Ws67oOQS5}G%%xZ-jya<_Vb7M|GT?h2pIWGu~_@W(W{Iem)bXwOX6z0IVS^2sDowB|vHHy^qO z0~F&M0(T&}xq{hXk~lFW{BM*DFRx6Rs|;6IT8v6-MeCX1`e^k8{Q|^Ku8_S^9MC zV&-eN-7avd&GK#aEu0F_VzR>5R}rerRE)1VQ`4tf0D1dp5?@_`96%EoQ^5z;tJz+k3)?S-wymAq?o?9Sx(GR>hN{43B7b!uJxpwIo_~b=-&*|FGsrPGOUl=Z>VC_S@lG2b6&XHR_C1V%MhMM( z$@@Y6%Krcjzi3-H4~Y_8Ggh+RVAVA6vD|#&0TSg%nLv#|51peSag$$7jzs`sysQ>7 zz80)&;pXEdvq#Y4@RV^hof`4HUqj&^+0*tI_#GEpM3Sc&vSAh{esyPx{lbo&zt#;HfG&XxSNhqw0yLUN(-9 zz3_rVYPST4T79R>5*7J^7RKT^#eX*c0JWEjzCP-o6K^zsj!gDfkOXUeI@U-2%kmd; z$1SoPE2|ykNJjE=oMQv}Tdyeit?+xmUl}|#H--FnrbmBj?y|=Fo++W4JneYpjI4-8 za6n)f5~m=Leclk_8aYlc+Sa6|o|pSk^H=`7(c|$xOCyV1%F>XkSeyVd zPo;i@e$8L7cZ_~3iys*LMrk}d;a4%w6aa3#)Zle<`?9Tw6FUy(1(`{}QUR~Kz6O86 zKfWFOCGkDai#$(%{{RV<+*2cI33qHEg2hnhb4Da|nqqN>U;^M3P)RlV2M|<28jJ-1 z06Jp6>xukMbBERPi>vQ9`6c?l_$SF^Tn5usb1nY>m2LB958ywCUJ>|h;p<-vcrQ$Y zOt7?yID*80NiUYWL(JfAmP6H@$t03{*Kh_p)Rc!E>+#B!B{{m1cW-m_`f!CfsKHqw zy$y1{F7daAJQJmzS6{qHBf;|1Ig(T8ia9@CgSoFU_@nXLK=3JjCrjI@>s*f^3G(72 z{(3M62mO)Ht}Ee9XI}9Ki}fjWe-zwFbu5SXXzjToUqoJtJ%%gX$}$Sr*(Wt)iaKGtBWA+KzRle=dN$VwClyrm)K3m1csHvI?&>%C4~I4P1<+;!7`;js`i;*EJ(X4?V>L zDakZe@ELC80S8jU*XdQRypcQ+#LA5MV59he>(kRUa^X|SZ!m69IUcngv{vgHy2b}} z_RR!IyAa*SzS-1eH{&IEJbQ}77V5+{_T0VmpU%1~w^0$UL+w(@axHB@Wnj$H1C>(0 z!}YD&D~;-MPqsQ%n+?MRaC(9t zf*6--E^r6A$*Dw`O?CDKl35Z$WB{BFL9IBZBFS!27A+#2f(GH5wD83$uwV}uAoExz zOQ^0@T4YOpimm&z_=Y*4M2fS>%G)v|{#kX$USf5c^=|IVjp)ssay(>?20mzskHfm#%5w!@zmp9nfY}-%bsfFm+5{M8)(yUOidy#0r$l}WY{W^NXgUnol^Z2 zUGI%oK3r~h1_!f_cYZZ`W}apbD#sTeaxsOgF~@Ic0)@%-?@XQHb;2BzU393WxyxF! zdli8rs}=*Gz~YF@d1pO&q$Dl|I?@&cA23|{*IZG=msT*y!45$+_CLd{zk+naI3)@F zYvba8uNC$OfP+Wiy)$q3KdpTK021BMg}UB2;5(lWRU%htIO{12juc-q{g}kTWo}Tp9Ko|h0 z>N-`N4&Z)NwYoB82;XsF++gCMnI(X{>foqfxKwrZ&sw)68Feb#26BDsmDiZ<y9Y6{PAI_uuI$Ufr z3>5i_o<>D#rO?d9vWghNmnBPdR8T4RlDa`Acf@h>a0xZ7G*eB$jUrX(;{>0;bgpjh z2L~-FWgSU9D&x76MSBShA2q@}W>5J2h(326qyhLEQf%7hGdj-5*62)y zfp!?c9+>v4h%Ln5=~DSgf=SLeA4-<%O|+IQ+epZ9!wxg=P-&J3k<0z1C!Pa*xg3T5 zb)8Lg6WrrGZ#em1aV!mbHAEkX3 zUCOAdo^~ox=hXkv{V@wqrNN^Hzg#~adLxbTx-)1`kNN7K&b+48g5b9SPI<3h@q|WA z6}kTama+c;I`T`HcBoqXPmN#vD15(Ge^2;Hra-^PFVLqsS8~hHoYZ*jND!gsrGYco zn)vhd$vp`oKx);!v&k;h86@_rF(%%6dRIqi@vLgFM7z73;L}R($2%c{&N#}zpcxyI zed_!c-dx>-dVMOgGBw#kuC70gPamCV!33!Lq>#wD7q40j(EsAd!+Y z(yk(fPzUqPRJ6-mjAZfOtzH1)zoBq-Er+DPy1yg%yEgv-f3N;2er!V9Mo&RacUFsV zVYP<_m4gAm$@HLkj+k}YLEr{Gah&WIwMZR8(%>--}OO3Yms%?LLz_3mq! zl!%vc1dnR!{4C1SNXy1nvc*5#vwQ?^h)nGAYk=0RVS@9_Rr%kNPpx;=!+3^I^x&B2 zqn}#f!#hhu(9C6ZW^?mKs>a~39ASa)>48d|uJt$rs3NM|sghB+3=g_oe4zT(wgutZ zt^;EmvDe=f>q%VjTTG`k5lb)(bG&puf~#J`Z4^lo@PB}I_p3tA-U!6ka_%Y7(Y`tRgqQXC5R-9oQxkz6BVpewvCqK<;Me_27fxScvgKn?PL4iRDh3s8goq`+?LV# zj;914lzw$yNo89rzdkV*A#wtoj+ES6FmIbf#Jp4*T}oIAupwjoa0O6x@?zbbU;)4x z2kTlMD%8qq(uso{B5cC@de=1#)Rzd&fTKKyM|Qk^!qi!s^B@o%6pVO>&Vng7~0IoB=j4o^6n+V%2&$=Ay3{1*!HX|;MCErELu*? z`cHs|9crB5g;!-K4lo8sKBpbMtIBkb7F=meEphX{Ezq~#BSFBpzS?X{k zmXmLfA--1YkEd$+iuh`jr1`cz7*&MyMr`_8G^SRNg2Onf62#G~1svmw=;j?U{zXM9 z`G~5k!8knQ0+OAOI9;67Y)4)x@<@s@><1aEH`;q9)``Adz@KWTD#FqR7%VGTG)F73 zj|BTJ2@#i#ixDO`8LdkjZz*MEa>7>KgV22{Pc}v25E1!MlTOkUC{?U%})<{%h{+k#X8Fgd~gE`KVuhfX_HW|1akxP+2d<7W*;XR=rU>3-Py{G6G&960nZse-nD5n3X$r}JIQV>ZzKNA zw@9KzJEhtP$@Cx|YfXGX8blRQo8|#cdQ@S+_=rF`V_??^i9J-fhb~ zQHD%pADf@ZRSG6l7rW+bksBn1ette$A-iKMq{{ek2yLJ)e!Wd-$7t^I`NT37-4+Mk z1EBm3Az^bPgA=yV&gJ0Zg3R)5Mcc=yz``Pc2^f5Y_Q9)?Ng23~-BhZKAE@=FCZhKv z>|)i>E(zScW7@5Q0a>MxmT0g(RQZ7V=B3KS*^RrI;GP#>xW^SFi#+lN6Nr#=gWjRI z5vzH!hHeLzr>0l9AI_;qdo9$WDb$4rjBr_f$6-x7kfK-^^IB94O)^#&7BCnLBg+MkH1NqgawTerK_6Xv(0GTj0u_vYv^P`N5h|u*E(9org+NXuPoeziueL#g!)bW z<^KR^ay!@6_Bw^0qi-GEjl_2l$^#^kFlAtU#eQi^6}0ONr}u(BV>#d-U#)hY5b>9Y zJQZ-48Z4$uc0awck&lw&uNeD{?mryY!RFj9la)*_+`h_ZysTDXorap=& zq-ZxB0ZIlb3t-~B`^VoCJU8(7O#5!HF8SmixwMS_`a|^o5$=Bs)kZRtQI6!)oM5@8 z?2g|4`qNCbmj3=_g5DL~B#v;Zqq#NlAH_e~67O4@PY-wkSuL&9;!Qyg*N}G!k;0!| z52bkzj{YThzv4yXKBaLbrkbu=8yJaL!2TWM_{i&%o@k$` zes`X6cQSNveL9bZ)m7T!OuWyM&c`f%rmRw_z~+*^_MI3~rm4jrpH{6pvvZrf3Mitq zMnOdsQvj%BxQZuO+~J(y3Q3-HWD!OkN%@IvWALXi5BG%vY@7@n(r--TJk&;AtP=#2 zQ7XqSW`%}N1gXzI<3P#XmA+bqm9VOSZST(`(-mEW(K3d0C5{GosNOwG?F%GwqQD2v zjJZEhXd+h|GEAu=$W@5>hSSk~>n<4JjW$36u1g$r+L|~fZK>dCQEG%xa9Tptzyh48}4fYxF8U7=~>bN6q}lxZyYW2z`ILz%`cH8 zs_%9J?9R{*M;#4x#x_@GaZ2DMYldOBXpOK2dht`n)4H-qo?bd1TvKcTk}}PMlfeTO zAWfML#e;TZky*EK%3VmERiN1 zK|ju?xn_zpCc?_2Bq`_c6u}qn4YI2=ZS#4Dmj}}n2+z~48xV8TNvY;eo4y`1#)^snw_=v#P`t8_FJ~hOR)Ki<^KSC*!Hc-WWCm{ zx7njuO0w-N$+7m1xIJ)a0>{{9cArpiB#sFiWNt9LcLt=ezIm;@;cRWj{kwmt_N_)+ zZ8t`ErD|3o-mgXxx98GUGmLCEt+-Dy3=+&g7 z9S%BG9OZUsDm<*^_Lcq;4N#d<5=cLVT887C1==%#ny-CqUO?oK2Vikri*vQ?LL}Y+ zBY{p4nHW9+0;2#R0dpRq$erMS1E)r6kqHUyh>_tR}dj9ZWiXsoV zhCEe+9&w8J*)@9~RFX3fc}1ki z*(Y(+f-Bu-^8*u(1%6`w*31vZaoqm^XWRb(TI<3=RF@&oh?gXJ&(hbpj{4Cqtyo+_ z<8m0=h6kYp8m${i6ktA3u|Ff!{PnRHsBctiIi*eJ07>OZrCDV?dR_m{#B641Es`i z`{KFWJ1G@zEBiZkEYcRp{`OR2nAYk?+hJgWPFYoQSXVmH>NeKLKj%)&W4A>ZUT}af zKc;I=Bx_q(((uO9uscGG;2&Pq4aB$Y8X_jf8 z$DkGXryFURwb?J!{-E%pHpnCXDSm}nV@Qi40&siPt0hKUN-_z+$;C!unV7L&Pinlu zLCSM(MNJlJ|~`>XOljdPoQezm`vUz-w& zegzm6-5n1wXzR~4()ege=1yf$I2#D`@1N4S%nIr}K2Mn^`=YV0{3#t+NZPboI-2v} z7F|OW#K;@uPCyyXYtXCDyia8;*YMm4nHD}mso}o0&4stL)alF-!-uks^ApGz{KtNF z$ioi~>14)@rTAmBV>1HcY^gAi>BO z&%YH3^(n0hqY}KUcCp6DZ8$!Z z!+SI<@`c*t10&Y6d$R;(BRG+=LRQNIG zMcQPLg>o`;^vzP13z?K&#=j}*eKA%bEZ}aFcc(m7vP+?wsO_mFkh5)mcISc(E2Amg z12?(FK^%&iV5PIu2Li0cHOnCl2rJ!}s1*#QC2OI=Hn~sU6;&J()jf>&Iwi{7X-+vD z#z_d@p~v7WFkhgX?AuP<<8k$?-gI!}lq}2BoC?Zxqa|r0YE`WsW7ceZSEp(wIER@W zq<-;aUAtS`laPBF=uTJ-roMf-noEe*Sc^p>467$le!i93cyq>z9c=ZYBF+^h{v7-I zX1pw3TGq7D-%A*!qdibW9X7Wh)+D-Gt))D@SqhRnQxfDq@G!@y#ZqD{w8(@bbu}zVm>?*@_Np?gOi-p2 zf-{kxD&Eq_W6m-$!KMXiCP?=futJ}luU;xEdy(fcEL(p?$4Yz}Y*wnmA%f>D$C6LE zrdzNQJ-$9={w=s4oggY*8{3&?haOaFP{hXsI6rtE^K-+^(oz`I_2n#S6 zIO72K{3@lbsEXVq-|0mdGLzJi$Ujk5W;scKvqf@HCoZbF1L{s{oY6--e`vH{v`9`F z!6QGJz^h7-f-*hLRhA2h z<5W0833p@L9jZC7Pwt}##ertV4QSm<1Qwf|l~Or9>t(T40WwNKGI7FMYXbqvIF;I@J4FXY=EnCr@onPBrlTZZuKCs zW8Vjgw{EwRHt?vir^}PeFvot`t9P)8AqH+zKbsvoRZC0R1TJQS$xz=QR^7RMX*n%` zCcKDTiw9k#p_3$FVMrg=n2Tk6OhuAyyp-mlx+73rd2CTmL4GhtrA?;WT3fS8uzAn- zr2ha9YH3&pX^}}Nk)(-ZjdGzuCxCkMS7)^|g93x)u*OI0SlWu+6C5_~%8vaNPb_^; zVOrL zp1H3FG{|UUAGF#>)#N-+jLYVserP_2={#<(p_PLJ$ROkpX;^?VPCe`5j{|&6*1Q;> zW4SM=X-EJN5icy!R=gc#UGCv$Bm?)K=97{9Uu^4xZwQk zJ-7$2HSsp3ae1w3lWQ8i#Fv+M4ZcYvJZ|nD z-b<{4QC~>ho4NB*@(L)TlHXF=uu(-ZH z!;*IKlg(hfAl@z8F)FG+UflPq+MJ1QuW>YzZ5)I^PadRID@%mExASAjQ_~%5cxZFf zzd~}Vq%rw(%|={L3#ncXM_=%$qSMtBmXX|Z!yNUdTEuO`BN59I0O?veT^Xe8OJ%kQ zmINPkaw+~(G~0>bR47BvWPuj|kt6rdrAgllTQ|x?FKr0RxZ{k0^`L1H zTU|lAJ9vXSla1b#%l%H;#$B?N+B$^{Tl^}{p9r>qBQ75wF~@&us|B>zLgUPBpfMqb z-XkBOq?qL!7ACriOK7EmcQm;XAU5$fQlE&_FsY*;yXs-LwPYs&?HLCLV`%i@6BXfUfkbIXSi}$lZDU90rV8cag5{>UalVr%C95J#bX+> ziDEH~`_dADy3-YjOp0bV9&6BwNoaX;lWy#?4tiH#45bt@o#9iF&Q#;KYT^-!vGf(+ zSl3G)cc>HVY z{{RCGz7NxKIr7i*uaBVIP8+3thv25qg*5*F`sMv==Qxx25%oP>r}&KN#F!l^JjOi- z>rdDlpv6G!W0hkgJBC58j*`5OsJ)R^{fW0A*EwAk?tGFBU~LuKLrua zZBN7cf@+#Y!`ocOL03cR&=JTZ>sbMt9cjUjdiLcL%x&Jt`_tg>?F-@kRKA1p4llCZ z%;_9AQKll4w*zoecAWbG?_XTo-$fiLZwq30k1$7_mNucxN$7N9(rW^n&6c=DIIj| z*E2FjrJkRBqSig-m-n7w+%S3!o|QUTc}82HPUz3doH7jhcdUzKx|TbamIwQ9Ax3hA zM?>vSwK}94Y?941XhG#b+(7*3ZS*pl)VFgaaoS|;Qs)Ywa(#PM2<9=wOhpEIsb0UO zYAm*b*hwRKfn(fCu6aJypQl|k)0*LKvq-y>Cvd{3KZiAA>=2cID|}O_z(Dy2(DbOLl4iG(*HRAdpamfIG^}X^x|}DE zQ_Jw&#jb?LK&+}WoPI!8&UN$`k7Nm;N|FU!U?9)TR=$1n6Vkq)Ew`{oiH&R8pa0YS zJftHuv{&mZbMfb~%6Pwx7^xWzGh?{rXkv)t`&& zvP7}&XwL^ckxVsvj+4#%NCA$_bnQ@Taj)3!o;3;@-bLOH1~J%otFy}_dq_gSo^jU| zPf%NTu)M)->2EI?;AKy0HRuf`7TNR1jgilYTLC~3$C3!iIO=g&_eR<6{HR!Yha7z? zL&XU>)+K3Q=19T9Ml=#FVJ#D+GvNGEd~VaGoF{o?|QoQrWYv#t=aJ=Kz zn6X>Dtbk=UjAz%qJX*?FLlT$W#s_+ta-v+N(4}Da7iDfGQ@1@>0a`OU%Wz8k%HRbf zq4lQ8ZY`w{%^5`}1$uK)Y+o^o7F3TLdC%!dIK7C2aaLu-M}!|R5D4JbJ;KK%oQ{=d zLoAmHUPd4f;yiu=ohaQF@&*M}kuFW@jh!c6ztbIjnHpG!SX|@*{?Da))|0Q<>h?sz zC`tJnNy7v5uL`r2B(7tKNaa8Ujxkb2JoYyqX}3O9=)t(;W7D41=Hl}8Q&{eLI6QAE zw9lh0MBYLRh7B$TBz-;QnIq;dy4tGn0eD} zNgmZYv6{SDLR&c8{^$Ypu6-eyCb$y^{{UIW?wG*)zJj{=*BPcu638&snyIag4s;-8 z!4(geNij2{S+FXDDCnu#-w`nAeUvMGDL0&Qs zaZ_GfV25TRF~Lj%8}q8u3#6G1s0fj$U4&p`(wuZ;v|5JND1q6WvB@OoCX3l#*4(sX zE!!uI`_yr?;_0{j%mM)9bpHSvo>z(745XX0^5;0mwGu>`VlwEG$YVlI=|~5swoON= zOM?@}QJ@Ku2;5JmD&5_b(u6Qc<$!)+jD}xA4N{A0J6+C(C=i&2@ql)mWDi5w){0sS z(2+=ZcCJtkpmElg3m}eWhBasH-x=>qa}wM%ep4rq-!z1r4mwn^T|6gh$GJ%3%R9LG z(*}+hUG5#&fmFa^omsL70RFX;YpF)_$!IN@TF408*&%`VrvCtB$1slK(ss8B{{V2O z{Iegz4r(S!O6H-s6JN?cW(b4~ecboWRtx6F_WuBI#(rKn=C;Pwi1{d^9^Gr7TLpqS zks0$j-b*ngf4iQ8*i=PWqgp70P)4Vm9I^JOWt|w|6$DJ7qL*;Xp5O|lEH^gaWhlXeoa^&_={6B?k0daqCWMZ1>aDe=dPB8UpWR~C`n>*By zMJJaW`y#!zR@&K6K4l(wTD*U_4 z0y>_wh7%;reBHu7yu5s*^Uo)>PKYE0kYo<^14U`BZ()u|t>fIgA9MFP73VhUs)2yP zZb+|BGWk~XDCCIP^(VD?s-43=wXHo_>0$NIc_4~y6jxeGIU?krE3YPizX1E(R+|Td9Pa*sTmAIT@F$^{QHio%YD~i5emsA&}tKjFPNaXW$+w z#zZ5N)|`BVW=#^q62}xGPBE7M0PEAB^A-)gSw{motr+76a>hw5$IM9-Y4$G^rY2L2 z?k(HsDj3Yr+Jv)PM8TmFxl&b09@)oC`qx_wHp6@&ywZc_H2EZw4=rPe$X&Ma*m|F8 zmIc~bTma4J0Hh4hmJha}wzQq?iV#Ur&H?GqV^zW(5(fr3FIqx(v2UCI0M%D7ZLi%} z*vuhu(2<&g8RN2;!ErCmAPz)_KU`1*bKOr1+sv`~P_musL1zcIPS~qgH?i8;o56+i z6tE0%2^|MMwD@oBZ}k|on@r1dD+NVSki_~|7vsG*S<*Ep)Adp1?jB@uXCNUY1Nnna zj5+H2KD&k@tEWraC3Ueve-`Sq##9L71Y-&gN8?<)&eHPal6snIj3KDl;dtwjUhO0!O zGN0M#jykPzJ3s)}Q5Enuh$4-*;00`C16=r}?&x*lz9VwRHX)k?Wk(0nmf|UK^X((s ztyhj#ir75lvBx+#KZYr-8#GvnnMvesB$1!3am85dmvJE*oP*k>^1{ejSdx2VimI(D z5Tq78&N|kVWto0z+9Wq+aigVugWzPxg=_)m_jU3KTKgBlV;&aMzw4cR-x7ZTdY-OG z?@~>jAp~?Z(x9m)lS8_YS3J`f&2#&&9BR>KkqU0JB%2mVY1QapS*&y-$|F#VslA zNnYzb@TnDm7^vu^7B)1Eds*=P>S|gQ?AA9CoQH+VXX*jLA6omf;b-kFWv{^ogX6iP zv%CrsE{=9i!=`;0`jd+M`=c2nHI(To4N0w!*~2Bp%u&l_Cg@~P%E)pGo`ez9Vwr9& z=Yn^*3_^UWa6u3B>-g8_$H700-YW3#h%~Pa+}u6popUl;s?6Cjd4wn+ka%BD(5CzZ!NJ63MXMp+t8%XI5=CfO6I3UGfK zu?^g2{bZ4ullYI`2h#vmEk?^wf&!`K&hj#XL*hgNw#H)>`$Phl@_)+ zABi^+&7|ByBaCfqs{!+JYv)~q)P8mJ7mtw#h9*~ad6k!QU$wpy7&t+lz9$j&`48R<0sQTMSG0`Hg(TN8i+)iwh#b8oS`De7IhwrqiA~iCtrjq6#yE z&ow=qXyF@f%nmXJKU%kL2$8nJ!?xj!AJ(>vQbrOrRQ#ZN3Q^XYqZI0;xr4oquFMa! zuh3PT3aw|%3-qfs{RM-U?kO+sugLy3&VS$Qe~Mq53<@wPqPtH%B8+3DbiN(~e4p;u zHP@Qyd_DK%{{VKeuly+;Sa$uiZgkbjYf>s)Dlr6+_}5!phPwck>2?sT2PXiU)sVP(OBh;B=-X#1ffOA?GTbaxwhqa*#+Cc2FGrx$bImvB8o+LFAd1X;8a0w4~!0A1Uiq1$LOE?5xk8 zJVPkG*Ct{Ld1)G!#xhFwu6r*TD?0(6pGwr%%gd|JEPwzufS|T7ep(p= zl4L8u$4cr_yE&w^5#^H#g?D50qNOnr~ixHbDq$>htGY~g_(mF?1TYesWbi-yIkdx-pxF?c=wstGS( zDtRTFzA285u5-vB@mbp(;4+L4O6o~DXmV6;*EDyzz-aJ2`3Ad9+|zjD>ceok8KgzV+&!8u5gBw2f_a1adD-{{RsB*UaNG+0%U852C_j;Xik& z+%hW+Fv#>Gv*)%Sc}L+|d>%(iV}-{&SC@8=O39qkTP$-+AQ)*npQi$h9M(*>?5xLc<5w9F;%3~%71XQeE<{8!pF>L``Rnq&NAjh(a|<-Rd$rR;g9$0L@f@G)p4P+t{-(Bmsa=1Ms0wxh)xw zazHj( z++kP`V~T~5qSIRD1DlnM1HT01e|QR#%6O-=-aM%1{{WVoI9=bBYGKK#cJkE6Xy;E= zjz-4fGm<-wY0zS5R#FZ~#tkwixH1%tgM?l(da?eM0lK=~5_u$V2XRsIsRzH-vzK#g zD-ZiUuw|b5QpN_|8!-cKw;e@4bg3-TqlOMLsz*cXnw6rCNJyOnk3us}je!b8Wq;W{ zN99kSOQK4#yo)S;dn$%pl1?&xg(UXZFCwfmGodWZTeq;QGg^z5+~^L|#zt{UuJEY} zto-AOmhY)fER0_?uGTjATZLcZ>C-gbHalx__Y$~SrvNx7)6i9>L=LU624jNQtlf!l zWF-M)Vxw>eIPN`-0$Y}+iC@lt2(4pt8J7f!f#wjs22U9^DaJA?oR*Q?su$!8Fdef> z$tLC6*a~^irE3{jz;TUk2hKWnseZ`vZH^wGcjli4#vq~Gq@0jCW9wTs#@qrTU{6Lq zhLypQ+S!QL!nS8E-e%-Qko)+?s z>T#j;b?O7|K>1;M%~c8;v4h}bEqu+BK@sZw8TRNA80%-xNKBABYGD)PcYk|MzxI9z z5MbqW`fv_sYF>STL!-GT93))owQWj<4B3q%j%qFC96Rc3b$fYn-y|7^hE${oK4AF) zkM^6d5V_;>L2ZDmZrYNvFhU~2;!tYaQh8)3Mjga@{IncAL?AOM>}S=j&3`Foq*%*e zisT|(=&r$zRojg(F5t6xdRv7w%ocJ!-wL#BYo$B;>`)N6hc`pkL<%I4XjaS=?zI_= zKW8Jr=vTz_C@@=D@kEmMVhhuv9#Cx*yy_^h({A7FtGwvm+&&|D&WPkt$9_4jtT!#+Yd zpj`?0#Vtd!Tf<7u1aZ1}!bWY?S)OOi$HedQmQ8kn!@hclL<7r!F}IWaU`GRa`G)lg zeq7dm$Dl-_d}@1=X0t8uNduwdI6w+2o9v;Y(E8+3g6Uoc%2{I-fCuY`t%3c@Lti;AXNL=AKVxF3Qtop z6)dG2w}iSvJd`Ey1L*Q%=dW4mPQqJEPN2Eml0WL3b z-e$>WmfrU>D%%+~N7FX^pjvkN7zDT-D*Kl5ZEDq^rvC%-_Sg=|3&UcVZBqNF98oOw zaA|g$?=@({w!xKl$6TB?%TGhFkF+M|whIoazAv$e+5E(l6zrhoO0HAUKRvw#tYa!G zPrdLU+E1Tu2A|nP@1|Yj101ku4<&|b1-7^R(&zoil6TV-?+m026nw_^{Ob*+pCuax zLXgfKn+si4afID7_c|P(*u55J{#$MD;ab;M_``F;Me+&{<^FO#w%XeGeJ|}OM%~XZ;BaVk0Q{n6ag2WEdWh3`SKfSYHxr;yT6!Dag0{5XbE5`gax%S zk6#C03ooWtJ{J?L8>WIog=u-W34T|MQ?A5sJU=xNK3g?>Hq|hVMn>kY#Ys4Lt2@0g z<|(iq34_av9?Y8Wo%9E&ozfb=M>3gSbe#b-WiZ78yT}66rGLKrUfnUrt7lSaDbgDF zcR@g-S`v?3LhiTsw5`Z{wk||!2%iR6r{Mcq($e&DO<_%ICLIy=0tj}#T^!a%;7qK) zdHTU}tJy_Z;AiR^*#2iJYEAk})h*Z4_^)9Cd_8WckWEhj`6i(taPu6bi8rySTkOuV z^_@D~*>HC0i}F}VF+;h5S|=imHsboAhqLn5-31O9eDiBOcWu2qlRB(=@biBCxYgsQtq2AK)Y<@=I>cnvi+j1RJj3v+2wF10oN5 z@elAfCx#X1FG`qfp9B5U*iG%7>golOQqZ%jrUoA&pkY@blQ@sLsSg~TQm15~ zTNs<*Vun#YYSNI0|0SxQN^z(rAfDx>BI??|EK7Oyu?`|Z&&2I<7?>`KJGt^%%ZObE zB+SfEX1Ft^c1!#{HHa6mYPca&D=~n;2=Rw#5O*oI7tM2jeF%?09&j6k|9bB5OdTov zHzY+TFET}HE)q5XDiBvMuv__1ol7e8J@H_!Sm9~WK{3&y(wDj~l$5Dro2g$>io9Rm zS{y0SF51mKdgA^A93YqB)`7Fs&uaIg?H%XsDywLIGT4l8m^$TLuxa&-hrB+xyj~sv zo%^f=)~Pz)Aq*s<0Q^U|&exl3_u9x*P3<1@ncusj8}9qC zdzxN*SFRzUp0ejQLUAIzH!b9Ym~9lw6ojZaN~tm4fuF146S1e zS%7YwB!y=v$fDowu0V)kDD?NTFDvW1p(DT)KY<584B(I9}T5c8*DdxMM!GLk2<>i?r|K&~VmW25pOzcxn73_T9XFbBlPI+fug0OC zP-C8q6;V^I*k$a7fo$OO@xgD{&0K+}VzAqHyc&JJf6=ZwmvJ_ookG*gBlxVOp z%_JVMPr){HyOoQLWLyQ)AP)6jaIvLvF;ihQ2GWSVM>Z!*%0(}q#0-lyITFNBZ)gx> z?+$ppepDO0$munUKp|%>DJLA#F8^gVe2&*TC1BxdR1WkIfQfcFy)zOjuJnqozir6H zPG0N5viEvu-bwOzJCR0Lu(wQa-;3%#nMnKKGzke?C7avWx@xhz4m{qhMR7?x?{btK zX_l_gC)Tf@r)0*yS`)geCi%3g!nkj(>fmNEDW*5PXV#Y*hbv+67A)B>$XLJ_=X(@2 zX_2&XQ&4O!pPLKH4bHqNR^6A$Q_&~Ck?Z`@q8x4Iwk5LDdO0dNGh;jf;S`Ql{*e#ddy;#FGmz-wE0gLERrxI#wzi*F zF}VYuOD6~O+#8ri1}I+sju)y)qYRe41&*1OX8z1YRKiZwiaE|gh|9veAncN$HsYuRTMT*4%P_a$pi$UT-FJWP2Ow+Vjy+e9>s({_S z`*2Z0W=9B~eVU8Ry&LR~GICe(divzx@m%mLj-1@BexE9_RB)uv{1nN1hnx;&c6z}1 zDWO4?11l+OQ$oE0w)9ZF(gr^5O;p} z!Bg=be!chM8P=J}D4j)uf`>nCWcHMBlrw)em(So)8#dd9kvgVllxw5VEiT)TfMM8H z7j{Sq^JRzUA2T?3j84=z*fxK&bxn{LS&wc;sDALfw@WQa?os(1uUp)@L~YG17H>K+ z)*8e1IIH1M7Di8-%8iSab5lwiSMF(4bw@%kko~7XLwd6HrwClp2mGn4%)-)((ro9ZI1P2oQB!u)0Wr0GT7HOs7sRx#rO#%(F2!=v4u2 zh>mx(>oxXuH^~wXy)5q-u#g_N>h~B*)ZF&UT6fh6WTgXw9<@z=KEUD|ppZ_H!y#*>@?}*zDDB%Is}TOh{_5tMfCfZw1R7TtBm*_3b9a ziSn+fuCGWqg%XASfCpc9g~|RLG<~4xOQZ$Hp*!UwS{?U$a1}ETYb|qv0;uKg)r^d5 zA)7`G*I!pLDjx!;n=0jc+4Vz1(er}4$rt9!@M$!3Y@8QLO3Q1#{sH=?XntRbr8vg% zZ9IOuPCGYkZL->X8uh}2pBgz!5?XNSOZHs3+(WICKe0()mYG=aSA~Q=7`k6p-pD_O~vNBwh)n1GKU5r8+QtiPeo~I<^-nCsxrDyDpl7w9vN>5Rc8kxt0$+K_Ctzn~ieHaD>eog%Dc*F5o6DSw$NJ z3{q9P&lgr!o-U<^^h+B!2_8p)Ws|6u+cB4r=9NhRZYF{cRW_PkE}L1qxn8JDX0dEd z7@r3xNTzrav75^t&GNblapOi&sX(=CEKf~nUM8iF3zVy8-uT2+m%LRB% z8%B7+=Lhta4XlLwB?QbsyboNN<{d*9j#PhI+=Ta&eC&{Q$z`8t|B!zTb-6vGeAD3^ z;ScWv+^HG`;OcxkD^0;8J+zRPh5l?=jSe^=0n8RWcDjENt>8QFIWbet`>0CSMG{0m zja{Ohig@Hv_(j54&8N*cKYmfMvtw6D`B=Bu8KpY;daY&{&wS>d1iOc9jG z6J(?`G{BX9g(1e>Af=Vg*}pDIp`v&A4c96L_S6P*w$rk)LI-J;57R!|G zEo$o&m{_Gr^;YJ1!poQ^JL22#Sajm>r6HDVMtXonqo6c2(=GimVQZ~d#e@CbX@=8F zA?oAyKLFH_)#y=k_W#%aIEvNq5Kt*9&aBd25*0V-94$-O&WznM33rT!N%8m3W5O%A<4+~p*`c+oSp78p==wj_3 z;134z!>f6eJ9bEfc>o?capFeiePxEXrMaf%TA0sQRhS0Dk{nyF{WZd$OaM?^7UbIW z<&KM=htx^9&}UdD8>n)+_9@x0r591)c}2@8cy)f+)V1X2ccfwXMpY^gDuZ+TKiFk> z0BFmojl+$+Wt=IT4d2g>f|(oyL;aI{4lk*;%3`@CE_MP=m`Sb?G9jBvw*+!q_PrAg zs6N7*SN$lX?%aTFH>@==ezN`G0hCG)1}S^g_upiVqUd)t&z@1b*ou;Wrb%A;qZ=x5 z?=O)LuhV-}Tq=4>Du2tZD{84K6EZOUy(;7U8T~}-cGWBW8oua1 z*A3aC#eQB5;$>dAf%3vKgeFk=EGPz3AI&?h@2nXPUfoKW6WoQ&6YaO!oi`@>$#I`Q zUy^vmWHHJW4Ikf)urJD}cAA93jcq|!s9(_7krjU6aWuc;82GWURgzm!GckM3?w`{0 zbNY+;V|Gx(EVk^!HZM`a?3Cw!peOGJCVvqAvkl!|Z?_lQ()R81=gY?OWAK1(Dn;P+ z`h9G=TG^G^v`^>VMxoJGBIO8TmGJxP2bc4Z_!kAXbzd6bH7VS5XQl_K7AKU}I--kl(@;DRJ76%!9sO>YbzZ;uZ18DQ2@IHKm7CNb<;UnPm zxf(DxUOkICl(~iBB*dgGGD5WCxzYCB{FD5mL#J`7AB32pRy2E~@n=qiUw6lBwAz$# zdl#8dxJ15}b8J(aZs3h+(U{Ov`c!N`gATyM6%o52x*cL;y+*HO^>}Vn%8l(pCNUZ5 zWT%gl-e|hDpA&5#u6V5bIz0{?+U)FLyeY4^S={dEEL%$h2Ed~nXG10r2@8L`L%*L$ z60UfGF-{o1@@?pv{f=LsM*Vvpv-Nu(f2R8ms04##8TXYui1$e5HHzUaLtN@UqW{HE zY#LC_$t_0aRH|N&p{X^`S|utWYCh_6Ojo>Py1?9pU3AJ+`T2zr=hch{>YL27D^3ys zw@O6Gh4Re{(5a=>RfiCLq4a#l@P^6%IZl=uHF2_1RdI2ZiaB~E3w?Rth2)GWiWq!b zg?6lv&gisIrXn4K&+T|>Fxmr5UHuu@jS|F=oNHq5YrnHbXZoss0~4@GP8Kq6AiaW! zBjp2~6dli}lk9TrQ`)uK16|6~s2ad#QSK<<6CkB-NWwlE+?>P$bZ3iy%SZB&RUm z2>Ii;if2rm3I9sZ@pEp%4JhY>+0f#)t7lW@r;27JgHXyJwL&4GT6-b~-AVsRFXR(h zQ5fQme2%a7GXG6lD0~V$xl?Wb2;G6-?6{qp}uj zUua!$@&TW&n{(Q^U;6P=B4XX8a)o30`J%YXxb@8Jgi5duBL_CX!uCX@U%}v)Ton9& z?dDX8&KW$N>ysiz%~-cnGBBpzu_fEXk{hp1LHg@PD9M1QDEaeUj6NUEUs8tE?!Sc8 z>VSok!A9y(Hy-t;Tq*I}f{HYDFlmLRM$wkGBW3e2tr(V{??Jr2qdvoJk{g>* zBJ1P39n(9`DsFoC;e=O>Y_j{LwK7o-CN^#X-zhJFZ39Dzypy-Obe_k%DBo#s^;k;6 zNPOy#@Mp5LkX{S*(>EGvA|z*--`hy8<)- zF~uu#G5C8h;l{jUqg0D-3OPH=)I_BYWRKrNM+S2e*;!XYKkb@P&pQBTP@qJRcVND0 z@H(&ZA%!Ev(*nUnT4TcWQvI*=TDm5!Ni)^4Zwz=GurI2KTwsj$qF)0i+ zp;UsUH{&;t%e2hh!t;KV9fKFy&yn-~x1*W^?}R;JpeL$7)>2^=aN1a)4^GYe1HiGF zbp_&C3n}&0%@Fw|21oU9il~MDM{*U=eYUfO_241~aNTR(sLyT6O_M!QeegPl0Xe+Z z!T~;^H`@o9#D#0u04ftsNJ=WJf6Jk}LPvyG@Lpv%{sDgfr+zjposy+NS0gbgg^qmv z&j$x6Q25yWbxeN=*zwtm%+I8dI>p7O#&VpFGwH_WiV0s?E{0x@v?W49h-W8V0z_ls z_E}xcG~NS%Fq>d#{!H|e#HEDG$p_D+QjsHN3u<#a4&=0 zxq{9I7m3ZEx<|aHAI)`=o{&d7a=}9L@c+ZdsUsYAl2=n7_=W*Kwv*X;bsYn;b8^$y zorY&!8ErhmH_Dwh?UzQrszlzO$nj_AzOUPdu*lF4N{UftjN>xbZn`#RoVtj8mRHg% zp&OnISoPxWB?>^SrFba|Bk>RV#ewT}$&B9af9d&mcH*3GN{y#&zBI)j#-)*;XF`An z2@MAiNeY$9kp;ea(kfT0&n0YiF_o*JxsT-5=DHd7?CP%%D_W0Pvp;a`ZQ|cXZK}}& z{J8uGT5w*^ZJ2uaGuc)Z)MPBL|AK6$U~Y5`NLV}@B-giu8}isEJmp_QOGmQLaITwwebugohp)*OaD9QRkRPMcg=v$fCpQJGnAn!3_M^M9 z6X7+hH}6OrXk|lk+wMy3X`vW4D!iEg&Vl>dz-0&(T~Q4xfWFuHVe9V$ky>NQj{gss z`#%8XC+_R#K^*XE6Mq-aa0AM|=AD6^O4W)KxxcHp%{|<$51cp@6-LzkxQ*Pxbyb

fqVur)!^utdG}}U@jlO$4zfR-yhDoY{M_@_U@`oJW0ps3z1UW9TYDAqs z=J=HOHEPlOptXir#Ty@~%bWCETSnOgGf_8aWauO1{xE>#aR$u)T+Iyo7D7l?FirYQU`#8`$KDmkyCV86> z98jd;mtns3?Cj&ReCBOzcI7NMtLUTJv!WW8s#H&=EI{Z+Om8}oR4P`zfHWMs);0-{&z-+hp4e?;Ik8;E#78=7OOyTcnNGuZVez_C;8KR&*8RuBf z{n`|D2c^Z4#nZ5;mY5Cw?IxUKxU_JWdhxga<+H6hy~&oYWp``!1UjE5I38KFUz(J0 z)fG&hS%Z+~+gQFWIq6x7XJ7h`GcGyGI zUx$>lT`3 z$7}TdWnGwI{eE&cY3pP>bm(+R$WG~J)Esd>iHbmmq9`Jf;L&^fBohX9OeL;De3qx; z1VXB)dG_}4GL<$}E&)qr?E2arf(ciCDseQ=)NuW5Ry8g%PaJ z#~x4jn6>fhwc>G{JRymw@p=CFO%2znv;4(;v^NyUQCT08jkv1BnE6(^UjewVH*pHR zw3drZ6~aDzD^P;&-qJSQ+Z^{ixdNU96q*U`0*AUbv%H4i-8wd9|CE{K%@V%*1B>3u zChSO8fTZLf;hi*MOGJjN;qAt{eCkP98$E_Kx~{HDWpO%}Zt3 ze9&Uax@B5AUAO9jfN_;=hILC7;bpd{syLo!FO?m0CjG%%nYtjtodADKy37_rv3IQq z8hY^vgh-HErs~}2LZp-v6xmA0wL2NE^GO}Tk zR{a|y?k9A6fVNq_pZ3cekWflOF0ZMS1B-v`VO=-3>ySf~z*2)skW6{^pLa2N>W(zVKv)tML7nd9HB%PcqlJXTu;)K3eK z@>VRJUJvG{;n6KEj*Egp74t`mvO&I%nrMkUTw;a|XGT#O@45*2o9`>Bs4b{*g@?d% zJf)PQ0UXXTX(bzVS0;<&dXMQT=97+x>gM9X6v?b798;e7+-EcJ%w1%6G?#ef299BY z-^g2h8)z(#Nb%Cqdn;sH6$3mEnvTyQYHD}_UmV6q;4uj8zGWU?wDV)M#+7GT?~%+a zqfEi#rbJhEGRC2+zol2$k$IUq0^rUv^$vO^Y*Ah>^hYJ|DyeQ;kf-au)RcyJ)agyh zkYGSDX;(SM4l`93=6=+hwQ5M>KDz1k&k*hKZrT`zwvcZbzu?*EH1OU`!>Bn_x*@-u zbtj-J4oiN!P^~ZryG)*X-TB_Jwq&BpdR9!Oc65=!m=PO9v)#7KVW0kfs ziWKE|iH3P)OdJrz(LfD>gl!0EXQ4s?4E!S80vzgdnOZ&0W5!q3r4gE$Y>FT$1(#+t;CB7%2PQ(azKJ7v5^WhWL-(HlD45GaAhsxS83kU5=^X0 z?b)tPc~+HnnNqv}Gvd9yPgPdL>*KikMu%|B_RY7W_6uzl&o!K9?b9S&+3HEf{>leW zG}$1|kScgqzIJ++rhvrC5rpYTrBBn(h1fi}+UUy<5Ag&d9BtdzV#$mI0)$ebl>NkC z+HLBUdzIC5&eFEchwDF0=Q4ky_6Sq{;k%BZ<>WT!*Xh+BMnHIN znu;;XaW1RFQ7^(l8JGgOmH{2CK zKxrFQo%6{ldG%wH4-%Sp3|uui7E!L4M9VNfJfMM@WSyi&CwbK=zT(HfG~mCfK&8i= zoz7Z3XZ~aEYB7do5k4Fiu-_cC@%ylD)%bn&TecOErtsfb>m95j;6{V_TGE(@9&NbF zA3mUEqrGC-w4*t$9B%oDThfbdDN|J>T?E{I`{q#Qsehwk`=(FUREY`sPj(@vbi@$B zE$v_|8yNR7aEym40Xzdxs~(JDQ0+rkVcZPhI^@$0G(!W&&+QimlJaThSSrihae1;$ ztUYReW8CC(fU0VzWX^fEs^?#$e=22t(=@+dCC6D1OaA!uQ8dhMW2E2U7dV2Bq=fFB`jWJ}?p`l24(v@}}Fwt_ECVU~Jx zJl&B8<9?#D<~xiufPwc8?~DqnwW+EcEmk%R*lTWJWUD-_cG~j~x{*42Fjolf zQdRpsscaZ|OVLJSOa1eiL21{3hlSZyW~s3@W%&0}t0h@bd*07f-9rPTYAdjTj6>N} zgRUbA;$P5<=AXNK*)n~dXRHwcdBcuklP_!UBi;-;;0M%@@CyB@qimjK+LUu!tazrX zKJ!&D_YEm-bFYclrb2nqz3hHQIn*878?$2fP1EZwltxFiAgwhlQv;g%Y1Yg_t{Cs@ zrNbuZyN$7if!_$Lc3ZGHD{4&6Sce@C)tKr_ayB>-nD znX#30eQDQ!RYzYL1C}{eMnmZ-Gg(&8FwRd6garKf7EU2%IY)m+1X{>YGvH36;8> z>s)q$^}nRrw;vifJG+~i0OCCpM!t`<`sA1#8%8-Q@Xn5vse)SFo?{RF-u9~I_S7BC zSxIu+wkKC4uc*rLn74GMGSP9@N;{OG=y{=CWRrMYo%gzv8_($V{3LU%OJaxYwwRL3 z`bP@Lr!Q&QUXy~ZxQ#r+77h;$^!+HKBAELDR8b43dWc;vSC2r_Xw8Z{rIUYv?&sKj zDE_l*Y5K6XfhH{p-`Xu)I<(_2R0ym*rcFMQoEqq`jy|@-u96fP=g`Pm`jTUwcp4Su zJV=nm8$LXOW)?{04_C)O<}E&dIb{1Fi90}~cP>~uM!d!(%n685 z%RDRD4j-&2S&=!{^k$Z^?xle&1*)$d8`xhOA4-LpfZRmf=?qwvU<(BUNN4?A4;kxi z`CCVW>6|}?dOo6DRjklt3y|3Hlzl{Pr6o_&_HR*iKv7kb`+Aufv%0`6^Q9t8!P0RAJ#`17B&fO72w)o#tX|%@*bxK1c?~ zivCJdJW)QsKB*TgRW5;Yduf3k;kn*bOz5xze|EWWt>!7bXGE26FhGWZIo*~+XG1Y$ zCDnD44-gUSPz2C-b{;hk66a}B-|e^xh6|V+U}E^QU$*`^rZxjl22~Wll|B$9WxScW z(|v);V=w#g!*U&_Sxv(fW($OSW=7K5o0gTffc^a&1@WaQN4_64WB2U{vU4AeE@Z=o zx5R@K6?q8IZ3bg70m|TX8}9l0#&y@W+`KCoohZ_$c%G1InnIT5@x6N}%lzsc0W?`kn}` zFGM~DF4q7#-%pFDx9JG!EDY7nRZ(g!#;G?V5ArJaW6AzWmNtJmo0mZ;bn&{|F4eCc zG($*4H`=fzan!qruuu`DlQNm_lq|l;zw(jYE&r+^wKu|K&NC%#s0ymErCO#|Uv?h6 zFJJK+L*&bB@In|WtC6FlkYpcw^j`R`AH#2`VR@K*!2Qs+ox{aEx`(oiU1O5N(0OqOjQev%g5|R?XB)jpa1|T8t-R$+-~E*0 z$h`wR>Iexd^s&b+ybYIddr_*|{JW{}@EQ(J!WG7;I}h=DJL_ZUH?-GZL`j92$%Id-pz{t2A-ZMUZBDww8{&n!``q>()E=f$O zcW*FvuXNJ9wH8@Gw6o|k^0#_$)Z?mD_t-}G19@FQ&hRSLu8wF5p@I=?u-G&}In zUbxskG7`1CXKdzq=VYxOoo`z?;bm5ml=R%V+|<1f@lsF1!qf_;vh5Nh>i2c>u zCtJAuRpd8wwX2Akv83-?QRb0>_e{!Z zgWrD_f%y1*`*GW+9kbD!(-NEz1*uP}xnRp4TU~l;2g)j3iV19RlX;)O> zrV+^6s_?sW`Qm%7i~WpvXhaAMTp_EU&}EC)lSJO&p)CSREbo28g_sIT-&D|ID5g6f zDKYe$cMH4R6cYzf_h6UC6qW1gdZO3X<>R`Yr-LhV(%(#HZiCOPJ!S3Ac29-XA%Fb& z7Plc%#*8yJ_P8rY+M&w3axuRy{E~L6*^%D=M$^dJFk_1%>u+)-Bv7>-AN@v~;QTif zi(==82-mL8*GTh*ETdUe)<%R_>l>JzQHv-wW>s+$*M>1`l4Yl_)#|O4@n==rfeD*< zgnqX5>cYUOX4P0_eKOIanou!8;Je`g)iCGGeiSaaxMUJCt<+9#msgvnMV58P5%C$U-gN*O?>Jap1fn9>UZD zWD9mBrzZE)8^4+yDdiquC^hwHI(w9^z3T4WE}&`;GB$8B9#thRd8&EyPYrJ z?4}4gH`xti&aQ-vyra!8DkKWJMV_wHn?8}a6)86jg73r8-2f8Iv`V6nyW^u--!2#TODd2xE`pf>S-`|P8G}u;r__jtn zDkUn@ijZw@!8FSL4eB%B8z*yT9=i0L!XVaxXbVS+G`z^@g2!2%6BdHErzan9P%WqtCovUF{{pSd5IMe> zp`c~2-`_!no7$UOooQ|=L~ZhduS(AK)h?#JGV2|G#DuPwZUG}R2W%>lXoGq;GreQx zz(w-&e3{(XrG#ci$3ttkWeeX4BvJQGubKzQ3xBkSw=hz$ZRemAdf^`wq)Vljb=aBv zVqLb|35ag`eu4F*F%4eRAfniW3*B!UQp=G1PS%#hdaK9>dTdp-6@Mj=D4fWkhI#~j zV?vO?A&2YYJG{K@>yzYj*WWR{d^|Mp$B zy;HsSkC}qn)PR*l7skmsuD9yJu0r*Ufatu3jY`{M)1Rx zc2QG#lN{9}vR^ZAMk2tLh+Y0!GQgD@!ULc4YG`@Lb#BHh=Bg1!j2LCks6Az?Y(R!V7OW{d_N+_^NAdY!nBN-xSU;xOjT#i+Zd zf{QX@Vdsi`nvu6au|j1QayySm5EN<4yK>0n@y6^YN% z_Mu`^tQ#YUA5zfpImB`3Ty@s^TNUb|ToYMRYjW79`WrH2osQv)!Id1X;B~kI?PZPc ziRO|YJpDNC13dFuPxC=@)bV{ArFR;0^NyV=s}+2rpMSWOu1UWZypulAXJyFfY2#Uu zBV9G@kBYad-@|znEX-C)lb)o(P1Rx4GXh??_GET2`evI6u|G)_N>_{;AdJ|fxXP?+21l-<@}P2syajlRP5>9 zKa=)pixpzW&O9bc+Ua!)?_Jn&7w2p?f5A zyxd8$H7?w5&f!vGkj1h#vBYE1(Gs?%;kj2@RQeAf3*ug8)wQ60+Gq>i6f3rBVn!zcT-fPY?6 z*vh;rO8q_wbrS0my6E$@v!>GS2oC8yphSGw59TOpYUl*f8EbD^cBp<^gJ%?h@?Fua zYQEc68EAlC&EJD#ozvH5q9XT*V$;w;2$IJMs)Ok+*aAR20wWKSE8wj(v)#ndSz0hz zhP|f;t_Eh!o0WzD1X4^Xolrw6`B_|^Gpdkp97I@`Pa28U`YVU6E?40PqN7<3-d^(# zp;$Pw5FaC@PjT8#C<}+W&93Hlv|u0UcwQHOHdFRiMFd`ImkHU6_M(meSYcs9R-r@+ zR@$FTuT!`|f@ZLzp=z?4VvvwMS$6#-Jj+Eb548U*zNNt3qw&?hq*+b53y~%^2QmFc zHq(q=2W97$K@!I~VTy>1r#ZAo7TB7HMBjt!;0cWqkl1<;#WASU*vzXIzv@*|5e|oc zs|U}nTQA~$Iw{L``pL`e-+xH@={FzwDqU6KYNc&+07haQ*W25&f0g{aoE~FjoONqV zc_!!Ro$%Pki5%HXQg$mOoqp2!YsfOPC+^M~iXW}Ub{ITJ5Lv1%OZ8Eiug*pi-<3Q> zzbR^vb((OXCC*=`Kc1$Mu3C;tdMwI~VqrzM^x%5(ZdPu!*YqnpMF;Q0C2OpwWR;}PE!Sp#?IzIwopVMo5YpSs_P53^p` zL|e9pkH<07XK~?}3n!6Z;;ceSt zmdd1Tx5&+AjEMrZzVwgrzs8JyY%qjd7akk}P=46u*R61W$EFzwv@( zx~^9J2|*$OB}D(#iT}QG@6Xqru^JLfgb%}`_1wXnom*M26l)a@Oe9y$oIJc{WRvwN zc~ZKwe<}U?N-l#E;^WJ|Zu#{y&T4kLoC=JH+GjKvR-1`A_jqdMYUOKznQO+drlT(z zKfr*k4DO=1K4N_6Zu~*y(+zPpG<}^gtc$+y(Tx2A`C^{kqdJ&VJ>;z%W#)Bx1lVg^ z)lgaWEu{8Jv$H3hy;EO*;<%&k4|x;Ca@j?8n^pE5e9-uV%zz&lRD4W~<{nx`*tTjt2?&x5^Ttqw=Jkt89$ZHc=WryefYCjS}4aj z?}HTActw1Dzgt|ZH193kE-Q48mrI~j8u)g zB}~)LIG<9AG@bVk&koylB>|jOCs4b2`SG9gS`<}! zZQczUwkyV~OJ6u}C=cur0C2I<6L>Qd#FDa$6ONqaZaLVGdlq;O+Ai4*7wFdk7;Ww-ldg z;<`oL_nfHoF~pXUA|etRn7epvNBzq2unGi5YQ(QwuG3SRYvat8Qz2d>qr{0GVTr5j zR{E{^=)6GM5HG!`T+76k&FeqmV3wPf)qX=6P6%RSGMRktfk{J${Xv~?Iw*W7Q%APs zM8Bm_2Qm7pEU@bgD@qeRO*J4q=plCZOL5&0qVL6gE@>w>=^w2ro6Ue6T+W{BYX;r2 z<-=-aJIxnMox)FF<`Q;H9zN}+Jj}$FJT8<+$T}n%s*V(E&m>xqg`IJCV;JzCV<;7! z(VVal$4c*yKwkx0+Ox{xJIYdU-<$grAjcanK{hpEA4r&+?4zHUW4<+MHrlyA5`XEtu49Z8nZ9MHoHmQ_mn zhM&S;%f+~uuF1$gr-AzL;5W=!?Z%mJYsHM{nmk5lKl1}fkd ziVNqFtr+_byj1`2@kw9v7d3)=h9;~x=F-U?f8BGaig)F3 zBD&v7?W)+~{1%j&;hNn1$u(Qf+kDjN@*idQS?xvC4ECDeHewz?G0#he7 zCsstL(w{Plu(!FNhXfJ{0n?Q-YOzcW?9qza`gYz$;4N?dn;5|r^Pj^{3o7ySB<|3Z zEr@B#4pRlOCC?#z)^q@2e){xYUpE2rMRM>FXPmLC&x`~LfebWD@>?Un}Q0R z?W&qdt#z$7TS909gY%<;1R0EY_u4#ZKbm_*{+%P)iLAaOC~5h0SR75Q&;)qgPVa?` zZ9N38VjZfZx+7Pp3f$(HaQ*`@-RF-~@`5qmpUYKC?;y$|({-2ZH7m!Y;3P?}1V|Nr z9~k))*ryOY#+AV7tiAXy6$Md1)dc)WBQk3wcE0FpoZ($wnpPSit`ZWuksAs`LDecg zxv-R{K7o8qc(^UB8Qw}AI^Svg|IfR_?q}RvzMS;*8+a4d5)%Lyb;ETWUZ35T=@62< z)+fexuiom_)_+HqL6$jS6pv{-RHk=BpbVmu8sz1yP&{&Ql&7WTN=UFl{1V+Cz@g@f zA-19zIhuL3gqCAkeODGm0{NwA&2ZARt1MUhRvjtc3&0oxC$nf?YKx+}=xKB$Hyjs8 zk2y4EZ<$46>CwdAF%pwlylk(R6K+3kB7Kid#S|HsZb#EHZQt{pyHql@7-$bZ`^)&b z+h*q)c2gh)l2E5jq!o_l)0$@=t$nZ_5dORyJ5#AC_B%O3i}GxQgT)Eha;HOL_@Sa< z58ox$8^vTLj-dcboJob(7@V!6cBP?hs>yC@N@7A-8qXcNaymut^z7-2ke|LB%~;N{ z{z_oDy6Rz}RXvxws@=lX-o}H(m_+ONBFu2nY>To&(~yXs&)R(hm4GSz6sq_3Hgk4k z`W22YT|SY@aekU#feXOKrZLdtD21 z8q%M+|*4(yY!krPml6|~uw z)m0l)Oznc?xB$BSiUnir_D8J@t=;0CmK)GtNku+f!b=`}3^Y*Qo`}>bjx6}r5P0Hp z%OgFR#L{`XvDNk|EtHX1*d%b8)Z$fYN}M4#FsyOlSSSu&DbGA>UP_5x__o4;-3^y% zr(Kbw%K`-}SDT*jyaqc5yjOqlOJb{Vsq#ijkD zdbo3n1{DM?mtNfSu@lMj_bG*ufjZY_T;58=wiQ^#A*t z#Rsm{Xel4#;sN|%lzP5;Weu8Kgm)a7#;q?5M4#sAkP z+-l(a-HGG6BM~R{ELG2LGxs~c6p&r0*#y7=ZRo-qG5UD#X-vAjiftLabapwcO5gm= zN#qbCEaf+FmJ`-O-XTy{)OtCkC!82ONnu4Ur*PN;Gw$#G&-2w#V_OuHeq%dm>dxYKR=2wrC_XgQ6j z0p~59{5ydPT~j6h%HaL9QmB1pSWM$T%TW5`b2O5dmrs+bmIOEQKFH4fqx_}GkJ8^2 zF*f!q&`NOwMyTm)8)&apr}0^XyDeW`K1^fmWy$ngD#>6a?tcI=(4lLyXHVXa8(B)( zBx1h=bYcC2cLLou0ZjJ2Bp-PvSF{DEyPf-jF@{f-9Q^L`R2H@(G24kdD0b23i`!3) zJ5mbX+!TV{PlIJ*Q)v=&iVuw6xgQ;=sULFTQ7wH5_z#e@UeupabYuT&J9ecvU&+cp z34`a$EESSc@=T@u2e40;T#ZlO1Hk=@JDW^fR8S$dp&rBaH(HVgba&U4xos0<3T${e ze#d`X(*BLi+dehjmWf1IE^eQgp(?KUCMAfZ&^!mltdug<2!BE|N*mu4dOrFrEko`@ zSR*XHK=NMqX=*v#N**P1jl2Qp#&$byd}N0v^4i`!=SVAG{ZFx)zzIvPoXO8Z%F*rP z=&Dj8^AGe9lD@N}g(HLpO8me(_jd^(%sEv~#jh`<;Ct8yI{Awz`c>rW{ngD9^=huL zxjc)={Lw!^3Z&qH%OewsXP4pnj7r$|;#XF@=hA(V1)z0`Q69^uBEBo@{@zA`MfZgE z9SdjFJBVhOW|Ehv=$OMlZFtbYm8b$(GCtN1aES5iv$(0MWNM5uxCkIygvUK|KDDArKeu3 zc|9t)eyC3eCKdtk)h@^`?>ioHav+yKb_U9R@zQ83gR#`Kh&`=SpCDI0f0+F)z{YkP zA@r56RHz&2`DSwCF_p6ePERhx9l^DtUt~!kg>Oe_e=U49)79%8E(BLUP5?)64T06IDW_^6dFaWF@v%8Q5Z4lSzk8?XtqiUC5)T!s|rMJuvUXzEL+xIpC(h zPNR@Wj91AFs&x}Ih6q*u&Q-m0X)xb(xsh081g>N#?a<~VIo=HLVJeRM{kA_9JIqsV zN$#;0le?h)rf#o0k@+Bnv?T=WU z#!<2IZ)uD8E;R_1AG|;DgdS+vG`HP`x^k3N7%@zYOAWg0_v8Y%tQHje=mq`cZ(Unw zzZHydgq`|_3u}Fm{xW@I$6Pz6KSzEf61KosH)_Yq&5=j@rf`Wi`sxh*S$5hZ88)wF zb6oj9t;uLvF@A#S{eenkzJt8W7trR>M&T#Vl>H}Geg-&0VzSuRhv|!!TW(FISVBJ< zZBJH<#82_zLUcJb@~V!L4eP3k&zCdqCrWbdnM$)KmNl&nW(i%wgz+REZ&k1ZKj**s zPUXsFtaKr=-VrPgI!MsJ&*;*A8n!vP5tKA&afJXKp-8VpgM6o^g27WSD<1BTH=_0f zv@s?D;jz^Zvrk;!`HLfj>ebi?M&ZNmdsJqp|!5Q8^Vi&w`k>x5N0t`J&zdCDl+RQKgKHt@X3@#|y;L zk4$Z~3Z*nkn^B))uK6w^V1e7tvxt+@sp_7Ib;q((F5)EQhPa-FSN;buaSar{7WBIR zb1nLObnkM*$(G4V$XzT7@Sd2e?Mq)?bwAFdz-gWHuX2d}$iz)E?5ow|@=Z$0B&h6aX=CCeoTq)nx}Q_vp10eFQ7J;tA>g%{2iRRKe8F!0}4&h70V1QVo_4i z9hQ5XW5`1XKbDN3Xnau66o%)92WI<*7w79Eic!02)b|$1yZV=H2GSi<7B7bnakt5G z)UgHr?{K-b=ofORp~EGUiAMxu9@rT2pA#0d3f0oKF?K9dL3>%!rUI= z5x_$ND%wCp=YYC1_1>04??0iw6y&nIssO-J$x2L0Q=EFd@%$@()Xfy`{% z*aV-@2F%1{ObyCng|r6&1YLa)wSPMa&E-!~W zZzinUjGvKm0_eDSrCm*5&WQK4SMJl;TEUmRktasMZYk_$II$w0yglQ}WZ6}L+VjfQ zkm^wP47D;9hViUroYAIRBF#XllCN~3yv0f>a&3yO;HOEJwyLFMidlX+6%y_J0U%*~ zSx~5MVeZs=LQ|N`7qJ%E1vUBGA^>eU?`R*Q6(6Lbut#`^s&Dgd1mU~>$+`XFgIhT> zU#)<6H}xN&IWXD#;4t8K^SKFDn@ZZV-CqP#{a65lS&~)qYl5BenhGg4*F+sdXD)VG zjVluJ%-oFjXXjg^XUDz1O)$v&#C1nzT<3g~e{ofBYH zVL5ekqhqx0$LQpd5aeG0v|dMgclauQioc@`iNW0Nb)-hm+#EKmlFFt~G@uUGd@!tO z<$ktIY16$kQwXw3^6NCwhtF37bX40~zV6utZ)~EGXSPBm4b;8D(YidH-((MniANAB z6v^@0?O4Mlkw5-SU@A=Tb~umfny|lnQ!zkxaPeJc#FU@`UW;}5iU$r{Xcwu<*q9Pr zu++v6u1YavcbkUacx~~gm|Z9@CeaJ5FdAOL z*vp!)^HZ3vB^4F>R&J|7WVV&pVD!0ymWk zOioaAX)JABBd`xamG@0g0&`T!e|7SLwnX9h;bh4& z)(d*Hv6P)c$6Cr-`c7Z;+m@aue%KT5p?oETbc6u7ahv#O&1UE0gfE}2OxR|`3oCR3;*l5PzHRxht332nZ-ZT zZ;TWr-g#aw_wWQZ(ANCCeej3O4{sAug?CVlH?DE@SZNesy2>oht*%<1_}~I|(nfy+ zOV;3uckOFzwc__RtaGOP-j`4iqFb8VoXU02THhF$hl`cco6eJ}pNwcJC3os+yJBB5d2&Jk&| zOQKIQ9B{lv@3CYQAQvnhakO(kyx^e(c0^HsnN~^_z$$ zu9sf~PusiS?m9mMy#GA3AOQU94i+me46$Vv$oQMNF9w+p%nKYSsz4%19P@hQc=p9D z|6h%nLt3fD4vB8~pBIF#Od9siFC_Xsn^lxS-75a8{qJ9iZ9H3qg{SeL(|7)|fe2Lo zUr{;S?iCq;2H#b>7+gfS@LT1U7XBjt3ViNB@1aoblR5r5$4MhXquD5zCPJyN3cL&C zV#1o=agx&X_dTTsd9ljfsA!vgnKqbSbj-o-+M-jnL2$CCeU7qOyKgj^ zo`d!i#~8KSMjhX4^@bCoaeu%q#N$Rd+<;-Mjk=?KXp5!Z8i;ERuXfl0Ij9B37CnP5SSp^V=T7UAS0lIaMf!f2ouq5f8~Iw-c?z3E zVRs~>gc;#t53RO0-bm;Vqz+F}^m`4_;`-NxM#QC)DwbGAiQ@Plp-UMUjoE!;HaoZ+ z(e#~Bmt(bK{nDgsLFVs0mKyEE3U&GHHPC3`vDv1fI^%G*g)i9^MG9KREpS%JRKB;L zV5t25vYpyI(jZ4P?gAIq0lJWd32-e9PCL*Lis!&Ubxm(ivKSeG-Ryao#?pn3XcGye z1Hi|<@OW^tvGL<;#QN(;xc0+iYmCwlc33RhHkI2R;@wFF*fLUgTeOd7_+Tq6LI} zhYC0q6;&LYHP&4w)zaf=GNA<26*yV3ZV)Uh>u%9CjhTZpX5vSEeAlXdH=th&wQ1P2L;#4(T)X?T-SK6|ox300 zlAi2D{=ey`v;MESbIY9LLKO2Eb`Uc#*mPN|b2vD5KKgo%u*&Y%O6c3#Tw{NRd{!7T zc9-BD@TvY?9+Z}9;F8~3{omu5mneb$--B}oyoJS-Y?@%Z%q)+ym$odq^Bq;!^Kb3a% zOLjiadb-vZnLQ}xB!{C(NCuV@vri7nbYIZg3qHuc1sV7i#eY+yPC_~|j`q@#vz*jx z7)8qP1h|cJJz^*saQQNxWuk}HCB+36=-YvUdgaYyq#g{#b>YieA*+T*?@`?<+^!3K zk)z4&Y_|t5%7VAVxEDUAZ$D^+tD{NE?};%7jQEp{u>Hxa&2n_)4~$`*40I;%^v>* zRe!gPhO6Lkx}A+_aF$ef|8$lsw;ukjy#xbq78!08eAWf+0oetXyV-lGWc#{~)Q0S- z;fS8WUwiqst{WhQGyhrk)d`d=O2*z6BU|}iwiEgM3u`r?AZmb?>c;zzY@ZfnnoLrN zR;VmXn2gzXo`uB+nybQp>Fc6ns*Kn-x93#g>+Zwgsfj}r6zJyvQoXy^xD9HKh%&d7 z4u`$nxqm%BG5A_AYOY@?J>JT(-e%sXQR}H#Ek|l}ITDvA)<`NQqBARweIHnGuVX(# z=w?I^%0ai)NLg*fR*~#fsK?5$mPVs|qApBuve?Wfe9QDE3JELl2PLRZO4VXfQ1zO*uX+;WrvB7l?E?QT{W}cX-aX#^-K6 zzX5y+*UZ3C{|GFORYcALQxv&xjItq;Zf*9&Aq{G=g*b5;Hwk{^IZfba;B=Wll~6nu z)%CJ7Ti8Y;8=m{JGEpZzt$Arw!45Qc%Mn+!`1;$w zxg5m3Ddzgb>FcCZN3DBthj*SWHi8Ts<{PW3c(?YyGWCXNK1grX;te!FPU4vQz=R$) zRQk2`ptR59*x$+|d#Y?!xMKQwLUxew`)=G`F{Mo<&Rz#ITloJ&w>^oyZuw*TecP&h zpx_I9d`f(npLE`NDQm;U6@I1gR}>bM(K8xX5qy{FMd!-t`3Zu9!;)O>K`K3FvR41r z_Ae1zgM{(T?Zv-)LyKR9!@L6ySes1JEkG>aS@I}6-N?a--@dc!mvk+^0QLOakDBY^a^2-rJA4G*6xX9#}+eWh9KCVsE4^sq0^`A>kB-!p&(e(#KPSC5Ocu<1YO0(yl zCEk^Y4hF%tV$-nRJGMm#SplVzMgh zy;U?(4jIq;ZDgDHKx`WV!Tb9HVv$dt$-^pjmO2F;INvAx_U-#68;T~C(_6(dp#6!> z%A)quY>*X*uwhk%BEyrX4&fB8zm0@$@t#q0D-QNG3aWe5`7OzP zWw;3alo(WSbn$c&FdO6EcF|V&{I1v@VH6GSQ@k2w(Cwr%&zO6`D)*O7PhnK$!O%S) z5Dn0pv!&un>6QZa)K()8)PqtRv3JWXm;hc!Y60s+!55A0DAs(?^Xo%fUI&%h^)rI~ zF%hkrnewSkRFZ&RX!J~<-;SLRLo#uWy$bO!*!LIEoFCU_;7O$CibJ&2y;Zw8aq@Wb z4zas$qiCN((aiEm(E8JyLtph_8)PohQVhqx4@+>xSks{Cs3;F^X({+ET5$o6M?-R? z_s2kqc$GpsDq5-fEu(aX3yMe4Qc=n0$3zMKDc|B(t*veBUhAexQ;CZc#;3vuow(zN ziDHKTPvS)`W%K>BTFk)oOz(zePbk)5|8z7F)lOo1Z*Tn8UFceN%d88Qj)pX=~G1UtaGu(;jsD;^EKRz@yYI&cJn1QW@eNL!9mf;>;Tj@tHe}DOfgkFGO7&&^OCEd+v`M2nh`8UGMd{ijZ zEEh(k8`)@Pjf3HE_xdSo%|;gc4PX`O{e1W(+}1B@CC~HA0`%fKAB9+LWl3OBK?+t< zNSDFT+I-nn^T~^P>mX-Otq**e>q97LU&IK{K9c8_v)rk-KotF?LL@K%y)XrBN>~e; zDAFN#REPg!8MO@>u_$TD*q{tcPG{{S<8Sorzv(|!)o%&-rKkCG1upv*U?Q9|xT?P% z+$9?1ix1k@73CxD6=PUFB3_kDAo(mw%W5Sb$Fg2TvL!Thv^2d(f-iU{=ZVGMgOTq+ zRd+4Xb7G?1c3!X=e;ejeT_4wj0WhpKm4@tPnL?g)!g6Gum^5SM9&)66>OP!UE0#=F z4kp&Kyneh8FWwY+sAE%?F})}msPhcU z20V)$aj3Tttd7o075Hzv3;`AWRHE?T1ZBC3;Mgg5uofz+l+LO8W-X*RL{@%L`>_+U zANe}wTFoNjh&_!U#ZR5_QPLIHG^YU95(q=kPn|oVvtBWC_$MwA%5O;yeXUB0XOMbZ z$k(tC0xYVCT+DpVVY{uw!Fe8%5;t*J@7Vx7--o zHeTw<+7YbghaFDpRRBSbi%AZg;k=@0gH6_m$GFzl*32Ql>Or;9F(TBYhAsiEjLo`d zQ6SRc3%te_d)dO&r>H{bWlC0V!>CnkK)1CWgd1f%m2ou-@p{M-mD}jY9&-Ai*&hH|)%AL`Ws2E)y2pBgCQ$I&QDw{xO$Q2o5lbJU_o`aEUn zvWTKRMDpUL`lTpf*(As&ld!OLT=e^I?UTO_&bzqmA4)>KA1SP=+gbhi{wo*h(*M1J z#@m1cW3)=nId}3*QiB~flBaTGBT+^B>s{CEVivP3e-^UiFJdsJa)qBeGmxrRkK2mq zOx{eX{{YJWBWMw%;v_^`O`G2#ssL96#) zCr7z{*KTv~^rW#5sc0+mHW+ZA%J#v@6|1Z391Po%V`=GV_8w^SS14LG5DZu4(nlpv zp2s4eM$w7wO9RJ`fUnAVpC@lt$v1!AhvICWPN(GD9>)s!YF#CtnvXW2dV2Hyf=zZVIM7g|(-EOBan* z$Jg^6Utz6;!}-HN_WQBauicx@uRXTR63uJ7R)-)b?#e~BA4TK36gd9DW7;unWhaZe zG!CWX#FfMrFy zOnZHcsSDJ-%h}nCg>rp7sp#GYoIiIYgO`SEhDU5r9G;b+RJLr|R|q?nFBcQU5pdRNC7&ujS5Ome-s$M<|7r3zr1!p?H+XzHeCMBh9o<;g;N*ZgZ36 zQ^&~ux1LiTa3m+ckTcK}TF+kUuSb8f(%g_q@&I_nJ5YuBGHcQ{j6pZTamD*f^bGW? zJYd=n3UqkSO@*1>_$IaZUPuk_o3`T$7|Be6HDlR5_wdtrds3KMOuyjXV(XvBzS?y- zIF8~({}CuU6cqH6t6utaQ1Ev14@WJU6Cid(U8>V+9~FF(8PN$pYtxBz%ygH>XdJ&-TWrY=?AEaDac@juu#d?Lj^3k-EHU-0RN*5`p4|WBQXl3U z1^mWLj>I4WV@cuwVPF2Vpum48RLDJOC1(>KaG)AcM|on!-w-UXQ12*;h$X_->ZY?v+ReWn%7;3Yz7I_eAEM$i=)F z?Azuz%h3nVA{*GMeZ-TCw(+OxYps43OgD(GZ)YIJ>5WoaW!>}A0<0=dy74vnhA%um z-C&t1 zl>cYth+E5t8ZSsQ7|x~N@J`4~$y@g!$bn?Dpa!Bgb(|l~R^Q8$MUkWbWZh+v6Yl)z zAA(?JN`mjZ$-QCv$`8@bK7!AsC)8-BejHH|s#S)8``9KNy{FfRb716S1<{XWaOG3k z!_k0EsD8ts2rS3;uXQY&%T(hux})A8@=uIF{Ubju-ttn}OL060HcfnIO&@dWk?75^ znQ~@1`lxYSTER!N_6TI6^S{P&Jyz-_dc}uhS|9Q>wa}rbw8dqA|Hye8Uba*E#VyTA+5C$ic@ROq{K@df&dhcWg%$?@!9@GNE;Fyv>EvRB%T1pYV$=Gp{qD8MC z4z#nUl_w0~1sl|n^Ua=Ye61_`L2nQlN;e#f%FAY@y3djBkNvwoIpSNzt>S7V;l*ZFl(0Dl^pYxk*guKuZ3D}>@TsE zIscnL761PesKuZI4y^ktl?xK0inrx!-v7?mPT>&;bL}1%bu}YfY!)=@MqK{^+NBVf_{tr5SeYqFtcJ@DN1J-1(>p#r%m+*(ax8NWat_ z=J%UcOcnU2gSelQp^TX=#{=ceQWg* ze+=!L&qWH9x-X3W0?885h=h%q$1C#QQWHl2;^h$tdH9-GBzkoY@o@_b82 zf6S5qh7R$OBqN)CJI{ca^sDY4S;hmS)R=)bcBM+!slCKwmPH>PoJeY<#gDO+^W)b# zP)RpCfT^3fk^}&`>mqS#Vo!2&*%Yo>7(|q@$Jv6Z7BV(ld%CdWP`LY|- zj5}4sw`%v^E-UC0FINbOyEe+r$TuN(Dr!g-kY}SU;hCkg~xR1(rXuKEw%INStfv0Ah4&GF_BJH#-#cJ)IZ&@X*McJY+ z7Qv+YA+2KapwGZAEHX(33nR;`bla1dyKx^1S0NqmGsxQBV4LLg!D&BC8-+^U>E;ig~W6O0et5hyYq$ zWV|ZG+3O18UTo$eV)V5YfUyr-iBnBV{{irRjo;Qs8z58JJheXh&T14g*9a#Q zYAtjG209f=?=8C@1BMtIfP3=|cf#?OW}odB7EaV;$F0ACTK0AyqU^1YrVlg-sE*&# zuC@t#f3CC6)B!S-h2W9B4`L$ON983w`P|QsfLn-o71i5YB|{pni}1`E*Ia4`ShB=V zIpsvHT6mSDllgBfh@GW*gw%jb;iTIy;DbnBmPnu{lnTTxv+@z>!tAKmIY5;Yrmy5l zpC+$?TwVNbrCUThW??vpo6|+f*_dzjI~D{wt3mg-)}TSu@n4|SnKq-*Fg|ieeD<@; zy;Ts;m+gK_^^H)M%i=z^snwPoTBocsVn?}^n`dyhfdZKiJDyfnRgVSybyitP6!K$5 z2HkrQ!(f^a$s=J;+>VZvtid1azdCcjmd}{|IYroUc$VE2XddEcB}XW`6eBG>CkCL`nqg8edr}v|P7DpOYKz@K;a^NC_94*sndd5s*UU+^>0IWT z2X0Re*m}^iTKmXM@Ew?-iGC@(_wAol@*a*K!HN?yk8RaNl$Wk}BAHG1R%mC3$aMd? ziGU7$?tXlXp6*Spzm4OTA#9|=tLaPvjM z>$CF|==HKpSD^JorP!Leu4HS`o%+Y>LTW+hx6%om4oWKgM3AZpGwfy&j4Haeul^&! z8Tj)SpuYK13oMl6S*5yI|8Jn$@o8!@M3n}%4g)V|MP7L6ErVK*IBH$^^rMQ}c4#w>MF4psou0rmUZqE%6;qK~hS%TV{z z_Frw9R5$lW1>T7o=exg}!(-g&yomk-xWuRV6&7uU!nZPBM=`3tpXk`BN!8LWWSM*UiA~?ATA{ zNk3rtkDnuKzK;q&fm{MlH!nZ9pXiV_1esr!)TN8>oIm&p+0Si;4_H5Ya&o<7e22@T z%ynGUnej4LFm(5P*c6|bM>)~A27DbQqDBem@$qtAOv&Zf+5ttzW#z=X`kE&u>JCi| z%Yk3GU-F+RR_V^M`-{Qo%~LL}C3*C--lpCYpHw$Y(g&B8IX(>_ADOh@`x_@KR|^`X zi%8rvUPVOB*iDUP7N7H-zSfhVe$#(+{DI(!;}QPyuoeSUgvy@hpB}jdY-rGw1iAVj z{oM)?44(nuev5u0yDw#kf9PJ^HBQZ2I;cj8Oz6BEXoCbqx|#7D+C?EF0~hyopGd@a zTUoUf6vZ!Q!|r36VY*XVT!Xnu(D$;ootx14oce*E)4uS)a&KwZy8g_B6=my3o$OkA z-8?9#AkDcfXkRqWMfdS{uz7Lr>MtQ*`MjKgxt0SnAN$aF-3vOSKm8I*CVM7e+g3dT zoK^L}4<;YL8Dh7I!gakCy%o4=@1y|p^NZdervF}dpsOfJUP;khu2$_LVI}t!%k3U+?%xgpd90C6B@up^;5gqM)09}dyn!B5`%X81Leo9vCL%u#l&yiw-6rq7!XQtfx1TROi>Xp8biwLvJK4GQ2D$|g ztz~B`wQyPQF+Yh-XVj>h#G_*4!2P|-^8Wyq0PLK|jx|K?d7-p0X8N_SWd1IY*TdWT z^n!>|yu##BIku@1k$+bCK*8l>0oU{@X_)fyj9*|YHq}9K7(&4R^|xJwB~H^S(1fUF zr}Wl7wQQ$nwS%|*;f|+o(;Ks=o?A_ zEi(jrSTkMA69NHtlV;IEvU`THkc%bEGULTw9L+)0l`f&10sULPI2oZ>ZtnO_WX#y| zfZ|TqR)#?S`;{**<``?X0XD-1vet07aBuGzO+=1NPTlvWw%$5MIeNkQyT>(O+3kqq zC%sYBjJ~Q-y{Kzai2`}HSyLBhS=CBXzDHiwF7W5^I=vgU1_89vhS((N#Zq)01&>_= zw}D|S{jWhaVNF3l+OcZYI(Txj%C>6w-Li@8uT_Eixr$akeqgOI*`#TM^pM8) zeK2C!iHoI&oF|3l%~qC|(20Q%ZOWjT${DDHe*nwJQ3~}p3p)QuTh0rZ+VG87$sEpX zihQ)gQ3iWKh^!$}f|yTkB7Lp`#!A0gI!gRC%_WbaQ)DZ}2!ZiIA-Ix_x^SxH2aOr3 zh4M%iqP=xLmqwt(pw1J*RdrL&Y{sbS0BhzWG^_BN^P3%3MmE2XxG7``el(3g81t>D z-mnw<#Ghf)OYl}i@gn1r{DkY6k#n#`#hb>I4^qpemRY?-n*QT`1V>{QEIVSVo&SFS zhyxa&kzF5;J6n39%0VHZc%Qdr`gO}EB=*58_My(R4_4Xj{W`2(TZFUTngGGsZHw*d zz~u1I{-HU?g&o|3B82lFAY52G%5Xz;r011n=2_0WzS&**q-EC?rxO7zKn)8Z*>6TM zb5KNSZK60$lwdw-AgRp#%WXc?}oS0&opt0GtO<&u7m>jMl|NqAdaXgN3(B(hMv&#y*>VjL*YeDH8+$sa+U zh6j>0nZdy!KP!0t-VzSY=CY67k-TXdKt3-nrzFSg8{yod?87D)sR-MK)0t-eKt;wr z(--z*?QO9T5M=!MJJKp#{*>MG0n8s}tm40qZ&KD<{EiW2ud&P$WaH5V0*h&V-{p7W z`r*V#%`OHy!9}|`|HJgc`xN`~_GmK`E!%Ifyo(%{E(_s=F0Nul+XMi66JIvIxeSU0 zXji6e$~H0ouY@PoF4cKveaP$ED4f@f60Kq_o5{JOrOtL!AK6u+`~EOW08ZdCDvU}0 zIdgJqZnR@x?oIL^0*^M=R3Cn(4KfSeVV@Vz%<$zH! zYbK-yQ6F^t9SyY7mj4eRZaYji!rVqI%6~IKH9$J1O7(A+(mLu6y1w@e`lGEXonn`k z$2Snk@HU({`tXW++n~C<(OS( zYE3Wes{DfRrK|icFwFWFU(dj54Yj%FIU)DXg3xgEqEfCe@{lVs(m*b?;_gMVR$hPMaAbk+JnL8S4heVz3ypN=vV=3T&|_`ISNBYQ2Qt5oCY z65rW%razIcW)7&VRMz_XK3>osxSQj!OG?H{%ql2Wqu0b1LyPGssU!m$aQ=ydku2sm z&-*Yv5mZX8MmM^7RIuwy;fH#bV&RU zz^3sKoqt3g%cXhBXRQ#(p;1q=H!c-hiZNW-#eop+>b5EZryV@UsZV1!e7n@7ejnR~ z@4s!TVga$~XTQff!RI;6Y*eXFep1IkzK7!UEKkcB28!EVk!Wrg{ceyuxC+VAPZ_u> z+==5{rn(M&({dI~MU~iXg7^GoqgYMu7F%&waK@TS!F3WZ%OfUfJ_la+2&ej$MVP8_ zg>bX-RC7Ci-pA^l&;DzjtT28@@7GI_ALMB%N+ozhS$awxDpAWU2b&w0iQe8RoMv#X zQhM2qyum({6FNkz3`!Nu)e3#{Jsibh>8@sp>ZS|UsZmVrea-2U4Q6BFxqIq17M^T1 zvY01xBiV^ONkNy#XTAXtBr^udc#L`h>^3VGN_zqr8Z%X9>ujaPlO5moWIdm7XGh(j z$8=tQY@drW>kuhN#ej*W-<^9!Cm#AGUzoqj8B{**;J$`{g6lOHzCvy$>b%x~2qK$D zGlFNPhL{xm*qF_m6hA2IDoJ4;JKiBPJf-VxQhmMf zojAm?0n;LSl#im`2ZDqb_yJNZ7t2G;Qjral?E zT?YR3E9f+>JEdXCAE9YM3j8L7}YTZmw1M1GYN8xG< zriC|mS$UjP1%=8xKC`E6Z$phdP|ZPzG)3_subc6gjX;uz5D~lErK-0RH zu?!0uokau0Z|H;CHyWyklD`o)dIi+2vNYzo`Oxwy&a`9;QJj#v#_Tfo09i3b#K35Q z=jF%Wv#f?25fy>ma5zL!^Vi9CNdBH!O~#+D&eaNF8)WLU@e|>C?34IPf*Cm+O}sW_ zm9a;8rE#|u{P~CfW%jTTE9x~6!j!MOrDukvh}^+Qc=}e}iffrK<#V;XOJgy~v+BEi zC6gMSkv*rubt*ugUNkie12?BvEGs}rLP|25o(T$tZiyHW1`P^uD>0_*VF}4n7KN*> zDh>nPvU{BeR?C-VD5}xSI^!fva1ipKase>XT}5- zL}P9vhw*vBw-WUogLAdaAkLcWn}5Q}%gO?$bl*c9_ilcoaag#o5HJ8lpS#Np>gf#n z0UYSHaKP`jicDk_j#N)lx`e>)O2gj7xW85Iu}FY@t|LkOUUlZ}7M!92=Xc(Zb2$;) zH4avzAA^qXx%Xf)-^%WxtVI!`5Y3BUbkS<|T__G^F7NjWn7nv%#TBb_tRT3tetcf0 zw}Fm&k|RJ?P7L@$=Yg?1E^FFSkR;8OiR961K-&h-CV9DJi|io&%(X~$${zgwXtCWt zL+y%!BMGUCW=|s9dHcpmVfxhwS4pT4{Cs3xoI^fF&Rxo5ejhclr>*a^ioHL^r7=zd z=t$cwM=LMTs5)~NhNEu2m9~kYe?ZAJ4Q^d0d|2h3pBD~s( zCpF1rH&(wOpEep*@!3ql@MT{=S|KkonD#Boy7kOV-JS$NrAp>c{X>xjq9u(#3112m zLX@kXCCkrijr1q;n_DQ-j@5gO8>?FQ>3&$L+|2$e6cmmZ>(vu>EwG3e$%%Sd(4d%C?}2A)a3_3cmb^1O+Yef|E&*f8T{?q9hNvMj3O2;b&# z^r%olYT1J@Xct}=KSzVg`^fYY%_^LE@>e=?p0ifdyOtM_NkfL<@u{ZF$0Dt7cPglX ziW|j%-KJ_@v6AZf(0)~1hPes1d7BE%FLS?ZMHm_6pKvx?L<|Nw2u6edh`=^mS?(3o zmmihcS%9;f!ryWV)Y*mSl0re6e${I?#rfAW*96TpgJR6X;+BuoxTFA6D|nmEk^YF4 z?YpLF>vijBiEXPniDP=7{{VUL_PUp(2DaEp3b%B&f!8n-dhfhfSqJmiZk?P*p`#;& zYL$_;ZC+;tj1XV)P4{2bD`^ErT*=9WA$qVI(`OPd403Z+kgy?SK0I0{)HB~1RZOR+ z1>n0rQq;CUQK5pSmmFEBCShKOhLi{Sh$UKO@Z(I5Q)Bv+Y*w`vZ5;aU9}9b?uVd0z z6&Ty5x0T}gj;FqYzOJjXLn%H#@~+jm8bf>@HLImxVO?oOlz+Is#C{F8v`ity&hCYA z4?thFtQP;Wc5lw0Qul?AOCYP4S5J~P#Il9ks>!kE+VQG`lH>fcd2%2(N6d5$VRQFE zegBY*&GQVR_)yZ`w^&i&2$S6$rF~U7crQ3a2M_7cSpVFXbsQ+-y?z$eT`-AwkV1q; zj(pWt&Dr%u)1sW3zkES~+oPV)TdE-`Q~4h*&l%g=j$;`Ggyc9*mLNIinQpj1=l3Ti z07)iXF%B$j8|mLH)e3D8403V$W_hnodq-r^E0xwOtDIq*@qtcz^H@lR0aNaxsk(*M z)AsfYQ&Pe*m}uslBNl7j_M-8XAVlQFZujv~V{7+~tt z`EHv8?SFGmnMN@MnJ+|gnMf*<+ZJwmI9KdlZn)_UrwI5&A8#X$?CA&A0hl z>YC@AsG{12?FLN5sC?M(Y#@>8v7bPl(Ng#zH|kb%qE2`sI8^TQtJX@r zna%bwgm3B~!LJ6dk*e3@`JjPEbRO_;DBTQ&>JYtrHet~U2Ug-X zD5^QFM6vTt_U!<8FGf#Rr+UeOH5PBaRe(Qb@A%X@wz?k;!L*^j;EbiV=2>Lyez9@s(`^tDkoBpc9{Wr=iPS!}Vn2{FYQDXx7M4!cx1Pwz}+rEhCr}cbN z0&Z`+MB5Xvp6^PqNlh64PF&Lvp!dZ|Pv+WrgAUkH0W%dyw9-|xbEVwy>GvBYG%vr; zgV!bqisTtf2|br8tx390o4xcji~vZN0SE9pmZfDq1i9An8At4`&a^($UF*G;;G(L~ z$2xXOr+evoKAN**o2Gn+#w8DKiARy=v1R8#;i+1KLM-U>HneI&v54)v^Fy4{uKAg% zX>>J8zu-!wEt;r#(U{wpUz?fV97N-n6$uGG2(s%u&M#5nXiH$2+KOLHSD+)TN8@%D zehWF;B)TxR>|P8+Ui)pq{Z}N%j)i&BXS~uK@8s9h%-4&XEcTrG(!d?-aVlQ#@p3HX zyi9UOmjv7t5eQ_S>h!$gbC=7~DdK{5B5L*Fhekt&qrgA?KfyDxtnL-WC;c{X9`Yiw zVOT(Tdk)Hc)%P6p^((&DdG(bA6sO(y3DBSBaAyPY!mI!-Pu=nMthvszd8Mab z5gp11@K~$$d&Xf*5R8ywt}i|~b*%Z5EZS1+;42V1IjvB8&CwsnbX9)m$e2eqC1I05 zOE}2_!Zl31pQBZ^uWr->*gJy)m}yd`7-X?8gZo@&TlLo7WiXSp*5MT*$+AQ*4MH!U zOBUAu4^w9u)m9g+YiMyOw75fY2u^WHffg-L+zG|q-QA^l@fJ!c5`w!FcPQ=_+zA>W z(3|g^d&juHGm^P%@2s`vJKrZ2bRcyMHMeA1J zXB0@p=(on2lng%yRg`O5d-Y(DLXk?!8JmmZHt}N&H14%_ZJ#yGJs9e;Q6`6Lt5^Bk z+xfVyX}Zd{K>u<_WuUkze=~s)la=_2&lYcon;czq^3Y(yV7X79B(Z?|18ir1m*VcW zBC~C((C@2zLLkRj<3@8&r(U%Lo;mIH(squ?dq&pO`Vy(_2*)z9&K{aj(QPLT%w4tY zQlu-vy-eKSE7*xGH|KgAWew*oG%+8NJt7j9P-s?XlN)Y7b>+2n`SQ+M^pfV61 zRMUoz`9yn#wIunT5bYK+GUkrl>NY0@FsOO{xDXxZv5rYQ|6MUdat=a$rZ(}J>1)h- zR!AkU5_%umc%F}ws_=Ybdd37>>F_0pst0tvu@`z!DI(=>wD>Za^?KkAc$O46N%Hvi zf^G{O9U&p^SXQYzN_%4;9~T3tqVY-j!5C;$L^Al;AbUN@?)I zlN+*}U%HC*lOvc9MHsjHeB5h;6u9HnN_Ra%_k_{B#=yu_%TKE(?1J)??hfbs+&q}`&ie&TCqL0h(k?waj#72O-4G3H?8}r zNxE^pb-3g^2D#MD>P-e})TYe4(ilzj4GL=X6-_=fSF#ZjH-+sI{7y5@iedLQ+6*f$ zta_%ubLUSPYbY9IoKb&lEkwn&NV5oXD& z5<9KbWsL;yw>x;j29Xo}gfANg_tljpmXOPFB}#`v!-UPufz@8 zrb_f3qb`Cjg=GLZ%j{Lf_tgo}j8Yj6Ln%mCux0r3R%N+nLdH)=IX8_N*_C&Jek%sm z`P&%6AH-*Gw;Pb}w+aLpkCBdVHLz~E6`)USn<~Lno*^j$kt_&MC@Eei-+Z)c&(YHx zD0Y>KU!G7mTB}s7@%NM{^sC_gDt2-@t38M%j;;yUmixdI10Vc!2a(U|!+ZQt(~RzW zSH_Z({*vPJ{&z+}I!@7@xJ)|1Z8^U0Z<9{fydFP)C;`W$S`YD?|CqoK{UwU0sEYmU zu@^sPrG8|SM8~-q_WsF+h%seJrkuaNx%=Mr$u{~FD{j1@8z}mcN>1E=4_xDRbkas9 zLLZ7aiti>s(rq0Ff1tqL6MnH&spRi)2`vA-=AL%I@$+*npQqbs9?e>327ODSA2m^4 z5N$fjtRAJz(RU$VkH<5s@86tM|p5%FA zvc775Kkx1rQYY&X`Xr_-N~tf~Z`(w6h7HP*Xpk9S=9hKjHELO4Sm z-4jz{)%BvxMCg=qHhZF8I=u*S`J<&;@Uc2YP85T22HGO~yC7Vp=gOwnmNB z^t$S1&bU#z@X|OOO%*tV-{c_L65Z{Z$0Qi#a)DCM1vX-`YY+&d#En@zm1(qExY zsXcrq{lMGMU zglU2n7qgy*gXI39^r{#)bYUDe)tU#Vn>+Xo^aD$H`o>@Iy!O#J!_=6>xt~IvZM}wY zz$wZ~qOyX2ft0@1j?^)ogeD9H(ae z4bORURMIe-UMrl`C{Y!#D_BFr5sx!2o=(Tp_}g#6GNqiL{_*Aiv)d+Psa#%Dv!%}@ z?Iq}=tWZTWEArSDv?akF;f9IFzw)Bk?w==o?~nh=u08C-8l4Pal*PPh7wiZ$E|~kk zpUj6zyQy2Ki;V?wx#RV1)le_@`ffqfQ$YNp`Q|r$OWe^LPKhW4JFF2z8=;%8Ayf(| zRLXID7liIpnoM*|@9{j{{tsnp5@h^5vMnapsD`+Wzo)z>=m%ZCvj^T1{_lq-K_gw4 zSo3hY>vWf!-&#q8Z@ zdJXrS>8ZtWx6R0VIk~L?yKoD(WenGGuIf}#VftaoNYfb&arVh?NA+!rPE}*mPRq3@ z3TN&n-R{i87#miM1HyjL_Ac4x)=ZnX+V{eQCwbw?1~=HOUUD83ZwlMi>JCA_FPPFI zPLlVNsVue=zqj>2M=Eaf|KTFzK$c&WS9Il0?Kz<}VE)FZrR=bx+4Q!n#Ks5BJ z>i2oZM{3l#tcvOuU-eBOT?vK?Z|Gg`_5~!5f9~3P5I!w5%{PQ|bCtOZ`Rj%QIpVE7 zzc0*#3H(Ee#71;6{#$sgeX!S^Wp;lZn-Se&c3zY_w{7c*I zZ3h0hm9OSKvzAUbFuxd2a7|+fAz;mWepfwC?8-FeEnxkoARAF<(6^J8F*4IjxKhbK z9?M9_Bd$!eB6&1zV6|Yg(u|Mmx%PRZQWuqKM9~D8Ycuh=aFBjB<(m?~>2lR=XgsY@ zRm|!~|5lOv0XPlQu(kfFjzL8XnM=(jc%3I3-1!hEhfVB-|DZs_=`#b=+U zGri1d^z|Fnlt^+>^d!leoZoj}3vQ{*IfaRQxrjb&$xRO@BD|#L$`edqHJc)7Vq9l$ zkhq&@>w%5*{iF_gl492vAXIVjw=qOXx!*j-Ej4r(4?bscP369t9%afR5>6Kfz^2>1 z2WLLyR9hR>$KaeECL7%5dZwNTe3G)bxq-Zf!LB^pf$Mr%j59gcm)?*22X>7j*pAcc zdk!cC5!7jL9c7f-lt0U^GlH!Z)fUZ+Gb#F-j)}1Y(9=m|;`D2-|F;RT)hLy!KLT$G zojBe#D*KYEes@DB8hL@|sUFwj{9Aw2u4WWl;Dz*;lzw$=mLZ{H0fj#nc{%!&YYc3$ zSA1wSr*Zk48x*EqNpsR+V~JN0Y$!O=qd_A5SSm!)cy6hhnjNadcAjA!jeOaZsBj#i!nVu=YKMPp`*#VD1*0-ZSZk zVz|d+h&%$d0vhkj`q6l*$gT6h@!U@Ac{u`nw{=0uUxIn={Y3oum7g zx%LB2_qQ_%_rYC_Vf5d$T1$it<&&om8idT*^Hg!k2?~wR&zs08E4p}SwxvvvN> z(L!s=t$Wa10XOI5Q*6nl;xRDz(N+a1k*I_PKcT+snh1Ut1u+xa8o3ql%6yOJ=>mzt+PmA9*?E(KX3m&0(d-W-1 zM7Dx^AMkYqYX}mqs~GVQh3YxunuQ;b7T;6Nd~Br1ZsSd=_oo}}q0sKM=fX)|j+?i) zBv|;_nifD8S2a^6{xne7vr@Nt9PUq&E}V2$`^ic8ON!se%aqG~rtLJ4N%C^V0QvsM z>EVaTe<*{1-EXFGYiO=`s84nV`;cx55`B|j6u`W$vGpj&gkoAHPtH!zPM|E(7)Kv^ zHS+;!sf1L$!*^djYQx*BfHnWO%bSUwy!>^9S_*dMg$@kJtMg)YXef{s0Re zSn~%hSk0cjJ7&wDBCfPjDYm&YvnL&e?$tP0teD$*|HjP50J1>%X1lgeJHyIZHBIL5 z@rQq)lW)y+fbI)E28xsV9v@Rk{T4LQ92;$sTy2&0!IojGQ%MV zoc6-y+$2}6W;M2dD22GNe<=Jxb-MS-NOS4P7O>;sA{XBcGU*;ynjF78{hQ9TezMBq z9}2W$xGi)!tY7+!{r|@E|A)f+Fb>+2)bHdz#)CC2Iw~gYSLPwfL311kvjy4vcuN~( z3U#ayq3uh-(ZzeNvYGc6uKfRk@;l z{bpVd49q0^B8T-#%Sgt3y*y>T)`%*N^%qUxIbvdH&*vw2!fzB|Iv+E58(6r{iq!O} z3-^Qd*N~p@^>`R!!~anFb<;Ns(&_vlH$dda|1+-0A_!Y(aNZ3BZs<3OfG-ER1;ezp zJK2wRo1T&usjpwL0IxYNFIA|ag%5>k!T-}vvfCuUJ@T2E18zCli{k`F&02W=H-w+M z$ORivp2B8AkN=BqA}41{ZjP+y2`;A)Mo#{Jlkq&&hg`2mGYI4PQdEP1xu7VuXO5y| zXjN5sJg%zW)&G3YxG_0br1$r$U>3R;u`~0tdpn-@dx%?d8_FZf<(_(;Y=2Uk6fT>;F*3)cBBbfjfTD2re#d z+2HTXi930F$j}9aBn{2q^&(5kKA9#X86I&Ec2BPZG4C?@HIy9SEF$+gfT!ah%C}%! zv*5gI1u3Q@uPcc|dk5tZzAWT|E8poN@$^>XTwJbp_Wo}Hj#4>GryZ-&Z;~7#7m?kT_ub=~O)@mIDy0>dN`rbQuTw5~Q+rRm zunS~hJcfzs_5c0Cm#320=is61Nw|T*`boU9X$xA}y7V9d2iay^kO5Qc(lYmUD(^oO z~n(1~hq3r;G%(O_d({UDK_+R&%z0SCgGX zv}bOMDel7*BzPSBx?OhjAIfWZrr|#nJi9kW1`32ShZ*8ELzbo#3Wr1e0j$IYpN_T{ zAoKNSRXh$=Kib3OViz(zL?jl5txU)*Z}@nK!qQ5G!1n7r=L@iV+BJNpK2FxT3TC#_ zV8MqV%vml^cZo9s(tfw9V$lb``TQKJW2+GPuU#B@G#fEh1Y5{@Pgvj$B&tEwtgbyj z!ew>H>%)oP%SF)>F`}6Qz?n?|5ZS^=F^MeAiJYVok8%=kC$}Kj`DK;-XR(ECBbA$k z%&xhLo*UX>hXs#EI_)&ebH3z;U#IA8fdsr4y1gra_%CHgWH7dbkf|E-7&@qn@1N_c z1wD8gM&d7?CDGK)wv+T+7KNy*Qgq zbJ;vfM5*skHa*owvr2@mJ7KP4uq5OYK<2GZ-3$0ZgHuD&WxvfsrjOz%=-;e#A*9s% zu<(voOtTJ@JfJ#w{;D9>9%c`9EP}4|Jyc5l;NCHMmP0-dxBl=77*Rj)_10erMJVo* z>WH#!$)n_4??^7R8`GZJ!m3jo>5D{gG6Dt0XX2|0*m?j3NjB0NYpy|eDNq=Z+o*ki zhp7HTDE@A`xP{*npsX|IDb^C`?c{Fcuaf#6alCNq=+FQ}ll+@NVVV%O_6(Ztl7|}c za2`NypR)Due+8L$Qhb~Fr{XHPn(1=kSU#LSzw>6bm%tqSzfaNK`BD`z7&Y7vjfE1o8Z3m# zQVA^1;R`CxKfn<$#C;Hl?nuzb#a?<}0?i+G`o}*vL&wwY<;@LTL{g=kL)yR%-L-3C z+o?a&GQEF^QdE~o1|;pJ7KhR#Lh34RCtp+*^SKtP8@acqsT54BCqPsGA~0dPh(yZC zrw9n}+I?XGY1LCscj9Q<8ZIGwEqF-~*R^9aAN>Ck+2c%Bl3t2Haa89gY685 z93(7*U@!inmfJ{O+5>qFaGg!3)(YTF2ofYjfM-hmS}4l`_H+E7a+syQuzFjbf2h+^}iyq1RfUg;5Y z67x?XM#f*6Y*w7n@TP~!vR=INbs)-7CVCuvntvAU?luaN1W<6Pj*tlcJ-+q_vTDl5 z9ebEwrWAlzzXE2t(v`+TFbIxKz7(0fTS$4k#xT;$fw^YtcOj-*5go@a1Q~71(>l!ECjq{P1q|dZfpgWt(OujQ64zG8IYaM-x$ny~Sk@sR~6&!fw zY%w_?sw8yxrmUw-yw$u!#pUkJ6|vpD#Lo7MRJL2-85WMh5OBIG_2;QXK||4uy2wF5 z+oNm%xF{51`xY45A20{{^gJb@Qbh1z&4W}32%731w2NfTzI6tUAtqz95Nv9@_jLdM zK5lE`H}O|HfxC~B{d)W(4D*rhH(r~l6MP1KnWKisu}+`=kY9##7E6VARKE1JW*&VX zM=OYu7@h%~=yC~f1*W)GNiC~Bf%e2$(ua3n(q-pmu;zC**sC{batgyvL2PCWgE)pi zM6Sza$TwZx0fc9Z+Fyn>`}I>X_~;r1x_5V2--f?D)q=QZ3Z=FJKD>>S%iooK*WO~{ z7Sv#}JObobo}#>e>Kn>*$E?b{;*;gKcB?kR`gmyZfMhW^&Ii&na)gesF z)WBaYR9ysPf8qidMxsGJqg!j#b|kg>Sk2|~pF&&J*81zqj-Fgih+u^4Y@YWYTXY#unO?A7O#LqO+oy{T1=UAoF@zeQ;0pXar zzgzE9VOCzC9r-kiI62=SkjJ3CrW@c3j=#EnSyeDMN8a;0!dyJu=^x6Cj#8b`7=+0b53%BBK3}iR7$=uHCH$fHB_>6g zWuIJ*G|$+3^}~MMFHUt8V&@vZD4SQB5_qFSZ=ytZqsec;{=9F528K?bCxKu~fZm!D zTjmE1Gv?w%x00;y*W-a5oBM!F12PLd|S7#YFYtLB_hRl<>*yl%NE9DkB=@2 z$;=t*pMNM83&Sv;i^JfUhjr3tLC{6rZZZjTy6)}ehLh;biEFYfiM!j1EX86aOX45K z1anwb@c_Eil+^_GTItdY$p~}7NTH%mzizC{A{;4GX}gC{R8M`}4|H#`*`BP58=I`` ze(?J$le;F7i6YAT{LSKIBG?_=)RYPq)zG4R7FDYTU0L(!>>4v&#V>2vBr!iFJI&3o zUrQkKrHNshH9j)yzbbtb9Dkv!TWatgw5h(*NyXG%WZ(`j4o~jADATdG*;zeiQQT() zPS>P*{XL5CHTl_P>93bE6;9|T>p=m|MsI15`tlJUiPxTN`26U=Pso&#x}?j*OTP#C zdFb!-s)A=o8ns#~-##1cDbCg-Y%9RBzQmF-FGyuh&EJT$)4fVNGmNF8~M$&3`|Pu+!^r>lx+zuAoK((>XhL z=5nhEISNOk9M;#^xvz>#QWL3)w{=gc&yNq1t!IndB-v>c$oV?N`2MAJ<0c1FhQ5Kw zgo`NSdZ8V}M#!}FX2;{#_dBfm#b35uQ2sJJx+K3V6$0{DS@X1U6-K6Mhc6QXvp5zO zvTvzgCTthXrRN+N{WJ>G$vI`*aG}D=Um;-D37OA0Fnvnab(rJT2buiG=yhyziHc z86@s*n+`=i9u(vh71zm&_}GuEO@R54e70tMnld=!#M`o2)C}fvQSIe!3z}PmwfX-pMZu@~I~PU0P=rvq zILihOj)Qs{3v{wAg=%QZzx`1-oqiwkRRo3G9i-a*$Oz{b*j0fME`|=AUk5MTWn3<0 zQCW)MNzr!ltI{sx*t(4vyenK%voCnYje;$fH`soGmYnQB*QVz{ZN|ybel9ix1oNHn zz1lD987 zndTFkYW1yo)5X)U46r0J>`--M%axQ6jJ@Z>Dc)~JpH5PBUvxL+Fp2$=m=-+zdQ~4cew51h+2&i|GmZ;}ydQWQ6dqg$jP^+M5?3P|^6U_rKOe65 z|2pQhl^7@Y{u7}QYQh30;=>r&oEB9Bd`eVjGGTlge6e9&GvCZZbK@E3$$lvqoaoij z5s*CjO8Hr8<$$B;)e_0w$E55>lFr;xnxNZLPr2Gg;c}Kv6r*no<0xz^iT8(tC7vc^ z7s4lBple<;y+O2@x993z?$od2b9Yy}m1*@*M9?nnYrA5Jc`wq-H~BdDIyP#jN4TlRIu^~snJ`9&iGsZ=8MRIloC2>alWKGQF^MmbR-JGN&Z0ua2Hsyd!6utx-;S17`CatqjIHRCnQT?~EZ-1YGr8dTrq3Yb zfNl~MZhXW(S8A%`&6FRl4r{c#ge0p$2eur*3yHN)W<1NWp228^puw$<2ReDC5bPEN z+2tl5om=FT7=_R2Xk$XZJ>onT-^M=`3L2aJL;27Mm){)qh{9qLUeFoGd&P*TAQZaO zQ2K4ADjwG>x=r21IaYn|#I3|#p0?*G$EUuM-Ppj8*i0E!Z}S4uO2(MHVcyl!Pmdv_tC$%t+gk&;_RgYM5sS0mha zcsgw{dz~j%K~P|lAn-I&38J4HF&c7C<>SH4j(X)vTlxm+M9d65PLJrFY`-VYB1+2Ij;0pqwd0SJ@I0- zGd^kCx!M9f$3=lm88=eW#SQe6e&?MO#y;Gm0}mF+bG$?1bA%r}>@U%FmQE#WX+qr8gyGUk@O4}B;wG!{zF5Mm9JEpw?RScCt{m0J% zRP`4a9nR2m?Dz3oiTq7U1vQ$4F`$bXrMRXHC8hkS2R9Ap>}}&ZHrX*3ZjMm81Dw$4NC7I@ci6 z8{>zLfdtvH@0!m|z_7;i9`9fx&z~0`6C0vZYKFea=eP&OO0&&QZnal>A#lz9BV5!`&oh3O+o#pyEF@?Lqzh; z%x(W{B$@lvKuPIvaJ2ZOKYr-=Z;yIE*j(e0)nlek?Mh^`OUMsdfd5(V23mDw5LEh8 zapy=Ui?o(xm9aUt$W8LS3%=y>=>|d2U=UrQ>P!QO2!4jeFk2jD!x+lgI2yJ=W}SJ zw^1H+4_RX+UOn<4&_f*p^ljZMkIB4FnH0qY$NH(*m~gyg3V9{Mk2E_d5K z{a9O0rSQPi7Q6yr0$XO5Y46=#qxm2Qcx)2vFfQ3k^hM_ooCldA*bMGZMhosCdv`~v zmXzM!bEJ#@%T!E8L_P*}%o<;mJ}ncH+er_{HG;}hgv zxN+f)d$g#NtsGyN*fb#)&m!el(}aa-T9N)E%o2|x^wvm=w7pI0@Bu*Xy_(X2BU-BM zL^xb}08r2+my{rN%9my=Psr)DpK(pz4Jnd9-S+PyF$@gX%PyUD_{-6jS6_pVdWWpB zJKj^+wsO$b-I6p=d*u^0Xb_#|$Wh0BFyvz?g@g}#$2JFLnun(onoJA325O!j+y)2B z)}d$5{?IsF=J5y|Eb;L@>+*LI8bH~+V`**vFN1*{Qn6gw>rT?AZnWHaVD&MTU%Vnj z8j&c?k76JbI03ki`nj{xxj)wwD6^GG<;&zoBEvT;P_vwuO+M5%RuTfB}2S&!_mg2eh+4nH4P0F5qs zF>Q4El))==-z4`{Cz#j)M_L!LU7SWIz+pQ z1OKMsl$*0eSE^KRuEFa+@((4nU$-S_VulGwe0}F0@;2RiDP-KoY0}lC;X)|5EX8{qFWTen{PT_Qs;y=MS92Vr+e&sZFG@l|EtRtu8X6qb!DKDIor#--R0OO>uGe1*PdboWB5Zv2C(`pD#0iekH6bi7lOZ9~N@k>yUox}PI9Wz0 zi$~iDYuWu^pZn{U`o9-oEHYZRr#SKYvInkf1SwKbY! z9DPbXy`C0tkq0YC|Nc0(e|Tu#tKL_GV4ViVqKu56`sS=V%Rk@T2u{qG1~jk_S~$XA zrTU7Hm))8>|CX4&x-Yu=@|f%MFpC?-l$2XD?#$I7@eqF(TNon6P63mv`-k!cF!@A% zbGvX-OnZ)X^rLJ4gl@W%6~;$*k$w!iee=JX8KB8TlMS zjL-3UvKiz0+@POffSlG&)Qg0w^Nde#B)*Gp4eygfi+avJZtu(I!*QEKoz&H#(bvZC z2{mf({hSHDg#3oGpOS;ib8k%r7MNbfji}F_YO`9*@2eL{6Z@^cvagRsH*_fNYCuXR z`xH`ILl>5tRrtsr^{un3v3f#y1&kLbRtW~W6gIUT1m&N$FMyQpiuJ@9iX!+_#v3j6Dg&AxKf*kVAW!_rM2HNsVB-+9%<~NN zaDLe3d5!?=RmMHBMl(8~Nd;%$7ZP^CKNpT(0{=}t#%pD&`@lZ!Sy}Ja{PjYVvxIyM zMUB%;s<;fQ822>DPpsD*C2UUlo57V}eN_qY_iS9>5_S#g0NgHWwYC9M`9y>%uj0Mu zvfY-OZWC*kC2HHgWtoRb3K-tO#6NXfyq!U*;#Udqh1qX=Ra`7UpBgfDxLk{ZtyhMb zYq#CnvqxWk6+H*Z!ECBEh((!v*O~e8u2^H4_j7`|g(t5O z*I;3N>o++Pg0;z;3IewRBmR;2xAa6cvagYMU<#}sGe1VjV|@Bjlg_wsY!EX_OrN|( z6YaNK;WNapv@D>cXjZU#Uy?2?ap9k zSb0?^_T4hUVTchUW8AEzoSo}$sw}eLUyx;8GAk6mmupkQ)t5FXl9EedKpv=D-rZ zOe9MZ-M8hVxRla1pvt~$QOHI)O_J)c|Cmkh82*x;^dTqgXszl+yQR;0K??-ZvUhaz zRz5{y(IVQ-oSoH(&JN;CJUt|gh^}FLmuh5Gglp;8Mj)ZpW#>vx*r-RH#3!R`Aq*bJ zW)zzKz0r$T^OD4EF}}IvikMffXGh1(Dc?X&6veyZq9uyqx15T~2dIHJOMTqixA^@= z9wCX-DC|@7L24(bm>VCLc+~+MbE4qFgOT%X}}-`iq@03L(so`sVPu($Mz6CTB4R zYaM&Tqe$;I@#pWg0{@+bl}?=dXJSTgG0$N{O2t+$b(|q;GqA@Mfy8d1B^KhmrERsG zLT2-6JI!N}NhdXg{M<$D(+%jNP{=3pS4L&3NUfnt?dWT)J?5LkxV^}B-nGC?y~fcd z<`6sug#FKj?+tBPYWzbax+*oA6QY;8al51i zci;5%$-8_X`f2gPo{Y6kjG<~Y9*_T>Q%z=UdM3Uo@lgGv?>_CPGO8c{P;}QJ(zo$0 z!%P;v-Dt4To6?CMA;&mfz;iWT@S|QVe2bV{WbC?u7#l>MeOYO>bKsCHwQT+9(XuzV?Mr zmRM#uR7lfmg?Oh-s=A4p)iYYXA09Z$Vi#L(ob>y8NYOe!J4Qa{VGdtiLLYu5SO3Ebj}E zZwdV@!l?%>Cd6}|98H^?&SxS|EO8^}-P9t;su}dTeLN{3oV937ck)z_WuJWL9YRC~ zgqOW&T)cynNwF6XZec8L+Z*?O6}z^(x7Gw6Y#)Of;ND7nqn3hgX$V3IhaR=%pO562 zjB!nlK^HX1Baa0cAHK3fl3oR_-H7Nf%1UT*!x!o7zTw~W_lx&z{oN*+S~}ueMI>H! zT@~$Q<-1jO2;*6Gsdl10PrC=ahnZw;l3lEIgpjH|XJ%2YJW=u?q%@vRg8jPqeQpL@ zBy2#DQ zJY}q?<&0VsbO^V~uU+8P34Z%%s=4DNHS=MC>!GR1xIyCTwh+E6RVN%HMHp<>i?amk z0oIe6Sp>h$RMi|MpH%Ary@>@$v;}$IC7%LZFL>l>nQNw}&Jo__CM%=vd}|EL4yi&Z zFI{54Es%FR`9X`S;gClb=E!>QAna7d?I^*F&WRw~7a@aB*6wLW5{$XMoI^IjR+bqh z6i%sgqnTud9XwHLTi1S!r7`^X=I%;e#sf2|;>IT}B{q*0KHw*BDdCI!9hgs+BHM}l z%4sbwHQhQ_V8^5L>d$rVi$B!Dx1LCH>OYi9gES>%Xx7nk16j+>Y{uqPC{yI~gxnHE zh~{%$O(nrf!hZiVSvT%;sO#|QMi7??p$OX5 zFeG`Td6c?t)yZBa!I&fC_mq=L8C%-`H^-W1T9-#4t#EWT1kh`0H*4oos_`q+wqw_O zx9FZ$o;14zZhtE=+t8M)Kzn6SppI872j+Mx@~lVKOGv;PCVyDSdLA?Y-j)u;8)h0+ z<(s>^5fpE!2TKlgh9pB-Pqr%&>=*mJ5GuQ?cYcBSKeJwUQ`bNbb=|{8O6e6&PyFWo zp+rlGKcz{LAssFUBRrL3xU0UrZW~Ke5_;K&LQKBbL9w?KDN!oe%}L^`)2CGqof8$g z9GOb$+ETNeh3erk!4|32=50AVigBwv{lO*$oz??DI#10CgeA;zQ9MJC{k!bqPu5Y; zlzsd~S7N*F=6&Jwn>G8-Q_xnM=eegsmy35X5!>Plt6D?}``|#sM^)scc>aK^$-$9g z|7L!8=1r;ZZpJrd;;C$DoIj4kqE(t(+{upu8$Lo5&#XrREB2iM-P5ARWFu(4)~Mue z0_$C}Bk(j-Zi-_4FXdXlz$)FcIl57a&1(mFK?Za*iNi2uu`-qU2$pjxYXCsmn9eKv z04Y67PLg2XEW``=0C(Q*BHB1G$a~AZ-#okLP1uYf>{v-6aP{^xFGVoXvTcD7ETOO_ ze?NZ1uq)7$=_~h7CnjOjSK16H%5A|9bxn}9?Gon$o=0Ig)Q7n$QwEC{`j&Wfbgl0($Vr$7 zr27Lv+dQ;BxdUWs7!_4^t)02fu7E}Pa%0KSK`Lqh%?6uWh!Z`|6DN!X@+c1H=~)22 zY{=KxR;&qu?#<~>`=TZ8rB0uS&s_~ulWdm+WW&tmpRzgIDI}1(l^d$~L=WZHal#p^ zU#`gL=eL3 zHbGJIVkJ%S2v)LKrj8s@dSBDV++i&v5?UW7i>v;i&;VL^B2w%^3Kuqpbs?20bkUV3 zxcwa6j?P4D~WKqLc7viB&_#UN6eRJJPPLR3?yOGbXi8<46;2CQ($Np{0 z`^DYlF|$dO34Aybd6(&gscswklKHK41+*gY6_`N3UuVfyqM>2VzFd~J2Pv5POF;7( zK_>reA!5|^&bX(#hD4BJ^M#$Q_Z?|Gb3{oWVZDD0d%`KBU>|(pHWF^UO@O!I6a4W4 zkJ6;1j9iCm%iby(ufR=Q2);0e79!19U|;mqr0}Q~hCI!^zi&ZkbesqR_Lo#Rn$e@0 zn(hJz#~+&?*UxfJt6FJ|?J`GTH|)@khNrBKbKCP#70;{Fva*Xijag6N;?*D-Wny2G z+x>D}EQ>oM`fI~Y%UF?N!TZNM-fjBALy#YW2M1`bMxB=SoKhBi5bt+Cx|!UQC=Ok3 z_-#vB(4K?mB{V9gbdbD9YStFyEj?(`aPCs|C34g<&kV+snFnZF5_a|iSmYj0(VF;2 zKoXKbGI8y@iiU=sf%?ZA_?={MAk_I!ptt(PG7|3A<5|R)X?n*ml43quE>Hf}Yn)aF z(bnES;HuYEGnU#k5QCO9_{@BlN2qo|`Jjf(&ejzpMD33!F%VjDJ%KVCCo>{Vjlo}e zP5->x^@fNmVxklptiUB7#Sgk(A$%l2w%b@YqiN`Ujt(__kqLjIf>U)RtR?wC=y|U5 z`kY;YJ%gl2GPO>ru4Ut7Y|H9xaZ18)&l$r)yM1;^6O-|@Zcd8kSBquIKTMsHsZPZ&b{i#7T{ znicH>6ic1ODxEEk)srelZ&peG;dw3e;{wGXKxs~j0qzf#IiM`LK4+AxUTOPKMf}cj zPkBgO4ry*~?0>5~g;1$n_X#(IXOoOSA;u;9z<4j+TZXq!$zT4(NB7+H*_@ zb4sF%ii&>doE67uIIq?zatZn!eTm!38~);|#rC)TXtpgaoogu9mdEeTIhgV4>lM^&Q-nvTi_}KC(3yBCsa?`JxkmAyeJ0sT|3jr+~tJLzU zmhF={m4cjRp6)_{UUJSUu?!c!@VecAGa7OBAe(Ct2Eqo;2#fAp=IMoPn!kNBN|Lp5 z0%mG#h{GySDgUurw8i@ZqQS8r3$&pN|A&Gepi&tCoIx-PmNoiBZro~vD}}D!RsRg9 z7bAUZvFuG?GUvQGiM%_!IPNas1(Eq^_Nzcv^?I4)NZydB)xY==D`$p}`f|Rell6JL z5MS_|{D>9+(@of!c52A}1jW{2`b?tD8@WnIi~@ks2E8tD=cL{@JSvz}c)mS@2%eAH zuVfj61J$NmM8ZVf`sCg{bxA$%ws6^l91)YmFx`RkrAeKOv(i$VE_{uF=e>D7a8+lI0v=afW-ROQH z3Qy|F@A`*A4T+cV{b5@`4$G|BdLq1fl@VH=;EX>;B8ynDPLf^n}acyb700g z-L{tcl|6LN``tucw_&HHT~&QdRz@b@{a%7)&HfGCdixIAUbULp=M`_}lLfLp-RzPy zs@hS8eUS+@LHW(QezkZLs@mE1Hy5`Qf}m4?ZvjA!b7H=#+(7Y%N%kVX7UJhs!cF1k zLN}Ha$C`ch@xu|^XaNXJKE5@FCvj?wzH6m8D>;BbKmJkcN4HTWR)0~!PNh{!ng zI5b&sNfI&mOewop=k_C3R0P^t{x{ef6yE9`Q{N#s3Dxp$BGqlYl6Jn0*STVf`)Kk0 zCDGMuOzNxt0CgB+cQu@7kTNU$#90nZlMqX3EcYEdzNiUR8NZa8$XK{%BbruxF2Hu{ zRA#VuIij)gp(K5JjBAz2b&y{`fDi)(CDr~G@0hcc^H;_v2-S^)*spuDbVsJSg~9Tf z{j&R8!^E7y3Gx%+#`3+Yv$GtpVEp7~)CY9f2J%0+7ZO9uQZv_Xyp4sB^hEy}Chnbz z#zXGq2+s&TQrJ!cfHn(zHc)*8KEi}zw|Ge8p;!&SwYf(W@uJo~Ig^XT%8DtX}a z_gaf8W_AbHXd|$wo#-y$LL}6Oq5SZ%C}JCN>$uXqs;H~k`gg`Rk8y8#PP3Pn{pw=e z^L11|zyXvc(#Li?E@Ix%znAaVM2W8MfLlwXQS{n(*jY&~J=Tb6!e}H+%`jK4ZGr2< z1j5Xdh*Hj;bVCE9Uf+Aunh(T@;@i|+MjK08Y@YqQ#6HKp(I5_oqTeesK3QXH?Ji03 z^5r5T5RXVsEAva$-ofND9KsooTa7^d}F;ev?@OWv@CIpEfD0p@z1q1+@m=27!+}nzY|IFswdfAV@w5P zL^v28fTce z-s6@*oN1bKb@&yvLP=FHltk!wBj5#mBm#}n!+Yg!m~tG>J{~`$Hbf zTi@m?NfO32`b;n_((p=sz}@`z?cf8C%i^0!|4$6fFGW*HTm1QGrpm#Xzbq+&kGa~618rj4O^h|s@&k%W7Uh(gv zi}j&kZ4*EVi5~U~!$jjG3N2hv{HEF*7J&@C(dWMs7JJi#krjE#TgB7)7jx8e!n1{V zf_&S%H=raUSO^v7q++DhHT_f4fRat`yhH+JAsx>wkdduDs$9YyS$BChJ#)#~-ciJ9}lAY(JdN#~lR+wJGvcK#KB2Ks+v5izo$Qvz4N28twmg3_+ z(jfIN>ZHAzUc}*AtO>`#{8e$Sl*F;xW;)cGuMOTxITL+;2;T_+eg0UT<$V0@lCeaB z*nJz?IVVfyr#1C)oO?;!%z+nzl<~X9aExBY&|*9(_Tf~sa&?Uucjps8c+L(FE&W{M z-SX@t@b822UCij(K|pIg2iZu7&yu06-_*WaX&&t>k@YpKxjOT1;ZTozefEn#y!v@4 zBIWx1@_(tD>8EA=HV#8+;VkEl8tTp7*u_T;0V{h#YyAvapPCR=0Eni`=FqO4l?eZ;$X$gyCxGSKstU=?nk6#2F*zdxzgK5$UVY-c)!0d^|NJmVP<=xg6_*N%nm^g ztdJ894a%<6#pH+#Nu=l|iO6PVv}H3_)EAHHhQC+Ym3i8Adv-}^$_;`Pr{uXEe=%&p zH=(>pC(#u{vCbKjpGewz$V8VoDHx*?`nTRwt^sR#Nd~l=g3y{n?sMFehBg@Zp|0_1 z7EtLih}UXUqLBoGjEVy{oZE`Du(v<4oUczIq1GP*g^Q8>SJW7?BLP*?Y(_OsY8uDq z=Bs(}Ztm9&&C1<&G`tb44O8T3la^h(P#a!tS8$)C}lrn_r3TQs|IIzsuV z&f3wb;3zecgCQkCAG$`nWvSH@=Qt<};mVZytLc?$U=@qdJaT|Sp7G?|(?|Bf)bQ-E zN#Y;iONS4??ZwmT*a=b~_{<+!3VziptAvU*WGb|nbS@J;L_XNj`nMLpviyCV{H!=% zZog|e-g>(dL7j~h?-|ZHhJX(Myo_FfGn*R{!9xkaLZcSN0+4}MjV#Oe zBYU|xnl)5^*oUWOgh6h4n4@KFAIkGurJ($_aM=p)`K-IGLnKjpyH{3LfBwVdr4f`> zD@*1Wrem9i9_Xe}xN z0Xf!biHid5)$Ok)r(s=aZ$r zO4BQ3hYxaSSDf_|i#)%V!Q};qnXhMeyt7LDi!Cp96sbj#Pfr|8Dy2MKt<)BHN=%Mw zgPE*oFqbtnJqoZlEM z@(Fr1Di97O;0rakTCpr^?s;L@gy0`KAGkN^=xw7T6^fmJ$`Vc_{hBsAjR~g9gY4BG z((|GtB)&Jm~XlUJEvY!1*NOOxrFrzWk>FyQrwVP$BG$_GK!%e02)Mu&X34K;Is23 zICjzf#QR{&lV?n!GV}?SMjm5tN`)=Wrooa^ox|5%ai7!D)`nmV1111cxRo=R&F&9L#p1( z-n#86AErJlMi&c+P7+mmuRrr3_dCMTq)Z=jA1$5XY1U&*`+hAnQatpuO-i6t7EVj? z=xk!0W?koU@}W6?g`yX%{oAjVow@u8m(`h7GA@{Vj~_=|zmD$6=8Lw5z3mu>)+7Ky za)hb~fP2exIx4bNc`;-UOIz_j7%1ajZ@iC-S)sW8Hl>M3@leEj_96U=2X`Dc$q{{D zE%H(%%mi0jneWKAG5D-o7}-0mW&WgY!qKwA3Wckry!CLQbHw0?xZoWhLn-aevMC?b zJ6?+encFk(muh+{kx}(;UTB5!DfTQ4r5rrk`*X~9naP}UVIue_)i!~0JnPTv`beF| zA+`=BQMsDDh-!&f6FnO1X*H^A>zmw@`QySC&d6Bz~$nhO>Tp9grMTZkoE;jbfriG z7rKT5xRg@zdyRApK1%`)IX7yKJ0hgPqh%j$e=+METaXLq;;K3<(-x{32y#4_p1)rJ zQ+sI}xTj2w(GE6fmJMcWK^H(tZpxKwsfQ~!$5D{Qz&%4=oi((89z5aPa}dooIClx) zp{zmrsi{@b@}$-`_)o4?Cd|2|=3!w7>eJ}3zAWpHl`};x!KrP&j&Iy-E@PaKleMsN z$Ml3@4PraGoN#x$WK9$Rhu;$5IqV=r2k4T<%4OWt`9zq_Su1J|fxHXfJ694;>oCyl zx3&!Zm@qJZKQPlfedx&ys=ST!_H!e60#Y6tzU#z0aE__1K$FYKA886Ed%@@wxUl^3 ztbK_LQpD@kacR0o6LkZI=czrb4*RJaR@3~U?r^?~giRS=`u7uh{;jlVn7Z*GBr@jx zKoz^s@PVMa1o&QP8kNGYcl&pw zv##2f?rq+smex@uduo-G_7J_ZeXa}U>WZUI^4Th`Y?~w9<>O)&&TBSHH{pU;Q5VDj zL#@MgY(Y%>Co27Co-{R$OX5TUrnEHbEsUbNxLiBPt!UHQ3wQ*=D~%mi7+9^*3QONA z8LgOpV%u9{3C+T8va5FfI|>VovH6*y(m>Car^*`TtKZ~n$pS+;s~3nvd7U@F2ot^h z+OGrZw)yp7R=*I!kmpR}=)+KJ+gl$u0I=LcLr}<1bm#SL_U*mVxBdh`%QbaV?Wk~if6XXV~f_V&Uooh7(sK3 z@Nyj5-8f^zB2rdZ(C^pLPNa*pfco(5*@kxsRPEte0?VmPt|B!FBQn>^3${K%7c|>B zaTo3i3&D>_UZJI4k@Gx};?2eZJLCIQ;=-^+b|tYRluHpjSd+o!LOAm?a7#DI+1F$F zXAUlo@2uo24zM>)*$x*%o@9owX46FQveQ5r=f=L9ni+e8;tum71A*B+eINzrEMk>PvAO?1+24S9Kj&lW+JXG@VEG4>38cVgCF?nhL#Gy8l>oAx*ExN*PI zu?%AL3;QD^bD;G8&`{aq_-^>pt?(rquP=`!Y`ZCV$fXHOSN~y>6*qZ=$QFPRgu7wF zYZ!6iF^)XDi@Me`4@{AoR^P=hDQzfEJ>;#5+%K0G7-7Tr0nOu-K4~a64=}V)uVmT( z))g}JCQaD1fA5jm<6p}YbK{R|eRbFra{IoY!*L|HA@<^jir>oyB9)#9h@ zcG=!vV76S9!Ew8equga@W9zYnhaf1Xdm)w(|2>}+&aA5@7?gkLFaO&a$3PmTE55qK z-&7rVS+o)YfD_yrI3d4qa9wx)cvQ84Iv-D^yUeu)uMHNaWGs7Ji!y{F6*)+A-Twmw zw%b0DKQql)TuKMKerTLU!bGJDebj`)AzCW(_$s>iV`TlB!jyYlsUTgZzRj|^Y%4js zUJRh(;W_8cdd*BvGrv8NGLdHe7eXiaHZoCpqpuwO%0C#kjTaT6qI{PGN7a~vM}vXe zXDqw={;};cf~MO{-~k4#5LbZ_yHDIb=2E;Wj)QIiS?*P;oeR|Ys-cGBA^KnOB}FuF z(yu+UwKN9iO3||l7V^J~zvI5EO6w&>zT}DH^0;Ane>iQp%16}5Oc|kk38$5I#anqL z5L9a?a^Gwt!?A8MV5zZ{mXe)0Z5%!)5~SK}ti}Bv`^z%Rr1S43=BfTh9Fn#2CM3X* zFm#SxdYR^j7>1X40wQHtvF)=kT$kj&CN3l>zWXrwdaaq1HXTLOY zw;6RR#i#FM9!ggT^*)Pa6Qo$(wH0bjR-4vC@XTlKO}xf_Nm#Rv-}jIRh@+4FO^eMy zAY_>1_uUd^vft<5VBeeR{}$X7WxZ{WG1N)0f^c<0q-nf5wSZv6hkos4x)JxuBj4aM zv|6}pOLxisyh_PHJClYaMOqU0m$ncev0xqNi#i|OKE+7m?_O*K8Kq@hV!_$esi6lFWu6~f?ov> z{uuOj)7%9vS6tK_c51Q8@jL-7U#1L6Jg&}rdDhrws|2yk?g-y!Me%B{E{_`U_+6yF zeZ8CZ)7$FI4CAO+$I|p5-bOi`4eS!sKO8G=J$G4qZf%%{Qua@>pL8ZuMvT>O$migy&w8+eNH5$imjBVM|-MS4k&V0~X_aPeM%-49s5V$@FN>$rPO^3k^|_ zPhfZGYmv`brTCV7y6pa5N|cf1pGo9D)#ZMy&mI?P5yIF9WaynFx~p2+{gm=B=8p^o zn!h})v}HlpR1af_iJ(0|mYN@-j{)q>X6{>SC`fu~KdB1Y>_SgfUMN^al3^kNH)%=)!@Yb~I=s`LmwxV+NdV*RgL0Tcdb}(h?&u#3he4PU?;T?J2qtkHg=a@o4M^8*6;t}u;^T2hEXbe$KY+u z88Cfmg_tjVH?%KMY9(O=ch?`cQ{-73R^c~NHkryVF1#~Sag{(O@rA0sLIlc7@}oL? zv=)ORRU&lQN;L%4srU(+SeMqmx|R1lzuLjI%IO6=S)kD_{61f?tLm8B^0vUGpzMO4+<4HogWgFkFADI&yn37d9JOBhTsW1i~gRtUEvT zZ7!#CeerNTX5(veS>kF>D6r6=BF0^%#{1#xJl_G@s6NHpoFMA$x~TMHIvmoxc=mXr znm}n`Ez2GwPH19`T*+tF_P`%Rxi*aR$2NuM{E{lQ3gQpBS(yq0<@;(8w0F#rPML>_ z{En(TCtMsfb+dQzTlom{$HEGZ_|#>c=2V;NuGN&c^wUg9zYdwFhmsaqp7NE=?f`>2 z4jV2X2P)gwoqH0w(|-^kc32I48W?1^5p4q?q;WlcNO%}Fo2`i_ku90TQxP9{qAjem(CDTCvyar0cm0?f9n?b_$6(GSvuDHj-Tz`(SYcsijJWX7RIv?MLL zb~@D*ovNuw+eb>9KX!%rvyTg#DZzKb$TO;vD7BwkOg#9U93S5p#4y+gL~;uqmDEK} zuYDjg8|OYs_#G5YkB0V&kh$e6B_z=26`Mzibj9?QJZIuh$cv-lC_$>gF@8pKi*M2H zeOs<^a9#x+3zeV#v&Mqi!n1ez>iBXrh+VfFBjk%o)FbNkh9v)%MQe_H+Gdek{EOZW zTv{fo2;E=Ml!1S)P*ry|cW#joHI@(uv?NQW;5V+r+4RcMF^hu0ATOnt<7a99+^WF> zbyMcp;^%ph$RmSYgDPG}TW$OKKUbH^{P@~`P{ZR?f@1&d)gE^Dvj-N<$`*yv`l9IT zl{c0+peybED&0LbNav`q&l1YE`{J**KQ-M7zYW(Y6CEhE<90Ri-SkE7Aj=*45Ru0X zc&P`UF1r#yiZBg(J4Mh=b(rp@*SB#Gl~!|i{Mz}FUaTL&xEN#gjj(7RB35J9b*inu zPBld2e1)oiR94He*&3lEAjkYwz1FdS{h(tzn13sD1eWE(3>`s2;}Oq%xKT?Kk-NC$ z{DSAcCNkZsFEH3dXc6REh_6u2E#7$YeR!W3#Dey>vhxlZIY-LzmTP-=(HfZY?XC^H z=S%7ymmgIemPOVzX6>mfd0#0zvzNrTjbHBALjK~YzG2qvNQ~^w)(vphzf0eIKfLa- zsTHpQ+QYxU;aWDREhcL8)KX}zqOiePC=D|3D!^84Hej*F4hF|x-*(d`I#&}HEf#HC^=2#x~J}PUYt&p*XouB zo;8L?_5=`6T}Ufsm!X6dp#9a8`u6i7`IFfBfW)4nwL@A|t-N*^BOV$$)y%5?=AIZ~ zzU@kJ8T{T!A7{EKUry86epN}CILRKn-YZQ>Qr7-h0Y+nMZ51p=!*PLB{%t*Sk8I&{dl`iHI0Y_=du&V8nvYr zo+1N9QpLbXamjFnjt1h2RFJ*9BjHQ-A9;+zR-eJr{){w>>{Dn%bWJSI7ZP6ltrl;Y z%Bs_EngtV_5`khG%8utcYI?}gx}`?&IO)m~q7bX^TZtkmrc zW2?C#-N92r0`>A=%So}m%G+w}KfK(FLb8bq`iXs~h3LD72u_vq>vj0PUXnqYB8Hwd z`2YR`l*IgxFlgGHj-JQo!?d8g!B>q->JG5bee|ujybM&^Z1$|TEczc{%%b`)+0sxS ze`wG~>F-(Y(8SYlROeV^owIEZkrQjrMFWC4l`Vdte(?jxs!4bq7Wra_#2+f`!vP!Z z!gkd(d@{Nbg8l1CA}u5>3%(Urj_FF?h}lxHTU=NSJ-1HotScM(i5Y+UM2EO+G~k2r zk4N>$Y4&6nhI+UJ^aSJQBEI)#Z!&rP?Y%(FF(M8VtAEROta4oAOQG(ye%=xp<_0f=V<;WY+vbH7Mqcq(U@Rt7PH`W zLj#xtC-8xtEhT8c&}&8b30F~#B;_D?ci1j0a1{{zD@#2Of4E%{GNW#)xw zeyc6V(>xYe|3oW9WV#L$8j`_bY))f8c3`tsZ8h(ew7xvL2~{jG^>gCI=d!e17GG|A zYv1k(T`%-&&M1o8Ypo|fnhmZ`crbl3{zd`IwW)HFO0d3trGJp>tbD1Zxawp^iD0Ch zu~ordG1`7u7}dCkp*!5HCkiX$5rMLN21W`@w185Q?y`FO4Y5(ZuMC%XLASIG%9v}i zU_oh0@WKER3Jr8-5J=CB6y^9rQJj2S231^Rfr<1uTeS1pzU-iAcB8C|CI z<~!HCfpmBY3b5RTrkN5ir+$o*&eS7lLb?k49sLexfW8HSKXj}~5|L?ffpe?sVehPv zfijaGdnNtG3j|$xNWJ~58$qh2N{6wll!C);H%g@;fWMLDQex_=xy^e+(U5UGQLp}X zD(Gd-0Og(&T02`g*v`$pesEUZ?DI5hhONsD^!%o8|5eM>(q_LX#{qO7Q`h{sAYUYC zE^Zc+A1|$ToH3X`BjJVh%|t-HbTvnWY0QBQT5npHh(}E)HSWMR38ub{gCwxS_+#l7)hxwnF| z)OrT(^(#Kq6*a`p%NK(5cU~Z8`;ES3R;F!{AUq@_K62@0()0?Tx2;t|S?9iUN8@cA z3H^=xr47b$t3qoqk*{Aix?bMbbBq*8GRF+6QNfmvW_$oueH!Rley}+??`mvbn9poN z{^0LvCC#r#(mEl`z0vZd8l8s{f#9^v^p%6uk_x7(`NKSBtuLd0c!$edlM~rlpt$ZE zCzR*dXEq-Mcv{|B=f#^PL?8n&B{FK<&6rGqpGbb0*E&NQtfpIAvP^Ej=h-^Dl^L5z zou9iss;(%1fl{EI@G>fsdrjH(YCDu2sax*xm0#j34o9piWV?OD@iQqq{*Ar3cSae@ zQAZ->PAXgaMKq$38>&3+z>zg~>ORczD+Z_yLjSX)>7uGz!H7e$Us_o@dax}wGh3IE zC}iO-J*pwbwM>)M044E+sO2Uwn#U|7@!r zbGtJNOr2z1DTTU{|G*N^HxwQHRQe*Ea{f6zp#W)vrkWU(_-sQ31LDQqZs+#4vA2&l zBH|KjkSuJq79VNI0ZTDt$q0A$aX))L2`*WCT)-5M#`(MZDpD6fP!v)Jp^T}wwP;qt#fHXA2d8UCPdM7qHo$nVi5`+f+Ba2XVwZy*F z>J74x)xHpKfkM!!(uywi4@ShFM9{LJ$a<0Om8UteW7U|{zMcst9#?8b6)iVMDY2~# zOdC14k`I}jWg!!@Oy1id`w1EUY-VT0?g>^IC6w;MyTYyb?GADiT!QjzozYq>Bt062WwL$=`97R42g9k`qjw*=fy!j*old+$%UEMO_g`gADJaF7T`YL*1c4Gz-I!k+3 zcnmWfeH2&BF71>4FpL878$a?y{3TqByC9EWXAx^eMZe1teTB&dQzmpgWz;J(=ksn? zyDyx2U|~M58dH27O_hb;2zif=x0S|bx^fdFsXYFM4)Z~Tz~g>w{`N<&fH}@wOlZUS z{Reyj;R_8{%bQm$gPyx!s0kH%9|06UvaNW)WON?RwW^WO{ANlDd^N6bP zh5A@recG1uWVOnV%iu8W(b=}EF?Agc#QsG^=I9OOyOKtP)Z>60q4ImYS1eyV;Iw3H zlP$P}&S^1`%Lijsnk>X;VA~L$!rQ#7EL#s}Tvklw8n?xMJt@b?6N;02Hi*?{)MPJ2 zqv-&@tU?aI_Yu~fG^U!y#4(ziuT7LM)?FYgeqBbvBdBZj{> z$^nKrT!q(CQ9@Z?#kB2`2V+h!xsKOxUeX$B9N*rDyCT(JWt%NwQ1Pk7V~BQF2aH{< zC{Y7b@bwM-!j6_29Rqu$R0h<6ZqJZ5#)43N{sl0C0@md}z#E(^efB)|uC+-?lf%2- zd968%Rbs~i)7Un37LgPe+aPRDcxA;8WHrOO8J|CTL)U`ar*ISmbeEQbV^qH8YSz-; z1Z)S9OakSO1h9v_{pq^`lH6QJhPY!cSC7anCyTQB6W%Opk}&rm0co+e9S{j_t3`0r z#f49}ET_%2v8!5+4$`(v4%SHj%xEbR%TWUxv=)?Y@F`d1SeF>$3N;&>n`ZGKO?=3W zLi{uN&Gg^c;|U_V&qS*+DyN3MQ-@Ko!yYQOJH?Mh6Y~H8{D><+Hxz$js9`O`YI}W8|-u zlGQ;x)3cx3H%X*$;}#lF<$9-xbV78o`kl(QyJq0HF!tg_LMg^F?g8D zq>)OoA}e^!e|2n1e7MWM}KX3IKPy?^P2TRE?a}Gce4_`@SjQL+)ezk2$eP& zzsxE0mDXEuy)d|1*SyGwU`OPG$t&#EdGh`G7*Yk7*74P`} zn|O6 zIWp1+-=fk^b1CF6ZME6{E2Lepu1;tI%^Nmq{Aqyw!C|v~bgc*zilW=_293Bra_ifd ztHG0eBK(8@0PV`NdP%a9xGR%Jza!}YI|N*ZT_~;-U<|V%xTz z5LlZqOdcq<)!zS!A7p#6m}75m%TM{$5i%$LALT@4e?fg@nIw+`Z{Jj zC=qnclRS}0XHPYJwcAf^Xm=X+sm1d8F@z&V!C0X1fj-M|LPslbvT&C5ge_}U0N0Kn zi_>Z-rZ0M7HJABU@8R4etUn4OH7Ft`Yb!JxX5=7xvK@CQFL=!u^`~?hLfIf!HLcJ` z^3LN_aiXIyK4nmD*r|2-zU(aF@`B~Az95a7663ci-j+T+58&k2h zDz)Wjx){lnpP|_=7v#PM!I9KT*O#f9mT-QbEWi;|kBm=UZ|Yw+5bWRlE991@Qp_QB zH)RTy+mj@g;oATjul1X+KX?)6ONDpULT;G{7(sGq2OO~iuGJmw^eWFR0cs(H-^*d& zZJu8i4SxxUR(C#=c0V)xxEai5dHpeWxuSic+H_53*~>h~@b$Uk0R(dSP%MG^$#vGk zv{6^UyEZc9p94>xEZjW9T3gD3F*BCVWSeDyTq+1MN_{Dmf@DC=>c)zlFQ0IQIKI{8 zg@4vi72>VRY^-qPZ7AdS*4L+0Z|pQOV;I~1B{X64!JE%^jkur;JCuaOz^cE+IzgVB zb>*(|gY2sM+k||Yb==?s<}h19jg$-lCcdiQO_&V08sFcXP8ZFd<*dpOP}obhW_CJAFQ56J%(`r({%Eli9SLTK<0Q9N_Na~A0&_(aNp#gH>D8C$k|Rus z$X)nxrOzZk7{P!zj&Pg1`K|?;cBwT>pCeEB45s79~~AGrYR8RczCL9bJdfMl>{bb_oxb| zvBjW!_9BXZeQ1>V_=n5|V|Ii(mWE9j+u?X7r?>p$0!lMApZX4a7UF5btom6Q)2w3P zZWu60jjEUA=TPYJJMl8d88SMAN~001XyQ-2!Z$-%UjWa2>lxx2bE)ize|p~>%d{am zmXXmrX5AxCw_9$PQ<36|;p{sm+e#tF!M|q0Tgy*$Z+{}@xD#I4$MVn%enrw%Kg&!# z?=HBb$Jie%+UR$eL1gP5sww@=KyDda7W_23l&@q)Mf~P**6rYz%sYUck_{cGT->IT zj(5aWhoi8UnLDwCQXc>#H=VDv12)kRJv#Vjt3Yen;T$dl3bG1f3_j)ElqJ=reV1#_ za)O;RnR7I-#jR9bv5u&Kd~3mi@3}@cmQj`x&G+408k%PW;>(+}8>z;z46fh4qkMlS z6jtft-!DEXk|6?npE*X$99<4Z9CxcmUgyetNfQ|h?Be$IcwtcG)#OcPL0`u$HMVy2 zrtd*bquLfuakxa}^|ygHWMRr%+<%wgv`%YA13y%u9=lY(JFbN`Opl=fS)OkgMefw} zwJ%nnz|cJh6y|sQSK#8tSbb^L&mf~H1=HcEbGuvQN01rsK2O6+{y)HvteGAar4a(C zWv@Nf(Y&=c*wq9JzL#EMz7*MbcA|UaaD=thJd-NJS!gA!KLf2AFAg>Pf8z3>uf!|c zbV8ax+ex82cZy8RX6(hiBE)Q`s@Uw0gD;W{{obC&`hJlA2N*2Vfbnfn3n=ae=HBH1 zA0ISs_`~PEb#{~u_v}>f(Hf9j`~ysoJ-_{QbwoBJY~vraMj>&nznIeje=Cnn7s|8L z45OiSEm2pL&rh-kjLs-3$Qu{MHr>O>d|6(>@fJs2sGd zHMYe_VM+f0F|!FiWS2x}QRTRg?Qqzh^}R_^8^YKcD4_CrIdo&o%lL;8ou%l2)rCq}in=lqkMIgc9v69gBb z2s4yf;uPVY$td=jO4E`r%PDpol$5_jW(g#v;V2^~`J(M`X%_*BcKvaV+2N!cnuo%W-_DE-_YK%En%w{`2y}d|SRf z`uH$wo1fui^G$b**bVNOd_UvnwE=Mb0eGS|^5=}M{`Go41Lju%hU@W0O3!H*EA zQT>M3x90xGOOEUIxq{!C)tz{&Unm?<#Yd%-&?|r_NH|s6s z8vQp}-(ZSccmzD3!6yd)wHN(1gK#ViFROk}nsZ~8bajK%_5TgLMZV)Es(XUu?7)Cq zZ*7bAAph&-nY2x%b`{pv+M%auxWW6ocsaMX0x{r2e!+!rmD4yOd?XRnzx^mIE)8+~ zFL1~fVTK~@4}%LE_YrpMW<1>A;2}G>w}`4DS$_C20tECASN;KJWc^`tz7m$jONSdj z#Muz4~<;k1YIS|PK#_DGCWS5^fy8hcAB0Q!38`V$U4duwd9YgD( zP9FFdH&}*s!7-qlpm>(U6Maq9?!QC-_(!HN1u|MZU!t#{;4}={Rbh)Xgj=cpKPzPa zSy|WsMQO3&d3hqfX2?Tc#eZW4x3)B* z7YjkNLj}tyf_?SF!WK?K`nAeJb)bUwNa|RX;CDV*7!8)E@6_5x-myCIM8Ar+3qr!L z8GDJF%O``;pu2l8Cyn$g}>y5Ng|D z|8^x=(!s)u!E&dJdT9kZ^r;8nT~MIMp*~Ij69Ff@;G}SF(r?NWRR%`pQ5FLEHOUX^ zTODGZv1S<{GpD0}!0~JjrJOt4 zXWLQ!2^I@P=EWcMCp>!Q+0>T+Nw{1Twxh1Ro0Pe&(yM->%+S~R zF=cM~2_g|u2W^*((<)R>sZU9O=s?$w(q!#T_;b@66Oj)MHaE{dEFb=fu#=nMD4CT0 zpv&);4KAjT02BTgLW=dgrEZv<6%d_p-nzU>4Zpq0A&vbkIM)y##TMgJif>EiLOb|Y z)BErB2M<#VDzmI@fLbAR$jwWQY5Nmeqtp97+ac2QYR>z_#^sM|{RzQsl4IZ4t}dj` zihf(A_arX$t-O68XtSJeL`C@d-7MM{Off5{Cdh7!MAi}O;N`4AR4Lc|)MHj?;QG$_ z{tW>EX>-YM*>ME9|=qE2{w{WqkH06*u9l1*?dEVzj*G z))i6Q>~=0}K3io{B}7%DQ}>EJiymt-{>oTzo)F0)K2!QdpR*`*6jJ|p0Q|LTP_#Sy zgm=HWJH{KG} zCG@^k?Sb^3zRz~Ze>GB#{|z_d9;q8VaZuPGO&0*-xCU>>5`aLaa11U=RA? zPdINvskZKFVz|*7(0y(wG@#n}<}v@I`IE>+Ne4lUw>b&)x(_2}1;oSEK3`DrBc7kf zR6q1{Iu&p%hj1kah(i>`Ko~g2kL$u==kWW-{oqr;esmV={Yk`kJK^&WU|8&M+L`y^;ni}NST6T2Mfd&%^WWTyMxs!tGLsnN zQcp!Kvnj>`73+=Uq3(EkLS~+62Bhs8Ya2x?Ou#MLRJf??n}&T2EZasFJo_B;(kS|$ zC>b^eqP3f4LeRaUkFTMb-->AUAb!xx`AH_rn00l`@vUCd%Y4%7kqF25_EGa`z1j~jby|K4DZImY-vBdSbZ8>0DPK7bY-4J?V{R)v3X-8+OX(Wkzh;V)5 z7~_>stV%$=u{7uHHMg9MeSFNmRX63|+v?6%Vd*qQsh>M>Ozc^?rjg%OSNgsWZ}2%k zQzBu^9&ER_ntpM6;*S;BiJjA^iqDJXG+s9P8cF%dnl^_W3#IGIXTeQVt0x6t+JPNBgXTVyHy=B?+Y4j zFLPzWBcwR-!wxb+{=M~#vz~8uC$F>AI#WMMb)$~M73AKpFo-HFD3lz1GcC9LQ&H~E zwG&A)md+=5qo%_q$eI^6q5X-uuj;Kiog2S(eQ|c8D<8rJL zKWJvdHSg_?52;CIRLS#_;4u!ce)?1aEzqlhN*2s6%}{D47>_|uU>2z@W1qsE+wQ#` zH(#n#ir&oj#mO;;p%!aKEoRxxe&Qty@I;2#OhcvdUUT`p{uk~mVJRy9_RC*J$L@l< zSP|az1@AzopWAUdMEdVxIfFN>BNQXMo3Y*f7#Qf*%hhRwe`tzW6^*(lO%voj?N(@! ziM=$6L$%VzK83mxr}7?8IL<9Nw?=>Om{HvG35gPD^69G~y%|eX4@_;_=2aP9>^aY& zOTGR{iB3*xtjBf?axZW|Zk?Q7u|0Zotaq#!v!a(0>-4D1Xxm%cq*?|)&>qi5rydh- zu{*t4@zX2^Eu@ZeFFlWAYV(^e<7$w4CJZmaPB15O3*E%sBKqic&g5zz977D)rH|G6 z(6BCvJQGQENy_;>mO{4*czzvg+EQ~{w~G%sEUT`LMl!eG6`WUNCtZbEWkWV zDtE-&G;vhkGmiV&MEUK+ldSVn?jF6BFkft=M^G0Ls5AI-c{h&vzGwILdPpS1YqkBo zU8jRJ843Kjs+6*`PP`MS{0<7CmUvie_i`yo&W? zB_JS3ih$(MB{0$@-J!H}*T4kl_WOI@C(e7G^T&D5ALqT!AN%4G@0-2vz1LcMt&I-$w_nA<^Lzp#mi8Z8Y-zk2;$?AG+Hu>kx6{qzkLDVkNS(C`YDyyd z{N8fqwsqRhZjQqPgWWgji4PwK?ZzgJ8kf}G8Z0EGk5NoU2|%g62P|h0Uxyyd_w*u~!ds-XY>jeu ztm?dRmBQAel2lB4y_ohSwA_w_cITWY=EsYYzMuZ;4l))wX+~Ku43m?De+Pg2MkJ@! zjU@7?PZ%+zhH5C9J}5b=9;k^=dNOGD95fKV?1ci#)+dRA?&@yDO& z&bGCDAEz{kRo+~%ch83#cUKM0CEV;iD~Pz&;C`tqu1z2Gy)JuOgMUF=@=Y}_E{B$e zhS%qn1#M5+9JkLtwC(X2f2l~u*Yr~&f=F{k)G>XH`)08NAXg@{hr&9aoBC+#M3fz=``(A*<{}NXT4ny6;`zNa^0PZD7DqQ zV!CpFDQpR}e^h1*Vw5PjHvK2(qtg$WaA=ZhV^sMXo$!~WuX9?}LEfTcx6KrVT!ZHB zeo(xTMdVP=`AoL0)X?^vq4G_L*OrD4%qh=y#Gv<&6t4s;&48J9lewzLaE^6Oh|Qk{ z6S|9v;i>1*MfHT&lFhq0Jxlg$czf>1&u4$wpMC`++FH)Ih9jiG!W|gF4Yf1j_Lf`WBZ)Jv`{0#>2u?h(0D!!i7 z=Pc7F>v{awI;u?WcK&&uQ^)p|eS(&B?N^Hq;|kL{bV)6eCt=33e!lyjTTf9{H;w;F z;JmZa_6d*_qAmg>VP7VFV8QJ?w5A@C{G6}8wP;HFLHDtcNi3MLcT?rJSHug>SButW z+ZUs*qa8fcDH?88p6hLMFEw1ub zRX`p1mnFKno{QfJCi-y!GNEib**DdY{fMu4T0%DpbdVC+tn`z=7t}dibRKCxAu?d` z2yWvMDZQQyG(u8Ib0c{=tRTJuTDh%nhxpPiRISwGeo>eBL8i#&SIdtckEB)%U`?OK z_^7-b%BT>_Eg?Ck_mECNeh%hM7E7Qc~l7Ts5bb7I4r0ZbEO!Odn5 z7jb(XzSeZRmypmB(U%GG)YWChe;NoMhd3{bFlwpu8*Z<8%lKKAC4H&0mdr-8?3``7 zl+Y);dRio8Pi4!ITTo#mn?1=%tGFgItkxyEO>bVl{bt!feevP;Y(G%p@+(l`+oO!j zZBlW=%cLclnKYd$fcH~hIeD22R(j&`=ZKa*qxlP0kKZEd5xMUvFi(a|EH3ojW;#9H zIlH#|wc=8~aW!O5>=uwFL~kiK!P*)~LtYo~gYK7ZKn0bOlpXP0>|{$62T;-ifys5GP#7XxNTyPu@f# zr_GEQW0^?0_FcB@&2r=Vy$qry*ZH1cc^}>9Xna~ZSLyvcPzl89L-o$?`aPF-H_Nn2 zLy9}1`jpyHevtxi`&FkCL^z-cPi{WZBW}2sf1Fo0CoG}IamP~H;`Cdtldxx_`R8ei zRY^MYihDoLd7YC-syMA&j-nIQf7Eo;k(WRMJ6tuV^0dH=P1lyy2MaZvNW3_bkv12QDd7^`;l{lPV9tCZ$+%qB>dF+`mJlm9zGK`d14uYMxW~)F{HE=d;rQ3~u&1uX`Mf$}J)7FStUFWh`R9WL0^hO~=TqPK zqN=NgQ_0W5m47CR)6dmhRLt_bJvSgUOIlZ;!Wk)F2t*lu9TVo z%lGeQ%53&~Ua~_JI#|V3_B)+TUG#Xpi;7yF!yNq+NQR`9p_P|!DMu&sV4dXc`0C?U zwn~|zt!l;|v&7BGmak~~GHZnOee)h=CzZwz!evv8#k@g1d9I}6g~ij6_Y>jFQ_~A|OipN%k-~Lm`EbAi{C29FXc=YHw}81Pc#wM(J+r`| z{cGV5(Q=xGt<;EV#E%bJN*cWLE!Xiw@LLEQ@U2IQOJwe$Z4-%@XS(%7Mzg#pgOc7i ziR}cr8wk_dX(IB%^0)O<`1E%(BuwtTQ8yUQVYn}oAT2H(D9O19*0$fw3IeJuo_A$2 zp>v;4`8{5;zXI)S2);_%FSEeN)IWDN*iW>P_JQr}|Ja}2Ut(xAwsE>suI}4Gn%!G$ z$5W}qczR?ZsgyECDEl?jerxesAm|4xi1%1}szc(}SgTIszTnXnDEJ6!*ozfR%7qCP zTVpGLuEa-hk#(HN5+Xb|LD`YBI%+`jHof%u2H5Bj%oNbqB5?>f6~&_1oKz$42eO+K zKa}=ax`}M!K}NS8DnQA^rG%AHoI~{;&aVwkNiz&;Q$9n*5ld0{+a!d@aM6%;ZZar< zPwSv|tXC$rb32%obS9n-z4OOw*q=B?oGmZF5U<&N-{A^mj$)Ee_P1BIX3aGZ0Hb>0xhGn9l=VNe3;xHu zZULR9Qr^r;r@Nf=+wp&wuHA`Y^ty0HA~#uxUsbK2s^B1B;!ZIVcy|D%&%OeMdnN$g zp|3zcQZMGA7EVmv$MR*iW;ms+EUfDn&FP`8D8Y310UnxGkmft+ek6|mOUmWNXCTJ| zcty*;9h>SO26XxmIc@__uXj~cIZla|qS`=!iy$1CTkZ5-YA|IH-SlXxHeYFHpxg>v zPMzo0Gbn7o!OR=R)af02UtT|9WWmz&RsA#mwg#z(c3VUGeuBjTeZTp~DH6L&Y`>k= zJ~}H=`kWl%taRbSJ^_Tu1Bt3VH~8ID^@$}rfS%3r0f8egKi025DxhN=u*HpgCpAg- zJ{TBu_C)-)ZY4ZZIdqA3ny5w!Wu;a;BnB!X76qRv9JWc$m2Mh?->9b}7U(Xo%NwuE z17X$xYQ7MfN^=EDak>IsL;Ue$0Pke1ForJdmWmM00uG%G+6}1X6^K#ZZ9T(U?e;nc zjT=xT2liW6_X>pcMja!u*Xe)`P;CK{Tuv?? zG@&XH(HRq=HTluNvA+a7BfyeB>LHMlb$?gPgranXUI59h2V8!F3_Hck^(lm~F}D-o zm?OTWoar2X;A1f!>&x^yt_0%_M_YCQ3R-{k?-b;gfEcO+hyZ#l%b+=9+&QvZR#iar zPLlc@ATe#hozfMeUv-9K0Zz6kbQ&f<629HyfZYb1Hh>L#Ynu}f_-}$lma2hT8%8vi zXV8Sav)NFT(ng`5eOmLMmQ@(`+0T>< z2%c!DOCCr7w#UK@uvN<_umyA;@W8L=9s=zZr_rTACPpS?1@{>V_(ZE{wShel(fk8< zz3=6li1>&2(UXY9&j{LnKqY)%+{xp7CHP`k_sAke=8I&YvDFA7OTT@8c|&jx<0x1ffxrg zKF+G(?%?5@ojJwPa9X(D`+?Q$h3D()0YXJ?5Rrjc-X>nqTc!c~BdOwiCdDc6;9>J^~!F|y%XF3xKJ zk@5G)*etXDH60(S5*7W-s-{%($7Z}Z1ic2#NnW>n{9(4`bDSBl5j1d@5mlVWKkp&_ z{R8QoW?)^{J!0%NSl5GifP#Vd5Al6?L4BfaX4~~lt|5F7+-*eNrRz=0EFd6Xp#Eb* zp@qu2sAu$XM&;|6HaPxnHl(s3`s{;y3?D>~#_jZicW>)HAVdDek!1KggUVO{kp%=u zIldSFF#HklV6x;UurMlZ$PB!j1E*h=Cy2e_gumOe_a&@bvI=AWZ!ER)KZFk;zg{3- zJ2AF=xP05#l|Cv2dUWr{Dr9d`5Gx!P9Wit^n0oPdt6{%I_3fjN<$aa?dp1o-KHhWu zhb0&H(N+*9#IUs*HXkT)b04wv(GPI>KFJU{0whB?aEk&N)i0~y03$&zBR>9%kq!`l z|2AqvBtJk0aY&13=Rx=5gQcw3{;nMErKNben}!g^d_a6lRs81!iT~>(0KJjtt_WU| zZ)SZ2q^19oJY>7p7Tdq~1pmbf@~^XFMp%KF*c<|mjOKU0ndAObmL zlg(H3lqka1zX!g>rS-3k>^?(P1vFdV45d)`VNrNjEeqMN1gI=^QDjzP>Jt9XQCM~V zV0m-&cVXXN{~pc2O2j`TEe)+Ih*Gu2n6|Tj2EKmTmgUU#P-{?3UD|pUj^}FW04~It zPgfw|o3cQw`(A%VFcWwN*_mnPF;NXfL;dBTQSqIrqY(59MsSah}TF4g5#}OH*v>%-S{9q0hHA~y*f_<|La!C)#Vp1yGK)VA9b!k z##MsArh`ujx&I|RmlH7}UhQve8e1}Iq}Hr@u^!{_gP*1X$A0a_oAs;~GzehEU%-qr zDSv0g-@&W@Yfn15&t4sxGIb#fyi-}cQV6HewwgP=%LhO5kp-ZDa~fCw79)&#By@78 z{~~2R_&>d<9nhb7|I?ck!wRb*=(Zcc)w=x{L9cic4*d$(8?>l27vPeangz6!y4UcZ zEvt}2aHZ2f8+sox6V-7fHzHeD;3&iBoeM$l>Od-|qQ0aIgc*!g$`RFnO!x8sopeqI zxGW~I|L2JTf%~&X=vcb7cr)r~g|u=J5cAcQ0Y6&=6NCy3$M9#G)GO>+9xYSAx%^7~ zOKMl3aq_Hx?uf|lo&Mzy`45XW=Ulp;qmcbZmkc{I#Rev#m$7-)!s)L?VXfU|^8UW^ zYV}nQ6Y8We@h=wnGKFbHV!@7*?9>lN;+DO>qR3AhEMW|jTf{F2k1zFd57u>Ns!iq~-<%GRW?xdy zCV5ed&>b~yr_M}?0M;$cl*2^rPrn&(R(s@N%97Zz(rCAvV@NC_jtFyTx+xEtGbr2M z1OMvSb zA@1GDt(m@+04V$Y!h@;~@@tyk=90P(!Uho!&h2>cbDigKXN1|(V`$TX7+Sf6E{(~F zU+qnOT-bcUQT~&IIHNkNIfSF!b_NhHZ+ZoihwlD_yPp&PYYHl$!=s97_2kcWbom8d z4>&zCv4|n@TJ#R(aolM0U?hC*6R;rgjx@LRc_s;QTgF=@`mMb_B{>O@>`s>73l#BQ zE}yolFiRP5s7t2jz~}y_7ihq3{whpH27CN8&V;0px}_Hb2gnZB9#*)~eUlZO*)|~l zkbYmX&h`WwF(J5+_kROWA_6G4b;{WHIyGI3F1U}1bC(2)a!BWANp4wqup(5>i+J$A z{(dy7+}S_t_Yf#-N#Hx%+3iy^^2MM9}iy+!5jc>G0+AP=){0yy9hZ zaPus}S6T;87h4KgAs!2fz!7NGqqR!ZFr8~iHCG6h_qR=%iH4PFlT3zU&RlT6jhUUV z5ieY4{R)KEHJXxdsUe((qP4}__}@g6ZNq^;nzojpFHLm{#8s>g2H*4jR-kQgs>=vX?#U@rMcEIzvnSRZ~ zfco$UToKDe)(@lXb4R@biJ`lc(Y)(UR9$$`79m`n|ElIcrm+~v;AM0f7TUK(16=QsjX zI5WRCK@6&x51AyqQYKMUci-NIC_LnA4}qdiS{)^=qn4Ipa%yUW*(GeG;nC1gf4Ced zlQ`!a_pELPdOb2$tnoM_pTok!hOID#KGc>r@dycxF^UDcldX55s)biNl5eNSQ+QGM z?(b4%p;h@>cR|Q&$VuV*i1M)$D95smv2aln(1I6nHHAZbf5zuTYspI$9UpJ-x~C&D zVQJyI^6rIxXNM=aA@{M$F#uuK-AgqL*JG`aK`~>(J)nFYPamH`*??ZeAlM2IprzeN zwaMKe==6L0i+9IqE~W#;G-)MiDEj@84!)O|?y#MG0&~LP-~$4<{a zb$KE|H|}NM+ISx6m|Zivd+*bHPrQPjTf=6CpFTJt-hnI7>xFkxnm-VskJzbe^b&Q_$(oHe2pa7hK=UHwU!Sc$86%W2 zm};3?OgHS%`RO8%A&&Xh%zzNT^ni<8J`0vG&4h*MJrG<_w!rs#B$^b)eGMGWg*e#`|ZplRUeW=Uknia z@LvNofG`&vudGBOrt_1h*a={%oJgGR3aGvT%Stzq`%19MhUFkrC#Tzf#*$VhFtXpg zeFPqDDF`!)x&Oq;7q{MXXj!1&ES0e9)Qu-GCyA3_oQrh-u5>R!8ML}+G4?xNXRKM0 z!}+4OdlRnY_c=7L`!s)#$U-_Yg`i%hsHKhdQV;&CT+BZ%67bh2fTJ7~UHC*u5exkJx3d+rNZd4da5W%>B1wek$|HDnEJW-R(2^@aAw# zN0!FK;LFdeWURMDf5wEiBcIJr&GQfSZ$B3M86bn7-?)n&jsMn`pJ++z3$kDNmXqd5 z^&^I(;)fm+=hUx-TFnr5O&Y9=5?`WS3|Ifpq?Rpbx5RlI?@-g@MSqs|jk0jA2F9Ny zQvL+UY1dDZW`NEd0*QXTt4ODX{Db%`-QR|yVAo~!|^0oE}$ zGq$#=c|+f_iM5nZM6D+S=_7fvul?^dnuxoNY$^_k61s#^RJa)Nhf64}tghzECgk#4 zj^6S{!X(c){3Qs$_-eLRPfZ@)_Z&Vv?t9>+e_0G2K1%A;$kwwGlS;!_{qYltY>*%< zmHu}3Q_bzo$a__Hc$Lr=XCa(kgJM^peCTA;-R8NokSkF940JmjNrId_y7%Tf^x0wN z-30FtO`WR|oWJh)%iqdOuoFZjsIOq?>lEwy;XW=DOz>hHxgrU)Hs*tueAiBk!5UO>GZ9Pu`ha~8@q9>hfa1& z;zUMdwuNo-gx|WoF2VM4prrQrd8g}^9-KH-Tk-ZO-6e`8qOGR`1ilkHW(r^4C$-HK zt4{0Iub>7Q_tk^f8X+&_cHw3pTsRME^TH<~@kgoal!`Q%S<=z%KLK_!0jUA)!Fc}l za4=5M7)T3n*j|LPqQM3SdfWUn43A8LmZQXTcy!%7bZg}N|aGBGoE6}L1 z&nfzxig|`JPyLrEG<9HUjbwDlf~nY6um@ul$%Fh6w-NZ?(=bUVh&J`)>$xb695KY` zsVzxRUJ>=dxaqz*SfaUCyf2M05`qOwaG1U($YT=6?J?y!5gv1 z?z+EaKQ5jS*oa9873CDkjT2aiHel^Q;|Dg^Gz(}OPV--9WEp|nX@6&_cp8CtzC`|S zF?a`BfY0r@3smPA1I93WGgs5tvUgXUEr7^c0E%t1=UUeOi&JUr6mB#iEav{7lENbU z$&x~3qvQGy;nB2DdhUfm8+N8`itUhUh^%<``CS0b@+Y4Pfz{!M)euWENr=sl@JkT# z7INz7zIagng2f>pP7pa>=XN!U3&!a52Z=s$czSIh@w>~VN0~1SmaO`E?Cb%@+8iH* zJ@dW4eL)bcC-hNbpUpVy%Hx(V#Y3x#oTcR^RM7;T{vY2$AC=^3OP8##dpU6@5Qy}z zeemD`6@$Mq(Zz6Od9SA~n~$uMhcTI+jxil~7ywO;7T?Btv zyGse`f?l3hTU)xAJ2jW-OuWtD&>eX_iriglh}~35Vs(_1%?ul!a=}QV*{6PFvP{0k z(0`bFX;{F!t)8 zV(sEy$GARh!AbT_LfxREV%{$2c6MB0-#CG@m^5<{S%hv_u>llK_(05ABUqq-E;@4UPD*G|CQ<`M4;z}m+V53c5S>qx+MBq zG5#oFS!(4sYT4u2tU9oYLj4$}HCYN=_9rmpr_7xw0CAyP`u*w@>43|TM2A6fE@8m0 zj1G24alst+TcV}YSXHRz_Nl)nP&*|czRWCD@J{qg<6h>Dk#_{o5>$itgrKCEM5LoH z+lq<^I3ZamN`~u>kD#JVt?_VNQOjO|4$=%ynp6tKL`0O z)E(^2O#^!?tGpRy>6mw$^=$(k!L|E3`!63>DPFfrQ05+1<}?nj=cAkBy4Ox>TNwby6 zPK!`I&kMqDUumoVgO1gN z6&UZlVmx8`lO=bZR_p(zp<_5f`~5?aRD$z$kW^$zO0uhv(t6CN6M|OmgJRMDm|gz= zIY<2uYA^VAYDdv@y^Yemyo6lxZ-3_p>VgKfr(S^?hlUTmE{e&t6vF7Fa*%avvUI$A z1{!WU{7)m*4fMA4Kd5t7%U;K2d!yrBAK$z^ggdBNo@Sreg+LIiUl*=GQ@nuR;H1~S z3p!+kbGvs1a>)zm!HP&btZXfOhXRSUv96=Wb8{v;UaQe}W~a%014SAuENhw=NN>Za z?(>jaQw25=1RqA;Pz27g^R(_OA7)sdU!@P&w)n6uD zRS;JmT$cpJy_w?m&9ke!K^$o_tK{`SC`WV2sLjJDoiSSKg(_l7tJt28o`Myf340uG zi7E1>d<-kYK1@D(Nxiu6;Om#w;Hsn_@?E?`mXUj8G-n^)@3zyepOzgjxuuE@JUJ5% z_VL);kc*X7ta1LMqY*5F&8N&17V_7@#%TbO{?Aa=|3vIOUu8dnGV#@88Zvf|L_w2c zWMYyra$SLpyPCC80}i|Aid%Lq1qMgdK>Pm6k@T36su27{#IeFV{V@}P^SF@8{URpo`HSk zqg}7X^rY;I5$}xaW_W9Tn|Du>Q|<4a<+{+7R&%^fR9lu9_NjUZ0+)K#WbKk1T!D02 znu6t-V~efLIGyM1r2M0&^y7QEACPsGxWsxA)CCU&82f3Wr&dsrbsAc z0qtOZUg@6-2xK*wvl`jcvGn)690FdK{tPCcn&{M^ycuc9g#~Y~KxKt9W!*VJKW)uF zex%Wkjpa8)>aC9y7mS3^YMEGj{k>a9KkKQlVL-eLO2k}@}5jy9>N zEp14lSjpzvY}yB1Qo9#6*VN8W^YSCLM*p$Gz~s1Wgbe}+5U+9yvN{~}=EYKo#D|KzCVeL*yp zwQbm4^7`S9Jp-PU=5SjF{R<<%JuL5KeX#z%?B2**n-723l6Pxur^bhP`0J1BY&Nb3 zWL|+<#ZcxD5*t~2ZnZQ0JOe%@8=7p{EHLiCM60_(3pfV#d^dt>yytiFyu{OVPu z9s<`Ne(gNk-lHSR-~HLqU{QjJi6Btyj155#CBO@Ky zu6aGKA?CvPoQ938TPqVS^b|1sNH+8t5jOL3QJwJ{{&zTmnN-Sl;$q7H#{_5mp&@@^xbVRedfSHOK=I64-h~< z!;^^|9BWT?|fajCJVHFm!+z}D8@*u zzy9;MnC);YtbP9cyC=W2OoBhmX#xvjkq=;)w{MX{NIJX2a}t-zK=_6;UPQ|jzn{u0mS((#E%7+ zRs%sl-@DUr2#b2IEZV4$arf?Q1jv#BM$usIsnhU+qgF`ig>F{tTL_w;IJ96wUyvFr z(o#K^@xyjkOk$5K;&B##PPCHCoa-?0?yeu;kGy{Sa3MN?HJ}fGN1KRCUfLLSFek^; z!fG|wenyC`Ds8a<#`cplWImn}YmN3kTxq38-I8UtvoExT@mV-4^zIhOk+n)&e_B(E z4sIp7rSd_u8*-orzYuYvx&lobKLc!Kv<=~bP8(27_IwxU$V|Oygj?^B@ztpvLptmg zRAjD-!nB?*eGO?fP2k~0Po35+Ix%c4+>&Pbf?jAJ%NzV$W-zxfrxNyHWH6{38q150 ztQtP`f2KYz7R6%7T{+s6Rr~W3FR1>tK|lFAd7pyZZT9*eYA3Ed$O*#!xfs%vA@ z_PMp*b7Ph}=#v`|pOIB|O zcXF4$LrPaeizLRwkr0F&{6InTf-8#(x+4bI$zRw5B~j=HKtAXdC<wTQtkmyLH;V@4fl*97`XJSFvu_#1PoQFLmewWJ}}1{bcClmOn|hq$yO97IK@j) z!COC-C9(ykLL7a{z&>Y}pbN}d9=hACBCquriEI;UCnohB^&f#?>q{_jN=2Crqj=kVRk(vfY4T)<;xfi#uxVdOPZL7>NH8> zJ8ADyn?{**DZ_VtgDaK~Q{TMud{8ejzhi(&!&Tw{3vG3xg$daF8{j_^SU`_y)qt)Z zV}S@I6Hx?C+<2IHAC9Hj?h4>V2Vr=;9(M*jns9V4H9t~UGcoDCwHg53?@#)U~y=B;Ginc-|UgsE4WKkgX)nxNEh z8Y5FFiM&6ISge+1Pd<}qQa$K8`aQ7#mdX5<&}6&C;l<#6_Pd$@6ATx`&$jA@v105iiW`PJR@3y!AHhSr`ZCsw9 zWEs#wOQpb1^6}l%7#7|4nzr6{n3W{()=g<3 z#Y^dS;1gPc(UJcJ^Xadm1^eu$Au-Ai-QGZKfK3xk9e~YwLZ5Yd0zDxv!&c$8*bC^b z@Vn4w-8y*&DiIV0a=g12aUmOO%@5?izIdn)ViX;(;WCC~As`ii19J(CozgANcQfjBji+N1foLwZ;c?T!Pm)BfPXbL3&=5yq_0!A zzZa=8Z6c8jZ*17f2)&P?7rn(l^x1`)+EYltZGPpXG&EL{W9D`rkZxw9{X>*wbQ}?M8y)9gwkYXOoNQk;x4Y{SVf0^4kW-wYmHk)wPxH$V}cH z3GvL~G&Bd|v`jO zq9kwJE=i=^TAZ#<)6MkQRMW4_r_fM-Ph7lLO7fqX(VcH5@4mj#fUyxz)_2#%J$QId z37ekEL-T$n3zbfk9Ks#dbjB2AaMQ-Y9^?v^K1lGH$w;ErGG#h7lc#+_@SlhJspyuI znH+@}3$wQv88av%M&F~*WCA8i;L#~;q;mqKrrPn)^hdzv(vtrLFFbwv?x!;`XPVLR z6)3WW3r_4OaR`Y}Qn06xS~eI}m_~B7QHgB*lw&(KUizleY@-!=WGkq@ZaN8Gb3Cg8fBK5%2-u206NZ7={Zl zL~Jzhu3~ksKnaeh66i^_|5OkFXq6e*pA}w#zA9n<08qDEv+FYEN{bsXE?t!Y)J;vF)19ed#cLfTfb@=drxi# zlJWLU(U#T^E3ZV=~gCtMv@;&fJJh8&WSkZQ7O%O z4zbuq_reb#T9Y&JLfH`i5bnLEB0D6cgZG-b?EDo#oiguFWdPVz;njj4!T3p z^Ic$u=zzX=u+KK>Bj9$b*p$gC^{NG8xRw0Jwm~K*M>3ZvB7V}g>A@04KQ#In{1D``m_|} zkC@&&aDz=;ABx116)k8MWs%rLSULH>eUfQxo2{H{coQ?kXr4F3KYnaFXX3TP(XRj2 zxVdEimuMP7bY#=Y3%%kz(xLFEc}z1m%Ef+gKrA3%e$?B|(ixIZe(j3|=jZidKDP{xu0A z_9xfvF&+m65uzwYG+gn}BjWaFjmaJU8Zu?#$BT_IR?&xBB|L-y0Ok%}u|I1)XwC}0 z0@;Bh@a*N`PaGJ+_pKxEVZNoroP8*I%tO6M*&c}x!xL^-dI90E4OT^Jo%2|k$yeqY zN*Gaw7eQzuI_x@rBJ*MS;r=eqsU2+2dnt<+ zT>2hI0_Y!-Z%!W9r;%8o-HSDPR_wtw#wLR##N$ff;bG#Vs-+kN>>&U9S9C%9@#mK? zh3~cFeAasU4__MfRCX*G(A|6aq?L%(l_KB}KL`YbslX|`vfA_)Zb5Awj19jZ3WmS1 z6W-KMxwf!_vc>%o`195~ui+DoGw&R1q1Ui9+?}Xk@ypPhY}>(H!!dVm!!rK6A3}`7 zt)8oo+>mg0X>h&ynu%}_x|ocDh&hEKs;?Kc^Y$lRfo@K9P)};Nb3g>xRCs>vzJ4++ z8lpxKbnK)Cbe9={bPqgg*GID*5^9c*u+PHDx8^#6fnx1FeciZ8g0OnzD)`L1v3?9M z<0sNwbwjxn3(xMoWm?vYARxqF9}R?l6-3{+-eoJsUPse6!rlu6^e;QHn8+sDNXo_m*%8R8nQtm2{A;pTJJmNp`#9lG~<=(>PBjm%^f@cWP%obgP7O1;S@k?bGj># z{ZN<@CDIlnF}qE@=~<`M`|Z)^T!AlHHqY-oQ!+~=w#77LgMVpKBU&GzEY&UA6Kt6U zq6#;pBBD!-YHEu;rMI~xUPt~>c9^y<3ZLQ#RMf)a(cN-%tdfVz|Ha8 z%)v8|K5xGV8K1BNXE0j_pLmxiLGrT%3V*~Ehynlyt}6q9_YffTY>5wrje!ed4G#)- zgAUs}2UfBoGD#2hOH0M7AH80*5pKnVtJ1!#01BJcA1U}BTn8ca7p~*)*)A})(0Lgz zJ0{ba9a4U#Sh>qu;zm_#(<&X$TGU6f9f{}10>RfD8Z~j3BrgFiaUHppizjK$Mc1{X zDzn0xper(5=knvC+ut|j^RQqism?$9+S+oAg!Lh=kLk_Hjtx3=m$t_Y8?Pj?eOPZ zfj0fb(181zsQC(%*n3cb^RfdpW5s&`eTY^)#Ogp35MONO00?f?rEGAo6PjziokDy? zW~vJs%3PEa2S2czd%i!)8!zd_Iiyvor|mK*{CFxgpksli20HWktZtOwPs6lpbnDWI z1pQ7j;0d!y-(8rqmlj2zA_vU~q0bZ${;Du^!S`Dd@XZ7O0KE!@HwsG!Tg>%)gv(Lv zU=x_{{OW~Yz&!IQyxI7X=-%x5_lGkN0j1$10q!yE7$BnScIXjEoUG&~5RWWc=r;!j z%?g_kNfK+LNH`-E&vPRzSP@>4IA=5oMt>vV4ldH6MF#!llzqExmhbFx^Y!(?ynMxq zU?5kdqyaZy)U26yJ%AG5y#RJc%_7fQ#!eA*3RBK(+6a1d(4gTpdBOAwYOi)REjmqI zB9Cma5w2X86cPx8&qIC)-u&HmcT#ZAz0}%Z$^%NF7&Ba5aPz^&X8EDM7(lA zhbD%?03gS(5I1*{&r>0(7&K*AsuU7tlb)mbr1EC5V$}!^H0a0SY>^om~<*kp4g5OlMc;zOCVoHhMLlidCT|K#W5Eu+(zRf=<~ zx`z1NcyJX5l^jG2(aIL^$N@ozy*9DF1cX%Oj7$v5zM^O8nUYb~q6uNU*05)P&S5%v zvOr@5lmH$0Nd6@=oS+(@#C{}S7(C&%8_^7rPD#M0Pjz&f)aV!jq@Mhen0U%n%6 zD9-WkL}l#-(VB&oL~X+X>Dj6V*yktG08g8o#Ni$p4HKVeVi>MKy~Tba!0C`f28u6$ zU3!;}=v_xI;OlJwW^$}UZup)F0{o?q214FnF+xAXj@tpcl>mMOfzUSe17<~yDBfaoE%fXf4fsDJ~bYVt^UlZVVv$3b6 z)yBg4dxwepj~D~vxfC{80+k3kmjj_ZS0Kwc^gTU%jX)t1KjGtkmPk=j(V4%I>{%ZC z_9mCIb=t45R!@FBS5V;QOm~6H;YT3{s+xhyK*%yX2U^A+v4o_u@p13Uv1e+@Jexe2 z?XtA7QcX@8?#&6WCHVWdF_Mf{{C6rFCXv_H2`Xb4 z*YOSUX)8^N7$ZchinLDl){B>$Qfrx?Wxj5DoZ;!c^!R5VQL~Mq;KcO75F2o=as58Qp zE6_6){0lrWqWARy6c^3xiH}pkBccEG0T5nMniz@x-Y&~G=8hmRF)>01S=#%{8b4`V zRQHH+3a{r>YI}Ms?$g8T&OexO2WY@HA__;_62Ipc6O3anD=tK5UfvO#NKOSJNj)@g z4}FAKD2qm9rJfV;iN~bWw{-duy*5fgnF6+l9BDGh7?JB*VqI=MQjS<_jx);|zUzvsa!+jwt=l z_kA>{b$7FIdlu~}I54fWH}Tn_UiD>YDs8l8e34Vt5hnV8FfT&3}V0kP1jibkY^*_GkEChcMxk<4gQ8VVLk0$Rl;7r^c4P zIL}Z8V_DsBFd!2}+N8VW^(`?^bGFb?bsD>k-g$Tk)rIZ=xN~;ok<^)HT>K8Ypy)cL zMGv_CTCx?Og<>ny0F)7T{s6wDxGXvGc%=x470m)Ne!n@U>#A$BC$TV}pUz+0SJkf4 z71R-vo4NZ5Sr($qJN%b7Z)4&v_W|k8?~ToxY$j}l9xk~6$u?J@Gx;(T_`x8S_%MEE zM4oOn6a5#a`OhQt!+G9Gei|sIh*(U7x$I9MZ^JhWmOsx=rN`z+tdC8CHzxVpbf;utYX}Ht>PR0FTH!IPpk8aQli=a6^($?jYcw_5h`*eIGvnuF zIi;X&jBhP(cAJdv?#^*j7T(x?*C#SGKwY?d?9aPXg#Cs?VYLOM?U#@@bKLO*V|gu! zS@AJDpJ}3TYxKKwfxnJi4l>ym1FAY0-=0PNL7Z(hV&4QLE_Yd+$})6{o~FHkpB)3b zTd$+vqblp1;%nx-IcU|T(8Gm}EJ`m`s!BVl_xFYSent)}`1PhdAiHL+K*-j!>dspG zZDei>=5Y{H(di<5dpiVn8{7jsegAOS{WqJRGWU@~-h~L-JYPp&!e)8)+=`oSoAxu7 zv*l=cQ1dQI74gb*A;+sbGo|()^y9l~?CR9!{tjsA1k7YnvzvmCN@(HkWLgO0y#y`* z6~&F;q*`ms*vCg%_;`*Jt}h8*=3dJQ@`|aET_th?Kj`l$y3*1FP19&kz4!Av)bZj~ zKR`tg_QcPkV|z5$UVS(2VY{c5K>RaW(Fp&t-&v%~+#0D zIYsTAX&UF?f@rt_^FbyTtuqodwZ~}#rQp@cwC8FVPmnVxG*?%Gb;V&;B>X2!W?!3h z+^=#i&gdt5Pj0g^B2z}qzHl_ogiI$ks>Xi z(nRS^T2w?jh;#@Epj0UWB1#KLuR*Hx-cdRTC>?162{n-7d4GHFIkWeknQLawH~YKJ zxvu?RLIQbt-Y3sm>t6SLFXDZo!*UjjMTJnOri=CnAb$f{FQ#d2E3CxCKQt0+>SzDP zvvw?4d0Z@VX4!W_mDG}uMZ8i8VQ3W#a+$URQm>Dw9FYy^%Q$58BJ<2d_ciGG9j0I= z+tvW$F`zt{0NPhP{>swjp#W=n8_1t$<9ujh3qSKz^73(TeQadgm|y=eL&&wgr*bgI zR#i}(Q+m&d^-LzH+TTGqYKAeL`WXaaES= z+4UAz_EO(pWgPqJqq#IM*ujRtyA|7(vQ@95CBofQ$zYz7kZRGJCqARHv!^%d@9?@` zOC`HtXc~R61`&Q)TbWIdGsieAGXr}pj!4yvrs&QT7PHcBJ_~Cb61^&PT_8;TP(%3X zuFnS9OJM5I#{Oy~o>yUzZTj|oJS{(@s9&d8;f=GS+D zuQGeA#7r&Fs@UJQ1HU#OMN%S`fT7|e3HwvcE^ZD>DngQn;yyQ$ey+bgJ}7saeP4N$ z@{s~JNpb+xHN|M^ZYYoz#a(T;yz44z^t5%_;}XA^HO+-h1cLJ8Bo`rET;b1{z^4&e)O?ibo)j`3*pPKa8M(Jd+?tg|~ud_nR6n%@url zK?-%25yFjs1`CVp^`~A6Ieju5)8aL(Th%LrR61>UgT>f5Uk;RVX(*o)* zs*djE>$ac1P*Z4fZMU-#J0Q=Hb6v}RyX;G1GSIQwAa=+VcK_b;oFT%;M$u;S!q)W>5 z&_YPfBAx-c;vT&+w9iQVGDO?3Owe4>nYiQf{@1>D`n|AYou=>}{J-BK0P%l|FF0W1W;Jd5c`rd6rrb;WmBl0$e zwnrWjev4PzoEJe(b)!vlj*78xp4Jp~5gImiX8P14y62y?jsv%q|Hyt836;u^%LI^+ zutm+Ne%`d-AoHe(g-els5T(Iw$X42|BPQG~j+NNk%1B6t@p`dYS5&VMJfA2|mob(- z)ku1GG8mxEE2^hI{L4>H!4!m@AyTuWa}8&RTv+*)y$>|W65$f=H+S#yB=Ocvk=Z|k zc~dJ3Wi8ml2*9VmBjmzZ(WmAZNXI^x^P8<3D>}QV8mrg9WbWEY=dmG@#LA+B3uWn1 zH0q=oq&UI~ih7ev4!w}S3}&3wDB5Ccsd?vDSABWx3r&gX+l1!`Dq;}A(F4ne>Y=0F z@l%}ng}Z5c1C6g@j?`^x&~-EVqOT-BY{WViD6;?X(o!)*3Pg+%N4frT?fe9i~ZJ?`{HxhjGDX@1sXH;b;j(RQPYaNREJX|~_ zS>N2Ik!HRN5qiTRMOI->M!XOC^$_=bTT_DOyFYLDi}~&jyO^MqYf<_fN?+DO0hcYp zMZ#O4$V;A!PpoiiRVCzAxN*ALTSYbBc-Fbjmq=^ATTcb6%89sd=wZ%j{UVR_gm-B_ zp%xqirD;~e&;iXk^!EksD&6%7!3=91GUKN`jT(2!nuDNwvE-<;9Fp$V`8 z9)W&;CE4S_pngHrvp0wS%0*LvwL;Pk@@B%LTo%y}!1Icy-E1kx^`Y1O-6h-w;@awt za^5DH*(=CDihVg6J4$h^;_@5BRS7gHWr??2fl4dv9bzB@ySionN4-1q!*_dt%<(&u z2F3P8SRu{>CW)!K7fJ1UNb+ z5Nijp8^1xeifzQi0caKS@7V}0%lqH7PB;k#tyIMERxmakBiQM=WqCQSAeZq}x5Vj` zpKE1yH8$Y6wa)dSD+DBwoYWA6)A)9$T)d zP4S@?={)4?8QiVQGJ~dOytxP;8X=Q6&~fo%8=~nS>yIZD;iX~XMyqoGMSYK4945Ip zjjj{S0J~cGnR;l+CS6N4JHx=n0>18#G|M zt#mUX)#HUDDdv<&t@9hiQ{Opq4#f6ZeO zPGUx3r1t2ScOJOAG3*<^JF`8R9vReqRZpMsL-&eXuNJI;gXoL0}v$ zkOrJ9&m;qH0{*H!T>`j9KVF%>41XQH?c$pzmlEU_#Je~FYLlf9cTg8sw>xGt9#b5mL*~yL1)z81VTNJU8Mt2X7!p!s_qrfajZ6yAJ z7Ipw$aS5K$3Ld+jAn>qtOwL43{|oOY?LM6F$F{Pzx%#CHXbp3_2NO)@?wz7V%Ybv^ z1}ff3x2Sisuen|h8?YxHBvArH17hzYT2CT=feA}k9$^Er$ZkcgNM*&5Vif+qs|O6X zsfJU2rsTBlN}>8}1$6Nu2tG<2hF%g=CBsvpBfFG*pb*`OF;t#mg53G)X}vV~G~ZVi z4{Wi&hgD{dqoE^(yL~unIFN6#BAk%Su)A2FWtQUyq^1uPEN$dU3+Gucx5Qo$%6fDG9n#Psr=b1lLL81o7w z5(>i8Eg_iaUW++sjz6hwsb}buj5ie&X%+BxZ!wKR0Zf_s^b=M>AzX$yp?U)wjo#=q z+)g9Ur%kJyc~IhX((|PyLrUk5D|qvGG~UF4mBWDI8x*ZK5(lPGzK-Mdmr%nvBq+;# z-1glsVJ)k@{N(HOPww;ZE^%yrijCh58fkKMvgaDX4FDp}iBkp+QUkODk&z8R$ib+e zxkS!5oHJtdB^{CW2uZvTJb(Sy&$H>aN;R+>X#MWA^1ATl#VFNlQ?ep`2fJmzYD-04 zM*4o7kqsU{MqvtXcc!) zP&$NU#3PA0)N&+{mq5VS`HoLc=Uman>FGNuQ#G^0h5^z7u+Q$cnrH86gp;9#(0ch_ z8U^sF^UD#{+0Z*5v8!tF+DEvda630Y_YKe5hS_(fi-&xn7C9qiQj8>PP_&K+~cuyMG1Lzjv9m*Zw9l%;2MgZWN>3RrNRYy_!) z0@LuC``FbqUA)LbdceNqhkxfm3M6(Ng^0}~)b`=YEP%!tVu8})(JjQ$O;jf|907bU z=Bc}&SCvIp#2nf&R$J%KKH2+u2~B&~&(cVjD!J*_J!_{3p_C|YA_sPHSp~Xs8set^^7`j26{lxYH|LAy#$B8~J{r?_ zaiGB8erA$5Q|HtMMl1oj1PAQuiWLy=Lmw+YUQYkO_wJI$q*-7}#?$pD9|RJw1!srT zU1h-Xee}+z`-E63g4QBKYert3A}+w_$}C+}xiQMo*Gfbk9*%eRQaog}_aC4ZEn^gR zxUS9z(nKi!0Z4;s_M80xn4ZXEk+IcES=~4-zD|1jwS8>94;Oq5D ztIJ3(w`7~P-f|ixz5DKDQwW;!0`}RCLyJX_lrL$xJ0utoRsSu6Yp!hJiKQnV|8vJm>v>YRZX?WzDa?5)qwN4_t@A|r0Uf(c2k-gwu+!y#1 z39viC0MJ~#jX3Td0d$C~C^nQm_BSBn8fMC$K_92L*!s|6aE+^a%Z9?_8jCS~D$1V147U27)l@7RVe zA+czH@=);`gyWf!lfb&}5B&zk#(xEv7~H3SFZLoU0N)EJ0Hd=nrIo5!IG6K-Oc2hg z`agX~P7y^#Pq|VAG7#hJKeP*Y-D8CEWX`^0dcJ3br&=vJLNw?46Ph5BgD(}BY+W3hAS3BX@BS5IL zTrh0l!zOsg(%oh^d_#|&c?~ldbyudwh;+AJ({AnbE;swB2U>Mj<0g!t@WP`{X?Pk!VY9WeIcB4mx&Poy z&b;q_H2)J{ghNsN^ISpao1dCOAT3#WGrtNWRrxLT(c&X3lP5|1RsIgR7rHjfYqLzB zJ|x3IqPJHSNM-SR-?z<~>yvGN3?5orYaa}WMtU_hhz@rj=m_$i%ATQK&AJw^N-3nh zw&15r_e1yzL(DKuH{)DsuTtu4$!#3m8^fI;zr#a_9bAkyUMs%ED<8sY0?CoJqy_tn zHg!|>?x87$E$AtXZ?$K=Mb@RzosSQguesYufPi4nu;vIIA=%}uhb zdiNP|!NaG421N99whqS+U|Z#~wQfYpXvD10^y+MZ9heS&wyk;{!bSUugii{YN$yhRxmHiA+p&Kv2_mY5)B<_aw+6On<-=|6F_meFb$j+ zk0XgONvQ6@)aC7=uaI*iDfCOap&pBX0R7(}?Zk5pC=YQ?bO4?9<4wxrQmZRkPnggy zJ0;kSlZE#k=&&D=!Is|;t5xPp-!B8*SG*v0j3Bo1)N0u}5PG<$)q>~1Xk${lz_k7$ zdxwIb;&1s?IsonALQyT>r()O{UYvnH2VR4Kgas@sj0ZR%wbHhyMIJXktSFQ6^I(YO zI7_}ArMC96$6xE$^;PgLl-y;3{%tQ?36{9_*jToBo=)8x zBhC|rCxZUx&@@PReEj8MGp{ax=VMu~!XXKh277D)g7akNGFiyW-T>6r)ssu;F@Uo9 zYc_kKDKa=PsN4V(_EGB~EbCVLYe*cN6mNxvEl^;;tRN$%M+aQ4Wum@so#@^YT^TFn znfjKT+)A!Bu=X;In2*1>jKbbgrqA}*724385EErxv5DIEVCenIT=&xm!A77121UvY z6&V#A6`i%}AWr(v;1>tayx2ZRmp^KN19e5a^3IBR)b#ucw2O}FqNdv&v$mFJKMu32 zD>E70ju6U+#F6Qi!PX0y)EH(nLPIk+M>vIzKYc#y1!qB3!)H+ncRnh3%IPlL6S-u@ zOc!QjuM0Gs<|opj;R%Jyuz^*nhPREk>v;ea%&7x{Puutc*m2Oph44>LFqhu|$m^xj zEvmE9rCd^sJ}|f&!%8+%x6Edk^rU*7=X`TQ?VSw`%I`zUxjAk)Q3U?0f?4EitE5w7L^V3K zcTP3gga60zX{4-+i;I86mOz?|j?qoSA0MJXfoVsULZoNPSP~o$5P`X{>*#sg891~5 zyFR@M<%ih|wl@0D%!I^t_p_58a%_a(H5v{gflhP&e~bqI-RSY3y&s4!B1CnwKhrva zzKG(V`yzi5$^m^5t3Uc8GQcz|WX|f2nNg{jpH-jp6Mf?9AI2#XQEh7uQ2c$smf{~9 zTL3Q`#>4wJNvKVogojKWvZ<1cMc<2!JG5&3yfJ>kE3LR`DDf#67^G4hwOy1)cIDD$ zKz^PKn(lToQRcnSE=GI}v95P-p3n@F0Y?Ci8|=Hc7jBq|1@_JuIKWZ@(2w6-Ke`~> z&q-OKtDX+7hXY$sL-9|)b6!ZK^7#0EBm{4O0$kHBt0aSu2MuQNmr$FItC<`)QrhIa z)pKIx;r*%}Xr$*sU1eE5AYLFAQZDbmZhJxqIY!X+it{u*2qMyd{uzJ&8nd1(`B%xi z&lX{PURjxxOlYRU4?%vH^Y{W%8ThpIAAG$XyPENI+8fS2byYi6UALG#Q>@dbrEdR6 z4W^W*U+Y1KMM&`FU@(r$U$z;6Q}cf^u;N!U{?pBJ7w|rr5`EkMnp>GHN$u0x>Er3v zq!g-Ks?5xS^k7>Ps=eq=r-tZCaxFQoAx@ zLH(*yuLJdm54!jsTccr_cslj*wsf~A!Hr*mt{Bu9;3xE=wlC4pm>+y88;NrPNWQ2c z0P;>`Yg0vM)oh#hnxs!1)b9jl!Ku-XZ8v}| zC`wR3MMFFuHYbFgjO>)nCE#D&!@0N80hw#W$Qq2c#>3{tBw{sbW*NpR;_%*qu!gvU z*;R#e^>j6}9!UIB!cY+D0r=Yx>$|c#REv?#t4k*f8cvlY73Jk-2q6*BQ`@9-CX}Go z7dzl`$PQcSF=ZRFq*BN8@Rrp8u{zCoLGrs~;ME61d$gAk`e{B#idjgvqWL#Z8p8!{ zU*dI*VR@mT%mx4wOlai~;mS{aGh7LFfUi~V3XHi&Hf6G12gugVZIgftMlQg$VUAdS zLMZ_EaG+&>c_Om0Y3vW+>-)yRHHVIlJ6;|v^I`B2H(Ha8%iMe+8t|5r3L{)cAZ_ld zm`I=B8%iIyYrQ9(gIZLbLbVj31@`^?)`U#+=e+Bp%@Gyys~fSrXmCdY3$_@KpWTwD zWVezVFtQ3=)7+kA5k4UAzs0hehK+ruT48@|ExaIQU|a8t7j4zrvVe&xw<^Ty&eqFs zxwQ3etdDf;vnFuD=gqSCV~2SreabDhjg}&k6g0|5?<$CrPk}&`AaB&1A`#YRN8KPm z$j3ANyyfeJiUyd&uHr9rR30GIYmXO%#^XfcT(NR-Liq&2E79a^aJ;X z5DSl<|3vInA1Q}A51QJS>Oh_ARKYp+fhL3!vMuA6z<7m?HUN)26AmmSyr>4cRQ@}? zrL%Ad5Q>ebAUi2YVsf9YEVKgPbX^uX(OR}vN9+op5}zRe+m?Uq!M=ARGGBS(0qFLW zO^>B>#Y%|K=Mp12=TT3SJj*-**R8{Vx+SjS17ugeZH1p+#vRY~`X9*&eYM^u?|IMW zNBJS>MG_#U$==%mOQiFb zEZw)RrSiTW75P=#RPx+B|A;=z;6N`Y|8c8DTjfq)nZ6eSb4>@n?N7i`Cw(n29dsq8 z9r*& z{ZT9pD!ga%SZ8&h(0l zzb&+`;O5Sf?54%yb1;YyWgql54kyv4cr7>Q`|d(%u$x%h2bL#cccq2*H0`< zS66Sy{EYulm9*=7yuQLot)0iS^qHnHxEns9c<1c?^hR$(F=A%Dj~MGrEZaGgnsKK% z*~@?V8zhkmK9?%pXcq9z5*!EPJa@58x>;Gw(+HfCr@I0RY6dXI6S$j>mt9?Sy8~qQ zV@5Npe1079{-|%jEBtF&4iUCHAs#@2 zR!ZO#Lfv=`wDL85rlG#$0`$cnY=#fLOf6Z455?9R;HRi%uO5Jixl@~Zl8u)3lW_*0 zdo-dp2iww+-!zx%cONdXL~o2tB97*LUCzx=h#q;^*Hl6f^xPyN6FnGNHMHG_+dt46 zY$LIPu&650>0jLfFl)Dn))Le^)eCbrJs6kO%h)d2FA{U^ zY_{D?!4&thuGOtcZboEKMvI!Xb068zZR#)Ax53lqD%pwIH{@Rb|Qku+vQZ zY)XdA2K>ToWzeZBs?`{Vjqa;9^Hz5Xw`PzI)&8bkgAE=ZhGZ`IhK|zKEJh<4WD}?W z;l3&}`naXGXQAOaZ|bh_?t1UnsIm*vr=ogZr;em^|K*s3x{0k`Rncv?y^QP*G?CQz zWE|~fSdxm3W~aFRP%rpa1*+4swzqBBjn}%#M5mUTJ?O(V$=f1d*czyl^hlJSrhzv9 z^jWAI*q+rE#2&xcJr8N zi!*cbHJ*^`C=9F$gvkBJ7yNl2jLpW%bj%e-8yc=xg{CnkD{&cjiadH5)=m0SjWdp^ z6EBCMtzH=+-y-tNJPup86tZvNsh_{rwR*XZ{j6Yd4@k z;Jo_31Ly8g+y4%n!Or8R0F&|0c-xUE3aEzEGKJw&+Hc`OFaUE;Dl9?#T|s30R|V0Y zK5_le1(5_H-}X0Xcm%MA`o~62l^b-2g~ma_LKmckDM|%$9;-2aH@$VOhQrj{@{xW2 zrCSs%74f~B)}53PMReCWQ8toiQu6*uHKlP@cVJ9V@~4?G!ye%WEJ-pcviv%lw$(YC zt-KTYV|ew)F2&#U>%Y`Chl_J3yO7(A^|M;hkg#&lr?t^Y&ff;0(N7u-ukNZbnVLKT zQ2@Q%3;7?BGWB9d%))K5wNyk?I6q7po3ix4!JrY78h-4?_G8aJ(V@8}NkL9m#8uT@ zxh=i&M>2HE)U2thdXn443d_?F{;BcS8|_F=>9R|x(q-ElS-M6qOwJ~EgWYOR78?#Z z^t&X#-sqG@ip1^apVps&`vbpxek#w^+vWgSO4);U0uH7LhJ@S#pg8&9vNaOjR@wIp zgSjDq#DV<1j8eZo4*n=V3;vS%@@B#na#kx9`5aQg=^BVVY8Fr!U%^)E)z8E?*rOKa zzgq^pTVc(P|G_CY*b&p;=li1MoDwEw)s7wN#aHx^{sXnILW;(79SA5_=Xz{G+*G=4W+5*`k1r(1`*ip8S$NwbSK{F_*4N! zFXbOAimr7k$V>B>vN&VoxEh%s70<)O=IMlbV;+Ooz|t=vqy0RBS1$Q|-M-Rbu;F|} zsuCV#%7lJzZR4s;$fr~oYE*5bkI1*>NU`&VB>*7Si5ONEz0sGZ)m=`--aS)eC!@9_ z+~?Zs|1`AzcJau7h~3Yx&s}z6gvzBFA_Q4wjjzfJq*UOphIbfrX>`6aG5*DrSYqP_ zt-gMiz8^$KY_V=`w6dt@vOwof&PY;guYX@lPI5`)&8gC!M)lT`xtP;C)lpvME)^D* zWs;6bOd`?WJuZkrAOK@WgDJ!sa{$o62iQHE0;s9W=0tTrig-|GbkS?zWT}#jwmysF5T;(T{VBMf)*NB00tF)EaC{~_cuIhqAvVaFZJ#W zo*psXz83p3w7m#n|3LMJHLo8os&ffpFe*5-7%9|^+>R({5(dIo1s_5N{ z%B-vCCYNGQU#{g>v03-u<1%iHFadnBE+;N%1e-|z3q-Yv!?zoxM@%b=Rc-i}_y<6^ z2WSwZj--C8BhfHBx8L=EIMeD4$rle}C@3W4nw&5_SBdAv?YIx(GmXVJ&3~x(|FpP8uj@1r_%O zduK%w9bfLk82Dr^J=)`b+)vWAnwZyI-x-E4gl6efJUd-E?P(x&Zqy19hg3y=%?d!M ztnPd+Nk~mlb9sk>r?{2FC+1Y*R!Y07SU*7D^e#}OXe#0RMSi7$4j~c9t2KydMc4Xk zo-O&iA1M8tZk(;!WzcWuy&S2d6$VE`8NEj$E3M`M^nd$w!!7d_1b$m@R0s4X53RtL z1(IFOVhe|<;O=jq`$RCz8&^B6#YlzIm|DG2gta5FOx_5#X6lulMfyzyy)yHKMW|yA zQ#q}?WZay_BbJaZQFT^|0-0~Xybxwp3CFrV3k-88tM}B|x@7-?t5ke`#KdUeO3Pv= zm*kkTTlSq5F=$^d<;BLKwfAz*CWU|fPzOHhRO(S2#%?4VB2kD=*(FXdbF*1Kvm{Zi!u_Z0SKy)=?v6x8^kU>*1gnuujdLcxc=eX(Y#r|n$X+RW|~dwd@NHIQ%Xn~B}> zW|`&rAhYX_#(zz*Mn0$%6g>I@+ri0O4V8?njBwh-4wY;R@Z0?`mX_k`9!Tg6Q3+as zbbp~+?oH>M{>b+8VDoKl^=mHIGDW04D=P#L7#w1PR;7CY#Q`W82Vsx-sW0zfz3Lu_ zsD8+XcUghVMLwADxx%&hpg!)S7OQp7RXs^M3UP$K^Z9v*^5ODKcP90U>f6&f+f8w| ze%(?X&!3%r1)fv-PZX9;nL7Mmuf}p+!^Zh6U$z}rINGr?e(v5K`^@hE))QwpA2$5W zlyxm_2?oA`-q@9@aAE_PZN!C#d~phDHb#&8-0~84lTD6Qgl8-WCD-elP5oEmMMBD? zsny+D{Pzz@)HqcDyA1Ga&*G_F7|HXrd+U7}&=D!0?;R$)tKViS@wc!EA7R>K@%j0C z5i8{MKP>yTK2T?U=rdJGfCs&_4To~Wl%o8DPGT`~(T~Hco5BL_8^-cvj`cVD&NoD^ zs&NfrL{@q~ERokw02$VlPtzjyr1E7dcLq7^f|_7xX!2JIrk@OWCv0K1&=kyS$?4Jf z3!19CP}seoj9A2?1xi6xqH#i11)I<g~{NHA+Rs2{8txE_z*YC>Wo=`UaXzBk_I~mAX zVznbb9|KpYr_LEw<`Gp_xqzjE)_*qW_r^tZY`neQb--=@9zHxiAlNzY?Ul&+V-)(x z`V&DXn>I+3kaNuWWYM&?27T5vUA|{VeOYx}E3puYgjM)nj!M_mSFU6dPsPl4H;T8o zXyTD0h(|8DpMp*@}X5-{t7|;4eR(%7E@e_%)McX$xvjz z3w&x!bAfWAP?#pscA5P%O(Z1iBFNFXjTPoqGISF_1(mORmO2?qnYO)^)Qu}7>&|}B z8hPuaVrW^vr^zEK+NTg|$thzwad`4tlMkQ0E7|};f0yLtp2@{N8(-+^YP8QRxwITH z&ZNN!x+L-wL819>v1TKNC&bmP{u#HQ1c&%yI>#tQj{nRm2b>af92s8AZV{2`C>pD@{JeqVKx*F%K-bEszTl8%C3V+-7ej zy^F{M0b8O^y~k%=BsYimh-4Cr9p_}^(Mm}-RT@ST&`B5;~s z=#NXtGi)vJ-qHs2(?(Y?Rn4v1chRoKcWPW-$|-+h3H5UyVd=939o&N7!uEB~$RIjD zq2HcYAgSNtyysI`BdtM_GJCTp6_M?WY$#a|22@zH*T;cn!js48&)3dgWH@*XlUxi@ z3Va#sAe+qGH$puhG0{x2oT+af!F;{7%h^aYYl){}seTcVGZSJByY3TS8_b^1Pi-_m{*n{pQCl*4tm;a5@lcBke)L18}_4=7h}t+Ym> z-nKc%sR|uEpzAhBmB-F`X9hQemx2ZK>30k<(zo?f=sDGeDfxqrKxjRec^{X;s_}h^C#*q10rrM7 zZ;iALP^oS9=`0nZ%33QAG5ot6+3n*JXEtijeyLFpTHt0JsIKsJy-Q*KP@t@`tnw#)hfw$Ly(6I+-<^cM?d%QncpWjJ9v*?(0ClcAsZ?V$CUW22Q@G z5W1_Tk3O#IwS<2}tacX;J)&EoJU5`I!A^8DV68eEQ=_uO-MKnRrMrhst;5JEL5VZt z%{*PJebF%8W!w!I=kmKL*$=Pcf@nl{J)Y*w+R+J&rTVakVM2m?eAtfh=Y_v;VuS&K zhu#)L@3yr8B*gkzI!^wf)jUk3@^kow0UwpgqTUcoV!CZgFQTHysUIRcp~D)5>r1JNouXDaX561 zaBV^9m6f=f3P{<>rL5KZ#ApUdgo8CNai5YmN1kL%uBkGQH;cIHq+3qf8NDpkqWjW* z@**V>Ckkuyi~h(Vye$&?4fdIsA{dKUsUUpBD`h8SEvnTs8^z~5{rQA$Qq&HUO_yII zCauD(Q8~`jJFn!J&9c8&R_plh*uDOH7O;Q(><|?VJ)i_m6co@r%T@^#3mEA} zosj{|#+lJSJgb{t?4Q2pxP`=(5$L$}cg&JX$^pR|(3mAq>-^D}<=2zCulq+|_Kzm) zUttcvkbkBDA^(L2#EzPI;Q+fVaTm_ieYttC2?2l*6aQEc;zDxU^t(5sqz^PZ^HpoQ z)R^9z`dNPN%(l;_IlETWUt6+c$|?LNjk~5f=$2BA!@k5Z)1-!|7k83O!-E$_f+|Aw zjcEg?T7$^umN*)nZ@(N%-py3nh#M?76>kgw(D`@yR;CpdA8Qj}ky%6E)5`sd@3>MG z?(-c1c03JzNmkJ`)f~y^K~%n1pC*0bpDcJtH|l#KKh7yJ-{x)sUt@wssbejh*h7tC zp}?3A8d4g7>ZaW&!BT%mFZ)9EvB%lP1;k8q+c{-9m5XrW+vltnk6!k3pWGxl*#A}H zwd{r zQ5YL7I-RiPS3h+29sM!iaPec7Y?7St3jpU(;N^|4A?d)2>dP;`d&%Y5+F=f{jyVIY zrn)cFEt?gtKxnZWUnkby9R{`~9b6trvA<3#`AfZnymhs%uC6*%%ymWkdX$^?szFrl zs8BoU*2A=f2)t71pdiqt5Sdj2sq{3zs3grEx}lIwYq#PS-WR3HzEcF##CD5_#a$EIjj|8=)KSB>pn=e{7*EYu&b17DQNc_M8HkfY`@AWz6e8AN7b`gjq z1i*VN+R$V^&T@=X!JbR$`-&8}%eA^UdSKj#`Sp!OUzMHs zDKYVpU9J2dm-r>AHOUR)D#2CvR8OR47Fr{k(GIVC*t?3Ln{~|0e0xszb#a*qr3`IS zfW73`HnO8#^Ilcu8MyfNG|K7XsH80lj6Z(z8If&TsEn0+EIULQl(g`&5R-FqY6 zEag@6An1~Bn&7RA`bsYp9?>nZlBS(}Ta6c*7JAFWO;wT$A{hlKQrY*^wya6G`7!9z zyrkoZ;baNC;(wFmuq~D-kLRAm%lJ7Tir36YR`uDRI8iGlhGuSGcyySg&e;G^DV{CT z?PPJG8IyJl2dEalv%mJY=3G(9cAt-pBdG1zwzw@J$RGX)uA8|t@iF7d-DnpUVJ*)C zH2i%=BMScp%}I34 zT{_#vL)^vlId2aQcY{wuV&Sq!>jkL=7P>c2V)#Qw+CTXULAidqb`DvqQRs&ar%BLQ}E+C8yO*OPAc$*I%V zamaq=*;MJ%&8<;X?1Vk-IKM!#Z0m>9>OoXUL@0CGF;)o_7QMU` z#~SuAMXftTt@j`Rl`keXK0eKE5kg7h!WKF~B`HL@j*LB*t6rw#assp5)HQ3auc5q_ z!{8u%mDjWPc}1+b3JK|SlK>%)$b4C5x76?X47=BfM76fSB4K#<;Bdc*>v;dKjGe5D z(1KwaBlF}h3!A-FuT1_Av8+_a@OfG!?rp`2ERotaZV}{E&1qdVs&y~_}8K7+8Sdw)J_@#%BD-eyQUR(0PLMO3=jJ)@gq(rSX0*( zzV2a0?bgt$j~XMF`&XGZ6_LAw=<%5cE}7EX!MA_qNLgxm2;YJ|XU#NK8!=AqyFb%1 zLdI;+(lYFH)97+Vl92tQpez174MX~5{&JmbwzH0bu&ZILPjwxq-o}S(L*Ejl)L0NS zc=eTiMt^@7xY$I_1lRhIWyh80y)-mR`%l#EGH7TegJz;&0uT%wA8_;;=n+Nj>?nSz z8>@M(G*HW~#O^4*C$pi?A(S+boZQk*Gpl@{f0HC`we5_-XCFuY1F>0v;vu|mz(Sb` zaBBePGdFQg+}x<9Ds zO@W?{QVb!h&W2m1fIz$n$(YK)nPmXhv&1%_r#^KAN9H=Q>8E*@vb#tqN2LBj=UMfC zrFJ9bdwP6a=&Y!hQg&PHI7lRD^X(p`7sFuodB{@iDCte;8L0&j$7>+KgoyVrVJ*BY zMRX)VF@ZpTG1xXLvFI*|sTy!*f;K^A*dsR{&f5ED2lAKLVo(sg0 zHA!*^i3MPmh!pgDfbRwnY_cCf@3|}mR?GBLU9+Sdwsnj_cfFpY zV#zm)f+s{->m+t{8DL5uV}K4yj%hIB?o{&~oT9~68qdvokB?j9qE4rI7US~Xqz(3D zb}d0uCz*xYzP%$xwGn&Y-`qFutj&S0~au>vUh~)-K+#Eunr+k&UW$$8#==;M(dJ9qX=sU;Fe<*Ku}0Wk4he zUYqOn(Qzhu{)f#s%MsZeV-LPJxy}4~n!Vg99jq+9mqGsWyJ~7!r{syrSIDDsMJkft|IKb-hwZ#-)JDue~hsV5*!rR5GU?M1Cj0 zkG`xpadh{QriSsP@~vfp<+O;H{)3vvu#59FFFNBtRe(A#sM>-!o`k4EFm2J0WdycK z3my3~=flx!*84N&6|pZ}9w{vy#TC`IwH_^%sm zc8!)^xCF#Y&L#b8bNIXHST=NZki}kC4<7^PH{SI}?4&QU`YyCSMrQ#kj{7c}xrlhzR7WUq1L?@hzoa7u&TP-pORdr$ z7)`VHQx3%YPv7m|k05&%qUwW5*rm()QV5*N_?u2X!M^A+vktMmS$260Jx|5zW;R#g zl6g=WR;%0gCe~OmvRPkRwZYGs&yT7o`Z?{fwK%Bnhv2qL<9IXwaxkr(XYQ7quNOyx zGz&eLqUbxh=Wg1jBk39>9LZ9Hys%liBoQ^Bni!NPbB7~=x=}e`FpTkE5G=;x?c-wnvTnjy||hSx-I@fm!uzLK7C#v(X4`cv}0+Xk1-5) zi89hO+++>qMVGSKzRulzlOD|0yU;6l%f3GP35}Ll4Gpp!6wudgBYpYm(q3tvRYs3} zei@ZMEB&rDvO?w|v>Js^dH^QiPy{f4kj9tt+TEr(+dCZSdveV$io73hnyk?u7yWV- z^&YaXo^Z>lslUG)LuRpRosl6NLasBg5~BJL+lg2?`NK%N0)yB*MwKq^$&XG4PbcI- zZ8{J^c5a?tu3j|boe6w*IqRq<@CUWD2MESynWZ ze0@8bCOudsx5h_58Z%ip&6hy=LOx-iwv7;2Ha6uOUzBM$-Qnl)9OAu%)QdbQW$I+) z#+W^slz5D*Vo+gEdqDU8S5bwN>_Fsse@g4C1WYNfO(b^3^N9P8@uIYw-#>mr)Qh!k zWFQN%lWrA#czUVxH;~@f7spyOyy(im*VFeF9}hsri-!FCAPoK8TwS4ORH7VxhMq;1 z^XQR=w#MeP&W6d2hQU5u%@L>mtfH7`Zx;IhmOT-=Oy| zrK*WxGAlIAGhN9y)V_Eji^5r5cOnL4ICHb+-wq+U{4KqHm%YXFi{f8Rbt@?0q2USV z>SEj$BFU$ZdXk+unmC^M7rzu^Ux8hm=DxL;-hZPe|Bq^Q;OF!y7H*F&sl2Wa?LYg_kc+~8Yn_A9f9QUhLs$Z>X@3JPvxO%t-y4nRhhzp zp^7Gi;%F895sw5caJT-JbMTja{I4wRz5C^%`I`xypFj?S}lH zo0J3mZ2&qX@wyE zMB9w}6Kzx8b^vHJ6YpgKb*8l|U;&n;h-oDF3HY^-9|25GwTa=xW5Y6pd3J|QV-!2?{0d;r;x;9##?mqlEMB&ADi{J2zL0h z1Qfy#_>d54VdxhuqC>etdvUyJ|6;Kfb?hk;2;!Cve24K#5W#Ef@>%E`%- z)25!*nDtL%k?pt}hlkb;z(~^T3;*FX(o998F}2I*#-93p`GT8{7o=~5J3qN`DVwUt zDcpH_?y)7P$mqRM1yjLpHdT<)xNDzfeO=u9Y^rn(!PM`jUux2YE>!9}```Oi_f`ya z`&u5Q(Z#~>>`R@Kp-%Ho-+(=nshiN5kPqTi98WM-LgY7Yd#3B~lwZ4#d+C^|j;p?^sKoL_vU~JoGE-@8^;*^ zmSndN{#qs?UyKK6fF5mUr@%?n&aixp2c-D;s|OUT>nUqUMj!cjO4j>wu#zOm>H_2* zX7Lve;1{2!60yKY>;<(x&IjW9#i)u7Y|^GdIJk5%#R}lM?&?PjD~rU7Tvfi^J^x|h zJCg7TP$eGzMU_}|p~<^s#LOz+ED%&EcwGKNiKucF*ma|Ek97zxetN$_33u^`BVIM; zqW^+{*nRr(GLXWkP4Xl9z6bCFp)2V8s6a)Hq!xEkj_{wD6Fl z>*gi#xqypC0v54(J47;A`D5IgzS;3Bkg7BO<^H)ubU z$QT?kMD*AC4T903OHKay(mz+@pL^qXz}O>1*Kg2mMv`#wacQ*4DiH33E(`)e&U7Zh>J3o)}h9fCJ(*B=4 zg8yF&UH{eV{XY$H6aQS_|B_na|8#vP|GB>Z(Dj}Ahp+FaW{pyT&}D+YO`@N8x`U^0 z*Q7f8EiiT$1argb!{>n$s^nDWzJ=@am^=J;# zvl&l7KYWo;c+6@)<-JiE)_YArduigD1usRm6Nikr%Dn6@^|T+6{F;U`i#ugBD*;^Ka0Q zS`P{Dbxm!;ZB(Z|RtVY-*u{xs>Rzu(kk1!nI;<@F&RmmvbcboZIJO@n1me-IDxOmS zI);}>kB}z<*vw#z)(K+Fqc$LI)o64r`EJvPd9VKI(EEDb2Nvy^Jwd)o!4?qYo^If-;9iI{R-so0`oS^*&**RxJ+=3`s z^hiPpfs}oJ&z*B;pR@OwIWud`z3a~G#mdT`(3J@50B18RGINI#6_lspy*^bF77`P6e_J2%ekRmJjv{2mRs z9BQ@)pTPiJM%-|EyrF*msO*el=MxPo=hD#GqerUxJjwpRP?=$nWLjIKTB%ze;^DAc za`z*aOiO+Ga>kXT?~$*=MRqHTDc|y@o~#r)%U^5Fb}QLT$?wlx)l)gA?JJn1ODQh; z<1}9k5ejrOy!7L43ueQoFU`4hnByiaqMu!sEEsn~zr3^_raK`zM+44h+%hT zqD)}!E9)a%^;6tdK057S>3PYQ+xF^lkbjR=AW(Fw{DVXWMcL{xZsBkkb#SWG%p7_6 z*W){XLr(oyqH0(=(Hh^fhziSM`hZQ3b0@wM_+mLzK!48z<;`=99!;{pla4X&M1RjFctF3pM6y zEGo={dhJcXX{xwtO|cb(Ng+lzVkvt04Ar@L*k@>U_D=tMa{tiwNzi(XDSY%Wtsz74 z?09+SIn57O>HGMU$3qk;3xgNnMl@d+>X6;L%tQ*jm5TRLDYwM+oh|u^V4wx#^>^1d zK88Q$xCMATV~7(#0w#xeA8>IjJD{mRc^6VKO@e6+AI`w5TzurGBU=~AW@CD?fzg_E zZJZb4JhL(7YCJ$BTAqtsq=FFLL!iK;r41+tbi*7o_#Gg6>e(I+W*{Nc(44((mp#2b!2C^drjLH@TW9_U%$8;LKK>@c*6T|B0nnX7 zY-uNix0S6hK}Fxrp>SS~G+vnUK#+MSw6gFh7M-$?fqz4$1&G&pkxbC-R-m?+qiHh? ze2x9F0}?mRE*&=hp1-!bdR$KFhvcRENnNaNA2VP56z$9a5$t+`6)}??NLpt#S*-W+ z-OYf9+V}F^3#;ETmi_oV1gZiB>yOfg04W0at-fw_AinCtCFby%U2q8xt&HyyV49l*{YDhYt5W z4xN%8>w`~v373QW8>M#k7qhEd`v%WB_wh7#VK}MB&Q@w|vfkrE`h8Eh4^qplXQ!CI z=wn>?t<^S`?K=LrK1`2N6)Z>?8~c6cn$7h%Vx1hI56qW3i;{ZemLbU-s7+`(*Zp0x z9F|427(2A)UW$&XbECb-H|8e$qPP6vvqv{x^FN5xX1pse{S)sjH4VX)0+PXMo81eh zDFj$&I!jvZaQR#R)@)t(LA*@bo{L4*)yolL8Mg>@rY5K@bT1$un&{HXQB0ZHD8 zpWVjwiH(&Goy?C{?+;;u5A9T&%;7uRgfsZkWvvs$ZPL3-Eny?r3AESLce|pk%d_c? ziJ6$(0Ee%(bEnSWo0j@h-@hRk;$a3=v082}D=jh{xm1Z&1^HY>y}gT!2VjyS(31;J zeeaT?6Xg{+$n0|TRp3qh?gg#!AJxq=5uKX{aMb19E#o9F; z?p$A}eWz~?goIy-t7Hte*h78u#F@3?Zx?Q5$>8&Iy4+UIxMxgv>I=f)kE&RHkz#p1 z$M)UF-tH)@K#~3_80(V)dA4Bt3uIA*#lmx&Io4vPLDee7`{EU*nP&913H z3oH&!3wM}0XV9u;mpXNwGaV_sKQdFZ?ovwcpQOe(`Dv9|o&O!F1OQGzdlsmgjj}G) zHOM$wxJ~5Rd2K(Jc<;6%Sr&gqd#_1%Lt~X!d=ElCGgY!x53dc1%zvTSFy{3JUtw(c z<-v`!#kQl*+0<2QcGV$;C_p6@vOG97*fD=$oG4o0H2t!%>!7~+eU6Y=OcEy;E;_U> z(CSw{IK{oP@Eql62-@PkjMdJzE3}&0l6sW%?F^MYn^`qt(laaQ30w-m4Cv1BB39z0%XgV%BkN{89eKN{{m^yVBaRpV5XRx=h2v2j(-Mc8Wvc5O|`jw<-$lbTzaz6h~9Q#|7{GW_z zC;p3lRvhMSDi4S2?TlPMtY0-{EEJO>sP4-qdz2s zomNsfA5ycVV*IEL7+eurkZK(?A5QHYR;?w+Sn)FaMBjt<+rYpEB9Bc$c!4Q zrC)#s8vLc`Pj4*uJp+qfjwV)PEqc9q9Hq&##(8gl1+}O{vH{-7?iOEM@)*3E3>jIk z$FN68n(Ebld_y~8y+D%^82;JcgnoxptfZ;ZqIDLSp>Ta=gd7%enJGDA>{fKuqY!rz z#bgHH_-gcokU0?Y7>7?4;JX%^K4`y_vHG+vN-+&?4W4S=vXRP1)kcu z517H7J@4%8nn#T7ug2^-#ewQVS0fYoH&4xrrvVa%!d>QkWdR$7^A%~t{ za!Fi5Dz0}x^alejCp?%wKmTTEA>_;(yWF4*=?AV~-XQrz&U{C&I9`y3zXXVhO%tN$ znMS=TXb~EtM_`p77($ZbO~x24!zu1&U~?bOHe z(p;?R%=UING!v5y-b~9LnAyJd0z}F-zW)JEgv|jUxN!s2QUGdGb>}*>zkT?3Htb)_ zA5s>2{t&IUfGfq)JR@rIRNwuEZ*iAgiKP^1bGmlkj3&0aC-kslGXRxQF>|%uH;b9{ z4&mk`CZkhi#c*@{SvUTDF2e`pjQY>cmx6o@3~fhnnVa>SpT{;UEEWr;%DxDEkzQ9z zP;?c!`+&4c^iFM~TKC|A4Wd}o=z;+}f3-ob!}8TY&w^=v`Mw~ycB7~32x(W+jHCNU zA3xe7fE=dA-5MzjB&QfqQ7XB4_34hT#904ez2`pY9#`0l;Hl{A@Tu$`2b(=2YG>m7 z{3GbfLG=r_k7OwYchwEbBvLHO$ufJIvJK{wC3JTMYZ!gY*!h&u)P=7mQG11ER2v#79cWZ+p9oFW2@9?i5JGC z$}XL$a0#uQ6Vm87V-KIdrMxVt^m(Jpk#esBH;-@g#iCZMcxf=Y&c$8@GFkZ-MI!M*VDH+6>7@tZgREH!(F?Sa5 z`*@(W$NCUTqTnL#8pgFsL#QQMsWxIJ8r54H#|}C9E|1JdsojtJ1i36tE!mOyBqgNo z|sgB~UjJmFgq_efUM-Qlv;x7%x1aodIuy|W@cluzvmRl@XUud?z`tBoQr`qhlB$RkRd^|*q zNzr5W8u=;886J9VVP_QkKzWd%8MC3VpQ+O^`PpEDOP=1X?^n>EE;om8+I1#jAE+Jb z;ovlHA5ewuDBAVl8L8Bzr?dl7+1TW!f5o1Ja(}?6zHU8^XNcxmY6Ie+}kxDX62DuYZ`wN_wE9_>90kMtoxtFP9CygUGB0?17eA19McnXvOa^_TT zCsePj_0S&kP<#ZPECYBlihuT2v<1a({G`#35IZymXz_rMh>3LC=oIVE>aS_S$#`cD zzyMh$0R!s5jugftX^CF>IZXd$b@|_BoQao6<;aI99-;(x?x+hIb>3GYn5(JDO%HBJ zKgp%qCy%2q-9H_kJp5VF7zn_rOEwlT#z30`o$m0t8?B=Uss<+_olZz!hikL-igWy0 zDo<(%k;Yqqp6sL$Gz3BCjqp~^W^|I7KWm9*vZK^b8QT?Y65dvagk>|Rok4hzs?-D< zVw;_WRs-x;e>sFQTWfxAuhI!!oi1SGY$p~ylh65Hd5sKs6~5{t2+^EPGUt%n#Hhgt z`dw{Z=rtXBF*Y3(WwO6;ip8)Nk966UcRgI7k`j;^We(^usBnm88HSD~T=ZLLx4G7g zZ^EQ?uMu>|+}{{~j5c7vl}jEy_DN!6dS&PPHFWZ&>pBmNatVbOfuRMKZVSn(-n<}w zFYR=vsCUV?eq3_}s7X^+3>}}JF1V++4sG=wgyVJE1Il}*4{x&@G-m_y3@`5HBTjzU z3j-b)dJBpffz}(w-*3zs*th%;>o4v4Y{n_T>vmk{xo68XpqjJy2Ef9h@Q=TOY6{yf zC@exc#357`UmMc9GPX^c&8CH$(z(B^XU2f;4BEb>(5-N(3jq$uPeh-3BlMNIw{cW!*zNoIcI{)trvBBm z`_Q-e9UX0gm+xM}f_oWq`1g@;zI9NDVX%-i?hV7@ZOh|2|oOiGL1_8Md3q8+C)2Xo1A$IbIHc zc<$>p6|&)V2+l-ZEKLtI{>&1bZfeeY(}p*jz^;GeIc|2ok3VmOGDOWuUENT<<*~~V zIR=FFm^xaL%*L25X-6;WtCxGaoql7KsZD=WgV`w*L`8$O@Ac+Y!A0+7k6SW#>DRDC|DbGGh0sIVrb9`vJ{P#@n@P4T< zuYNtGa{|-NiF7bcLK#QZb6hAXpL5_zY~bWUofgR7utyoi^$o#?^GWee4yDI3cLJ7C7XbJ*gq7%oBIWSa>7#83H$TQK5wAB?-@dFtQ9mWPuSWq_ z02|ILc?6K=OEA+GmMYbj9Xy809;rzb=^o~MPlNhF2v33!Q45Re$PVq^{$#4>f-HAV zbh>J#9y{9QqH?wO;Vv|hEaK#$hZZOpgzN|io`CG{djQte5!m`-aPX;JTqhuugv92yXE9*CJDGB~ z8p;{ISxa2JB+VMv8L9z)ts_T}1Fp70wi&s(zi(z+jI4{&i`hD&dN%ZtatA#LnzRH` zal`c=>hW^r`Cm=0_gK9Q<$tNTH(Eu3(k{wjlEn97vOADmtq|v6FjnS)c;|xKQ&!ou zbVsICmEa9S0fDD!rfqUWqMr?68MTv+ZP!cdUUUwIG`d)Bd+E3Y5)|6R6R!;Sb27$_ z*;|GwlG8+T7!{U|;%lXz+c1p+dVxKUs>T-YnLRW~zA?wE#CqW6T71Ow(9=5gKfvSs zueD{ifPb~DB9yeyZP?l39suxTM4bwtd`c^BR@Q<=HuZI{nGDhVUjbD8*0TPi<5K7e z!Q*6O;e--!9e;EDPZiPr(VPDD`QQ4YW`DD?ivCNmmjB88$Ug_WvTXDv1{4Aw`Y&9d zmVb|V`?s&#cwjA;kUtie8(ZG@FggkVI_2#8$wGA8o7v>8+`tY{vhL=c9gRJ zh(X%k_w0uqd~scdihO6U9+FFor#2d`*Q$z8=}xvD5?^=2W*$W9fBdw%q@l>Rn9Xjw zSlG2dW90|8z&?s(?KqlXFD|w`Q}!bfk=#B1NSn!cD4xL|=)`|!b9J1|j<}9@z;ey& zOcDih<|=S`_gm_7?N!TA4w?3x=8~m}8p@MdIYTs6_@)(xxT8;*F3Xc0mbH!XbfN`S zS$=mfCUdIP&vCmwprlZS-NV>*S)k(8&ilD^HDcrH7)*K{K3pkG=X|Pr3=^hIu)vRg zg6}1uHOXx!>EtxkAVUHW$n?@+V5IPK0PMpVw16MQo}XEQcUZf0n>1d=RdVc%A2az% z^_^X5Xnc09LU=IEw1GVa_qwwXrcC=(bu3D-!r-SgMfk3$0UlR-xj#%)~(c zsz!CcA)py^yHo{{U;Y+;O+0k!j{jFCdv!IO2<0T|5%M|j73@(*((>-J$@(*@Bp~X# zAJ11=SsS}U-R7D6kdcNmgel;Jo8X}PD=2&0gFwt)fxk=&9^hqoflG&n>lJNvXj*f? zcP*1^j`KNNF&uPBqihE~#48Q$6(@9l;lF|?699H^b;zSOPdBe9eJ2xp8tUBp9cr)H zQ~_3PWCWn91zkEODlPTB=zA0RqM*s4C4C@Kxuh6edF6v~S3u7!{f_yl+SU!-kVc zR1yRidp*$P{TIQr+M4dv3g8|qF5)_tEddQ)=|xy$DE9ARFr$d8c{8(cI(@b<>gP9} z_E2dt%T!tgFrLm~s%$+UJRHo5ZHbGF+zH5hpL;&OVLEbdEmJQ4nWLSi^5q}zRP&#U zhkxF*ImeDpQ5{cD_(AUSk>HC*s|fi!<E+_IXx+CNhJEvfgT!%{2UvIlGVr4oW4c&CZ~Lv1BYnphytWC+1noN0n`n#65gc=%r;D7 zYk{|Um&&GLsi`+V$Cx@=f8<-_nVeL+IF78WWze_WBLb*wr60}BE65aXwOQPNG(01l z(gWpaifP>PVDKyLXq1W~Lj3&;Zl0v(*m2a&$X3g~Vd^E!uI@?0gZ-lR37Sjtr2+1X zD5qxVQ?$2pTh8nLQg^>E7~^=|9%s-oyaQT?>@wus6n&3}Ey>}E3qS92S!wv>bLKuM zJ9URA?21bc(AFA>-~API0|sP*KIQ!iqFqKgsJbl6hZDq|d>7~rl#$Ha@EL_(obqdP z);+Fe>)nre&+K(=6nri0_I^?=awF9Vf`_pG3c3UX6U>~mfI2IMAJhmR zo!9ZsWdxrAsk=M#Stwqj_~|)%UUgqSnqA&n@QJh*W&Yo)Gyh8g{6FSD+8ATGS3EjA zB96}cv?`AyYw}P!$>7!AOW!rap2mD@Q{5ilUaCmI)lVm&r7=jukCBpgNk48Se*R@V zWeqBmb$aC>he;Fo|G$zlQ5%jBJRJZC;C`>!gQ|CEXIY%H!b3VY0A7))Y=WxA!~=Sw z8_iQ{V!vq<7Ses%eLZFd7hILWnlz!LHg#ViM^OBJy|#{Gp1?9qY%QF{k@qrQ$zjPw z=|Dm&cg#FcY0BN?tgU+0c|?sbWvmg+t1Wna^*thFNUApTY4>{16V<8-*Y~cgbeA6{ z$#i!(3y?+_b-Vor#D921q1X`Tur4}pEHSFs*82LF8cNd5kCbjkf71jfgV9|GI3~So zo(bcy6VR$WRvUM1-)uPTN4M<9Fa;;6$POKfj{=@dukS^9R&o(Mmm2$o!@S}EjRC#d zVW~rIMJcJvJEMK^qWS7wPbS3}8J}tZl(Q<6D@cF@nH2$P9G2_E%w{x1WX{PoZE$-{ z*v34Yvg1h_OZHQhVZ1s82iW#q3jJs&m)!d z=`DzCoaEZuDsFu*qYt#I)F9|mAv$$Q8O7;0vfV=#37gcg+?DD`TOR)!=UlelwW;%I?h;ZW@|IjkbybNPLzcelQT@X;Fuj zYVTLp7|E-9cDuO4-%T*_{XvJAK$_rNMw);_V*tm%WQjpW9~y%3Yoj-Y5~2(5Rf zzvpyI_g^4SZmRQE5u_0}u{vGQ=muT^tV^5FPC)thcW?Zf^Y(tZ&;Gs7Grmynz&h9~ zxFdt9(_`nO+-OAyz|oy-T+i3ZRJO5uTz;Ew=--}T3+g=3k z{|G>{SjZh{PQh+vCy*mtXm8Gq{t8+dheAlFho}LzGfB*zd;#4t0}QWvWe%e$u8@=!%t%zj3zz#9w)0-Nt$e30qmBQA z<(t>EWs}C*E3PTm-sZA=-t7vywGobcoYWrDzZh(wq&WJax8hB(FhZI1UhVScB%Fz; zJtyP$Qd&~Hhfx0EUbF}Uz@KL;H1xR(d>)a^pJMxpa5{lG|3evhU{R^g+3 zVd9%iQ-x$*Xc%gTBm3l>5ls&hCR_tAM^GoSV7g;(HJIuY^%pHDob;8ZoC{l5I)=TI zvi)HYT#T0>Kszvlomqld)H82^Wyo2Vh7`6jAuAOhS3;`NrodZH=^n5SUCJ7g3$=<4 zrbAAu0$*LLCFr9%Bm>T>qOc;xMxJ8I!=LM<>98pE3ud>(WZANmsIB%8G-D1Scw=N8 z4e9(nD28M56~bU#jSn!l^|#Vd^apQ(Tr7AYk>(R?#`KT)699!rCCWm%t!zvs%60cd z7|?^H2YktRBbtd;mQn9aNc)L->hcBkt;Wp!yeo4HEVZ*=V<>y$XAQZ8T~-7rDV;UZN0ZoiyOd zd08!wg&3#z$JH$$MbCpwa3eZ-FG*-KMW_3u+3-1{h6-9^dnsDApVC~1DeKXLyYIvT z)wX31FnJ&m04o*7N;Ye}_gm@0A>AhG=H%xdJ(v{dFHCJUbJa>y;yW)4;S1OZUe^p#;GX(GI#q@eu*PYx{IOa zO#^tyHF1@fojw8$I{U2x)vWXG8{TtCt#pYbeRJBW74oqzM7q!a@pQGMA*Uiw)>`XB zyxE7b7!0JOpk&GLH0J8XK?AwZ4OW`B9_Q?akBiSn#QUu^RZMa6 z!l#^8p6fT&*O!#Y=*fHmfu8DB2$C{e4*?!beJsEpa0cR`-@_Vf&bWSQke*xvehF(D15Og(Ul2cUdub9(|9Q&uc@+5O;#BjrN% z&0}&>4PEJv_8BoZth@xlJr>(p$W!R5P5zMZl*Y@>8M4C|#^|VIlWc=yF?5Xv3Bm+VY7|PwHxUr11>@wZSnk{yQ*0nXCrRHVz zi;2W0-Np4{!9*qi-r(vH0ikae#~{tHuxNymoo%T;l1_D2?3K}?GC9L>LojZbBlncVB&(64{LIc4_p0g<#ew)b24GhWzJPzN;XhFO~+FXWqX!M(@WZG|8&) zzqS&ZkCf<`kAwiq1eJBx0cMdoXLN2_XpE$VKmn4!-!!q9Ih5Wu%gzUxCO3Ei+1o?I z9v~qi7cJ!O^jC+;kple(aEpJ~=#Btm*>B)mFe|v%D16W2!fE~GmaDK4{z_2~{^DDL zVvGA9@5+M0Yl}^{M|V#|D3_y2zn~l`^0Y886Gojc7ON~-Q^z#>h2%k-v-s>^oB@AB z8lgFKyWr!px#bIM2|>7wR$bheZra!K?#jlVDYFUiI*}Zv0RT|Oqjs67i#b)ZXekwL zBRKlQyNJ5zL+Zn1#c-K4!Jw=wepK|8oJ#1yZ%Ar{REeL|l1^l^RC!K<(dfZ9yN|KV z^HzE84?i!a3OfnJ#*2?`f;5{IcWvE1<1FAhYLb z@_P-{7c6MF&9FM;bm0r6GcI-rB8woSSUvqh?I^_JgoHjp;~J2~Zt_e;d>tuZ4zMxM!Dt zo81I7m5Z)w_|mnBn16Vhmlt}MLW$?8_*My!vN!BeyExV?yd+`R8DNm4*5Yf+^oBRn z^=AK*Fz}srrMWO&u%2R9us6aI+tVdAtu4jq$8;MHufY2YOz<^l>>EYdM^Z%Hs%d87 z7b~ri7EFl+XQ7H@|Im1Wf19}d?J->3k8*K07P3`|Z_1|WGnjDukqWOe6cL05XWJEd9;5!zx0f<2VRg;n2nly1=aMHMsOj72T zV6*!^i5mPBbHu0k!Jt5w5^KcimJD2{u7pFH`Yuxj$Sv0H%a2b3r-2Z<5m-hQQyoi3 zopgYeA}l9#wXk^=H`~}Xytw_cG@&MLP5m<``8sV5)Bc4*0V-HNf)y`ROytJ0$GjW$ z4poToc1is>Dw`b9@-{AP{i`qsk#R8JX-EeNhI@p!$4uI!v7MhtJ9&G4M9=ODQqtk=*Q!(p z*TnCyY->_NV~*f#2+c_{J)W+e%*snY(X8)(Df~ihT%P)o^vvu`d(z97-3cLgF@YgZ z;Ksm=lnDOJ%ZwEcCo^k#ESH|~>YLLvZf-rFPHW=0@|t{m%ilzOfQgS+T?IB)`+GF> zKiuAaZiOae(3SDOz#`11^NWx@rDVtna}K@8*0kB(t!b|uIy&?7ITo3}K}U>{4vH)a zEYsE^uP_tt&SAU56bgl8s^C%!ZzbFWBR5=o9btc z)e9GmXaFvoLse(_ZIe3fF|c*XDK@7__4W`^#ghKE&!z9hO3OhWQ?qo>G$GB3wm^1q zvec|kL)lWlCBREy45mlld(A^L8*Z$A7663*&I|pQ?A?FE=e1;eDLgzA3{BNpY^><~ z^qHFdjCZZJ_MSTJ4;;ztx@Uq8Sow#jK8uaSL{i@ahDXAn#aWI`Y={!jz>{+t0|PuU zN2+31KNmZzy#%wc(a~0Fi}5QPQR}c-M_SvOO*vA$kYKUWcb&*u9b+VlII$K1!Xy%0P>uWnFjcN|6k58iTMtQ+M(7nf>A3v zIP@fO-*A%XNLljkBOEW2mk+X6&~omiXXzjmbUcUopc7)WV?rk=M3(bBpW_~o=iT%p^v?C7~=sp{A%W1 z5BKa$+rB3feeQ{5Hf!@jtsH8zQZdnzx@+YiRqDIOw~S4}(;>Xnx{9nrR}Ht-nE+yh zcR*A*Nx~O2lmi4x&tk_OKFZ&`Ezkmj*&0=kzf&H2Xhbb<5t>#Z^ONjNBl(>u?8}&z zBSq*<+ncQbNmqyn$ELj5$V-5dD?ME&9p+==?*%sO%k3-8vgWl0neJ`uZLI?@sIw9q zRT@8jAO1c>4V6rgeDCk|=ZXKrh9`dtCU0CFB6fOnN$m4Yznh@4v-xLCBYsl1rHa{G zBud;YRcA0OPIWSKHWC3Dn}o)wE02Zv=1^0z9v&7P7XO8&jS>J@;epmE@acY;ygUI} zz;of`O;xz*1iuLiiHR-0Cou8KJVY$D=*xI@?MUkw_*TAbxuvLHb%^Lr-jw+l6IXyo zPPJ$XePUMr(hZkG+oX!RDIXY|>f%wy9aKc|e$ZniE^8s*Vg{|f;0!9QnK{UHXxKEb zCm1(OhluUqNFsl^wsq{!i~k(3|K5|N*vBmMWs?X+{g=lUm1i@nL&Sl_S{!EDPB5a< za8|Fy0Dqt%(Zzt#kb^?%!yFK;_XVfL2O%J7{lA1_l0ZiqKz(*1&?28#kfhG=zn86S ztm#J1>COBgF)Lbk2G9+i@=L=eBlnG{gq-9FcV--C)S2B_-#7;q#MOdNP0Npc!{zdh zff@fi`g08a&pgNnCJCCgDX7G{Al(gg<9ueVDQ;xZh!XU39u}G`4pEqdX82G`fcQA_ z|H2^omKp@|Ed*MukN^=B@#jR$4-);*rKbhb(SkbWO*?{XO%_LtzMr~H2bcZfcA@&PiY z9Q{zo{G3yfhd+PBpBXhIPz?F`L;g*%J(mLbWuwc5M7RHOVg29C5B^8WCI64lrM)(a Q5e+X_`$G&2^w+8X1u%?H6#xJL literal 0 HcmV?d00001 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/index.rst b/Seg_All_In_One_MMSeg/docs/zh_cn/index.rst new file mode 100644 index 0000000..ce5e499 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/index.rst @@ -0,0 +1,57 @@ +欢迎来到 MMSegmentation 的文档! +======================================= + +.. toctree:: + :maxdepth: 2 + :caption: 开始你的第一步 + + get_started.md + +.. toctree:: + :maxdepth: 2 + :caption: 用户指南 + + user_guides/index.rst + +.. toctree:: + :maxdepth: 2 + :caption: 进阶指南 + + advanced_guides/index.rst + +.. toctree:: + :maxdepth: 1 + :caption: 迁移指引 + + migration/index.rst + +.. toctree:: + :caption: 接口文档(英文) + + api.rst + +.. toctree:: + :maxdepth: 1 + :caption: 模型库 + + model_zoo.md + modelzoo_statistics.md + +.. toctree:: + :maxdepth: 2 + :caption: 说明 + + changelog.md + faq.md + +.. toctree:: + :caption: 语言切换 + + switch_language.md + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/make.bat b/Seg_All_In_One_MMSeg/docs/zh_cn/make.bat new file mode 100644 index 0000000..922152e --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/migration/index.rst b/Seg_All_In_One_MMSeg/docs/zh_cn/migration/index.rst new file mode 100644 index 0000000..854b9e6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/migration/index.rst @@ -0,0 +1,8 @@ +迁移 +*************** + +.. toctree:: + :maxdepth: 1 + + interface.md + package.md diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/migration/interface.md b/Seg_All_In_One_MMSeg/docs/zh_cn/migration/interface.md new file mode 100644 index 0000000..42f91bf --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/migration/interface.md @@ -0,0 +1,523 @@ +# 从 MMSegmentation 0.x 迁移 + +## 引言 + +本指南介绍了 MMSegmentation 0.x 和 MMSegmentation1.x 在表现和 API 方面的基本区别,以及这些与迁移过程的关系。 + +## 新的依赖 + +MMSegmentation 1.x 依赖于一些新的软件包,您可以准备一个新的干净环境,然后根据[安装教程](../get_started.md)重新安装。 + +或手动安装以下软件包。 + +1. [MMEngine](https://github.com/open-mmlab/mmengine):MMEngine 是 OpenMMLab 2.0 架构的核心,我们将许多与计算机视觉无关的内容从 MMCV 拆分到 MMEngine 中。 + +2. [MMCV](https://github.com/open-mmlab/mmcv):OpenMMLab 的计算机视觉包。这不是一个新的依赖,但您需要将其升级到 **2.0.0** 或以上的版本。 + +3. [MMClassification](https://github.com/open-mmlab/mmclassification)(可选):OpenMMLab 的图像分类工具箱和基准。这不是一个新的依赖,但您需要将其升级到 **1.0.0rc6** 版本。 + +4. [MMDetection](https://github.com/open-mmlab/mmdetection)(可选): OpenMMLab 的目标检测工具箱和基准。这不是一个新的依赖,但您需要将其升级到 **3.0.0** 或以上的版本。 + +## 启动训练 + +OpenMMLab 2.0 的主要改进是发布了 MMEngine,它为启动训练任务的统一接口提供了通用且强大的执行器。 + +与 MMSeg 0.x 相比,MMSeg 1.x 在 `tools/train.py` 中提供的命令行参数更少 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
功能原版新版
加载预训练模型--load_from=$CHECKPOINT--cfg-options load_from=$CHECKPOINT
从特定检查点恢复训练--resume-from=$CHECKPOINT--resume=$CHECKPOINT
从最新的检查点恢复训练--auto-resume--resume='auto'
训练期间是否不评估检查点--no-validate--cfg-options val_cfg=None val_dataloader=None val_evaluator=None
指定训练设备--gpu-id=$DEVICE_ID-
是否为不同进程设置不同的种子--diff-seed--cfg-options randomness.diff_rank_seed=True
是否为 CUDNN 后端设置确定性选项--deterministic--cfg-options randomness.deterministic=True
+ +## 测试启动 + +与训练启动类似,MMSegmentation 1.x 的测试启动脚本在 tools/test.py 中仅提供关键命令行参数,以下是测试启动脚本的区别,更多关于测试启动的细节请参考[这里](../user_guides/4_train_test.md)。 + + + + + + + + + + + + + + + + + + + + + + +
功能0.x1.x
指定评测指标--eval mIoU--cfg-options test_evaluator.type=IoUMetric
测试时数据增强--aug-test--tta
测试时是否只保存预测结果不计算评测指标--format-only--cfg-options test_evaluator.format_only=True
+ +## 配置文件 + +### 模型设置 + +`model.backend`、`model.neck`、`model.decode_head` 和 `model.loss` 字段没有更改。 + +添加 `model.data_preprocessor` 字段以配置 `DataPreProcessor`,包括: + +- `mean`(Sequence,可选):R、G、B 通道的像素平均值。默认为 None。 + +- `std`(Sequence,可选):R、G、B 通道的像素标准差。默认为 None。 + +- `size`(Sequence,可选):固定的填充大小。 + +- `size_divisor`(int,可选):填充图像可以被当前值整除。 + +- `seg_pad_val`(float,可选):分割图的填充值。默认值:255。 + +- `padding_mode`(str):填充类型。默认值:'constant'。 + + - constant:常量值填充,值由 pad_val 指定。 + +- `bgr_to_rgb`(bool):是否将图像从 BGR 转换为 RGB。默认为 False。 + +- `rgb_to_bgr`(bool):是否将图像从 RGB 转换为 BGR。默认为 False。 + +**注:** +有关详细信息,请参阅[模型文档](../advanced_guides/models.md)。 + +### 数据集设置 + +**data** 的更改: + +原版 `data` 字段被拆分为 `train_dataloader`、`val_dataloader` 和 `test_dataloader`,允许我们以细粒度配置它们。例如,您可以在训练和测试期间指定不同的采样器和批次大小。 +`samples_per_gpu` 重命名为 `batch_size`。 +`workers_per_gpu` 重命名为 `num_workers`。 + + + + + + + + + +
原版 + +```python +data = dict( + samples_per_gpu=4, + workers_per_gpu=4, + train=dict(...), + val=dict(...), + test=dict(...), +) +``` + +
新版 + +```python +train_dataloader = dict( + batch_size=4, + num_workers=4, + dataset=dict(...), + sampler=dict(type='DefaultSampler', shuffle=True) # 必须 +) + +val_dataloader = dict( + batch_size=4, + num_workers=4, + dataset=dict(...), + sampler=dict(type='DefaultSampler', shuffle=False) # 必须 +) + +test_dataloader = val_dataloader +``` + +
+ +**数据增强变换流程**变更 + +- 原始格式转换 **`ToTensor`**、**`ImageToTensor`**、**`Collect`** 组合为 [`PackSegInputs`](mmseg.datasets.transforms.PackSegInputs) +- 我们不建议在数据集流程中执行 **`Normalize`** 和 **Pad**。请将其从流程中删除,并将其设置在 `data_preprocessor` 字段中。 +- MMSeg 1.x 中原始的 **`Resize`** 已更改为 **`RandomResize `**,输入参数 `img_scale` 重命名为 `scale`,`keep_ratio` 的默认值修改为 False。 +- 原始的 `test_pipeline` 将单尺度和多尺度测试结合在一起,在 MMSeg 1.x 中,我们将其分为 `test_pipeline` 和 `tta_pipeline`。 + +**注:** +我们将一些数据转换工作转移到数据预处理器中,如归一化,请参阅[文档](package.md)了解更多详细信息。 + +训练流程 + + + + + + + + + +
原版 + +```python +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='Resize', img_scale=(2560, 640), ratio_range=(0.5, 2.0)), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='Normalize', **img_norm_cfg), + dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255), + dict(type='DefaultFormatBundle'), + dict(type='Collect', keys=['img', 'gt_semantic_seg']), +] +``` + +
新版 + +```python +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2560, 640), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +``` + +
+ +测试流程 + + + + + + + + + +
原版 + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict( + type='MultiScaleFlipAug', + img_scale=(2560, 640), + # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75], + flip=False, + transforms=[ + dict(type='Resize', keep_ratio=True), + dict(type='RandomFlip'), + dict(type='Normalize', **img_norm_cfg), + dict(type='ImageToTensor', keys=['img']), + dict(type='Collect', keys=['img']), + ]) +] +``` + +
新版 + +```python +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2560, 640), keep_ratio=True), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +``` + +
+ +**`evaluation`** 中的更改: + +- **`evaluation`** 字段被拆分为 `val_evaluator` 和 `test_evaluator `。而且不再支持 `interval` 和 `save_best` 参数。 + `interval` 已移动到 `train_cfg.val_interval`,`save_best` 已移动到 `default_hooks.checkpoint.save_best`。`pre_eval` 已删除。 +- `IoU` 已更改为 `IoUMetric`。 + + + + + + + + + +
原版 + +```python +evaluation = dict(interval=2000, metric='mIoU', pre_eval=True) +``` + +
新版 + +```python +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator +``` + +
+ +### Optimizer 和 Schedule 设置 + +**`optimizer`** 和 **`optimizer_config`** 中的更改: + +- 现在我们使用 `optim_wrapper` 字段来指定优化过程的所有配置。以及 `optimizer` 是 `optim_wrapper` 的一个子字段。 +- `paramwise_cfg` 也是 `optim_wrapper` 的一个子字段,以替代 `optimizer`。 +- `optimizer_config` 现在被删除,它的所有配置都被移动到 `optim_wrapper` 中。 +- `grad_clip` 重命名为 `clip_grad`。 + + + + + + + + + +
原版 + +```python +optimizer = dict(type='AdamW', lr=0.0001, weight_decay=0.0005) +optimizer_config = dict(grad_clip=dict(max_norm=1, norm_type=2)) +``` + +
新版 + +```python +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0005), + clip_grad=dict(max_norm=1, norm_type=2)) +``` + +
+ +**`lr_config`** 中的更改: + +- 我们将 `lr_config` 字段删除,并使用新的 `param_scheduler` 替代。 +- 我们删除了与 `warmup` 相关的参数,因为我们使用 scheduler 组合来实现该功能。 + +新的 scheduler 组合机制非常灵活,您可以使用它来设计多种学习率/动量曲线。有关详细信息,请参见[教程](TODO)。 + + + + + + + + + +
原版 + +```python +lr_config = dict( + policy='poly', + warmup='linear', + warmup_iters=1500, + warmup_ratio=1e-6, + power=1.0, + min_lr=0.0, + by_epoch=False) +``` + +
新版 + +```python +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] +``` + +
+ +**`runner`** 中的更改: + +原版 `runner` 字段中的大多数配置被移动到 `train_cfg`、`val_cfg` 和 `test_cfg` 中,以在训练、验证和测试中配置 loop。 + + + + + + + + + +
原版 + +```python +runner = dict(type='IterBasedRunner', max_iters=20000) +``` + +
新版 + +```python +# `val_interval` 是旧版本的 `evaluation.interval`。 +train_cfg = dict(type='IterBasedTrainLoop', max_iters=20000, val_interval=2000) +val_cfg = dict(type='ValLoop') # 使用默认的验证循环。 +test_cfg = dict(type='TestLoop') # 使用默认的测试循环。 +``` + +
+ +事实上,在 OpenMMLab 2.0 中,我们引入了 `Loop` 来控制训练、验证和测试中的行为。`Runner` 的功能也发生了变化。您可以在 [MMMEngine](https://github.com/open-mmlab/mmengine/) 的[执行器教程](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/design/runner.md) 中找到更多的详细信息。 + +### 运行时设置 + +**`checkpoint_config`** 和 **`log_config`** 中的更改: + +`checkpoint_config` 被移动到 `default_hooks.checkpoint` 中,`log_config` 被移动到 `default_hooks.logger` 中。 +并且我们将许多钩子设置从脚本代码移动到运行时配置的 `default_hooks` 字段中。 + +```python +default_hooks = dict( + # 记录每次迭代的时间。 + timer=dict(type='IterTimerHook'), + + # 每50次迭代打印一次日志。 + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + + # 启用参数调度程序。 + param_scheduler=dict(type='ParamSchedulerHook'), + + # 每2000次迭代保存一次检查点。 + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=2000), + + # 在分布式环境中设置采样器种子。 + sampler_seed=dict(type='DistSamplerSeedHook'), + + # 验证结果可视化。 + visualization=dict(type='SegVisualizationHook')) +``` + +此外,我们将原版 logger 拆分为 logger 和 visualizer。logger 用于记录信息,visualizer 用于在不同的后端显示 logger,如 terminal 和 TensorBoard。 + + + + + + + + + +
原版 + +```python +log_config = dict( + interval=100, + hooks=[ + dict(type='TextLoggerHook'), + dict(type='TensorboardLoggerHook'), + ]) +``` + +
新版 + +```python +default_hooks = dict( + ... + logger=dict(type='LoggerHook', interval=100), +) +vis_backends = [dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +``` + +
+ +**`load_from`** 和 **`resume_from`** 中的更改: + +- 删除 `resume_from`。我们使用 `resume` 和 `load_from` 来替换它。 + - 如果 `resume=True` 且 `load_from` 为 **not None**,则从 `load_from` 中的检查点恢复训练。 + - 如果 `resume=True` 且 `load_from` 为 **None**,则尝试从工作目录中的最新检查点恢复。 + - 如果 `resume=False` 且 `load_from` 为 **not None**,则只加载检查点,而不继续训练。 + - 如果 `resume=False` 且 `load_from` 为 **None**,则不加载或恢复。 + +**`dist_params`** 中的更改:`dist_params` 字段现在是 `env_cfg` 的子字段。并且 `env_cfg` 中还有一些新的配置。 + +```python +env_cfg = dict( + # 是否启用 cudnn_benchmark + cudnn_benchmark=False, + + # 设置多进程参数 + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + + # 设置分布式参数 + dist_cfg=dict(backend='nccl'), +) +``` + +**`workflow`** 的改动:`workflow` 相关功能被删除。 + +新字段 **`visualizer`**:visualizer 是 OpenMMLab 2.0 体系结构中的新设计。我们在 runner 中使用 visualizer 实例来处理结果和日志可视化,并保存到不同的后端。更多详细信息,请参阅[可视化教程](../user_guides/visualization.md)。 + +新字段 **`default_scope`**:搜索所有注册模块的起点。MMSegmentation 中的 `default_scope` 为 `mmseg`。请参见[注册器教程](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/advanced_tutorials/registry.md)了解更多详情。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/migration/package.md b/Seg_All_In_One_MMSeg/docs/zh_cn/migration/package.md new file mode 100644 index 0000000..19e5f18 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/migration/package.md @@ -0,0 +1,113 @@ +# 包结构更改 + +本节包含您对 MMSeg 0.x 和 1.x 之间的变化可能感到好奇的内容。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MMSegmentation 0.xMMSegmentation 1.x
mmseg.apimmseg.api
- mmseg.core+ mmseg.engine
mmseg.datasetsmmseg.datasets
mmseg.modelsmmseg.models
- mmseg.ops+ mmseg.structure
mmseg.utilsmmseg.utils
+ mmseg.evaluation
+ mmseg.registry
+ +## 已删除的包 + +### `mmseg.core` + +在 OpenMMLab 2.0 中,`core` 包已被删除。`core` 的 `hooks` 和 `optimizers` 被移动到了 `mmseg.engine` 中,而 `core` 中的 `evaluation` 目前是 mmseg.evaluation。 + +## `mmseg.ops` + +`ops` 包含 `encoding` 和 `wrappers`,它们被移到了 `mmseg.models.utils` 中。 + +## 增加的包 + +### `mmseg.engine` + +OpenMMLab 2.0 增加了一个新的深度学习训练基础库 MMEngine。它是所有 OpenMMLab 代码库的训练引擎。 +mmseg 的 `engine` 包是一些用于语义分割任务的定制模块,如 `SegVisualizationHook` 用于可视化分割掩膜。 + +### `mmseg.structure` + +在 OpenMMLab 2.0 中,我们为计算机视觉任务设计了数据结构,在 mmseg 中,我们在 `structure` 包中实现了 `SegDataSample`。 + +### `mmseg.evaluation` + +我们将所有评估指标都移动到了 `mmseg.evaluation` 中。 + +### `mmseg.registry` + +我们将 MMSegmentation 中所有类型模块的注册实现移动到 `mmseg.registry` 中。 + +## 修改的包 + +### `mmseg.apis` + +OpenMMLab 2.0 尝试支持计算机视觉的多任务统一接口,并发布了更强的 [`Runner`](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/design/runner.md),因此 MMSeg 1.x 删除了 `train.py` 和 `test.py` 中的模块,并将 `init_segmentor` 重命名为 `init_model`,将 `inference_segmentor` 重命名为 `inference_model`。 + +以下是 `mmseg.apis` 的更改: + +| 函数 | 变化 | +| :-------------------: | :--------------------------------------------- | +| `init_segmentor` | 重命名为 `init_model` | +| `inference_segmentor` | 重命名为 `inference_model` | +| `show_result_pyplot` | 基于 `SegLocalVisualizer` 实现 | +| `train_model` | 删除,使用 `runner.train` 训练。 | +| `multi_gpu_test` | 删除,使用 `runner.test` 测试。 | +| `single_gpu_test` | 删除,使用 `runner.test` 测试。 | +| `set_random_seed` | 删除,使用 `mmengine.runner.set_random_seed`。 | +| `init_random_seed` | 删除,使用 `mmengine.dist.sync_random_seed`。 | + +### `mmseg.datasets` + +OpenMMLab 2.0 将 `BaseDataset` 定义为数据集的函数和接口,MMSegmentation 1.x 也遵循此协议,并定义了从 `BaseDataset` 继承的 `BaseSegDataset`。MMCV 2.x 收集多种任务的通用数据转换,例如分类、检测、分割,因此 MMSegmentation 1.x 使用这些数据转换并将其从 mmseg.dataset 中删除。 + +| 包/模块 | 更改 | +| :-------------------: | :----------------------------------------------------------------------------------- | +| `mmseg.pipelines` | 移动到 `mmcv.transforms` 中 | +| `mmseg.sampler` | 移动到 `mmengine.dataset.sampler` 中 | +| `CustomDataset` | 重命名为 `BaseSegDataset` 并从 MMEngine 中的 `BaseDataset` 继承 | +| `DefaultFormatBundle` | 替换为 `PackSegInputs` | +| `LoadImageFromFile` | 移动到 `mmcv.transforms.LoadImageFromFile` 中 | +| `LoadAnnotations` | 移动到 `mmcv.transforms.LoadAnnotations` 中 | +| `Resize` | 移动到 `mmcv.transforms` 中并拆分为 `Resize`,`RandomResize` 和 `RandomChoiceResize` | +| `RandomFlip` | 移动到 `mmcv.transforms.RandomFlip` 中 | +| `Pad` | 移动到 `mmcv.transforms.Pad` 中 | +| `Normalize` | 移动到 `mmcv.transforms.Normalize` 中 | +| `Compose` | 移动到 `mmcv.transforms.Compose` 中 | +| `ImageToTensor` | 移动到 `mmcv.transforms.ImageToTensor` 中 | + +### `mmseg.models` + +`models` 没有太大变化,只是从以前的 `mmseg.ops` 添加了 `encoding` 和 `wrappers` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/model_zoo.md b/Seg_All_In_One_MMSeg/docs/zh_cn/model_zoo.md new file mode 100644 index 0000000..bd57215 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/model_zoo.md @@ -0,0 +1,152 @@ +# 标准与模型库 + +## 共同设定 + +- 我们默认使用 4 卡分布式训练 +- 所有 PyTorch 风格的 ImageNet 预训练网络由我们自己训练,和 [论文](https://arxiv.org/pdf/1812.01187.pdf) 保持一致。 + 我们的 ResNet 网络是基于 ResNetV1c 的变种,在这里输入层的 7x7 卷积被 3个 3x3 取代 +- 为了在不同的硬件上保持一致,我们以 `torch.cuda.max_memory_allocated()` 的最大值作为 GPU 占用率,同时设置 `torch.backends.cudnn.benchmark=False`。 + 注意,这通常比 `nvidia-smi` 显示的要少 +- 我们以网络 forward 和后处理的时间加和作为推理时间,除去数据加载时间。我们使用脚本 `tools/benchmark.py` 来获取推理时间,它在 `torch.backends.cudnn.benchmark=False` 的设定下,计算 200 张图片的平均推理时间 +- 在框架中,有两种推理模式 + - `slide` 模式(滑动模式):测试的配置文件字段 `test_cfg` 会是 `dict(mode='slide', crop_size=(769, 769), stride=(513, 513))`. + 在这个模式下,从原图中裁剪多个小图分别输入网络中进行推理。小图的大小和小图之间的距离由 `crop_size` 和 `stride` 决定,重合区域会进行平均 + - `whole` 模式 (全图模式):测试的配置文件字段 `test_cfg` 会是 `dict(mode='whole')`. 在这个模式下,全图会被直接输入到网络中进行推理。 + 对于 769x769 下训练的模型,我们默认使用 `slide` 进行推理,其余模型用 `whole` 进行推理 +- 对于输入大小为 8x+1 (比如769),我们使用 `align_corners=True`。其余情况,对于输入大小为 8x (比如 512,1024),我们使用 `align_corners=False` + +## 基线 + +### FCN + +请参考 [FCN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/fcn) 获得详细信息。 + +### PSPNet + +请参考 [PSPNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/pspnet) 获得详细信息。 + +### DeepLabV3 + +请参考 [DeepLabV3](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/deeplabv3) 获得详细信息。 + +### PSANet + +请参考 [PSANet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/psanet) 获得详细信息。 + +### DeepLabV3+ + +请参考 [DeepLabV3+](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/deeplabv3plus) 获得详细信息。 + +### UPerNet + +请参考 [UPerNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/upernet) 获得详细信息。 + +### NonLocal Net + +请参考 [NonLocal Net](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/nlnet) 获得详细信息。 + +### EncNet + +请参考 [EncNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/encnet) 获得详细信息。 + +### CCNet + +请参考 [CCNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/ccnet) 获得详细信息。 + +### DANet + +请参考 [DANet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/danet) 获得详细信息。 + +### APCNet + +请参考 [APCNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/apcnet) 获得详细信息。 + +### HRNet + +请参考 [HRNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/hrnet) 获得详细信息。 + +### GCNet + +请参考 [GCNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/gcnet) 获得详细信息。 + +### DMNet + +请参考 [DMNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/dmnet) 获得详细信息。 + +### ANN + +请参考 [ANN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/ann) 获得详细信息。 + +### OCRNet + +请参考 [OCRNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/ocrnet) 获得详细信息。 + +### Fast-SCNN + +请参考 [Fast-SCNN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/fastscnn) 获得详细信息。 + +### ResNeSt + +请参考 [ResNeSt](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/resnest) 获得详细信息。 + +### Semantic FPN + +请参考 [Semantic FPN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/semfpn) 获得详细信息。 + +### PointRend + +请参考 [PointRend](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/point_rend) 获得详细信息。 + +### MobileNetV2 + +请参考 [MobileNetV2](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/mobilenet_v2) 获得详细信息。 + +### MobileNetV3 + +请参考 [MobileNetV3](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/mobilenet_v3) 获得详细信息。 + +### EMANet + +请参考 [EMANet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/emanet) 获得详细信息。 + +### DNLNet + +请参考 [DNLNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/dnlnet) 获得详细信息。 + +### CGNet + +请参考 [CGNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/cgnet) 获得详细信息。 + +### Mixed Precision (FP16) Training + +请参考 [Mixed Precision (FP16) Training 在 BiSeNetV2 训练的样例](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/bisenetv2/bisenetv2_fcn_fp16_4x4_1024x1024_160k_cityscapes.py) 获得详细信息。 + +## 速度标定(待更新) + +### 硬件 + +- 8 NVIDIA Tesla V100 (32G) GPUs +- Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz + +### 软件环境 + +- Python 3.7 +- PyTorch 1.5 +- CUDA 10.1 +- CUDNN 7.6.03 +- NCCL 2.4.08 + +### 训练速度 + +为了公平比较,我们全部使用 ResNet-101V1c 进行标定。输入大小为 1024x512,批量样本数为 2。 + +训练速度如下表,指标为每次迭代的时间,以秒为单位,越低越快。 + +| Implementation | PSPNet (s/iter) | DeepLabV3+ (s/iter) | +| --------------------------------------------------------------------------- | --------------- | ------------------- | +| [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) | **0.83** | **0.85** | +| [SegmenTron](https://github.com/LikeLy-Journey/SegmenTron) | 0.84 | 0.85 | +| [CASILVision](https://github.com/CSAILVision/semantic-segmentation-pytorch) | 1.15 | N/A | +| [vedaseg](https://github.com/Media-Smart/vedaseg) | 0.95 | 1.25 | + +注意:DeepLabV3+ 的输出步长为 8。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/modelzoo_statistics.md b/Seg_All_In_One_MMSeg/docs/zh_cn/modelzoo_statistics.md new file mode 100644 index 0000000..b057575 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/modelzoo_statistics.md @@ -0,0 +1,102 @@ +# 模型库统计数据 + +- 论文数量: 47 + + - ALGORITHM: 36 + - BACKBONE: 11 + +- 模型数量: 612 + + - \[ALGORITHM\] [ANN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/ann) (16 ckpts) + + - \[ALGORITHM\] [APCNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/apcnet) (12 ckpts) + + - \[BACKBONE\] [BEiT](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/beit) (2 ckpts) + + - \[ALGORITHM\] [BiSeNetV1](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/bisenetv1) (11 ckpts) + + - \[ALGORITHM\] [BiSeNetV2](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/bisenetv2) (4 ckpts) + + - \[ALGORITHM\] [CCNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/ccnet) (16 ckpts) + + - \[ALGORITHM\] [CGNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/cgnet) (2 ckpts) + + - \[BACKBONE\] [ConvNeXt](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/convnext) (6 ckpts) + + - \[ALGORITHM\] [DANet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/danet) (16 ckpts) + + - \[ALGORITHM\] [DeepLabV3](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/deeplabv3) (41 ckpts) + + - \[ALGORITHM\] [DeepLabV3+](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/deeplabv3plus) (42 ckpts) + + - \[ALGORITHM\] [DMNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/dmnet) (12 ckpts) + + - \[ALGORITHM\] [DNLNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/dnlnet) (12 ckpts) + + - \[ALGORITHM\] [DPT](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/dpt) (1 ckpts) + + - \[ALGORITHM\] [EMANet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/emanet) (4 ckpts) + + - \[ALGORITHM\] [EncNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/encnet) (12 ckpts) + + - \[ALGORITHM\] [ERFNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/erfnet) (1 ckpts) + + - \[ALGORITHM\] [FastFCN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/fastfcn) (12 ckpts) + + - \[ALGORITHM\] [Fast-SCNN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/fastscnn) (1 ckpts) + + - \[ALGORITHM\] [FCN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/fcn) (41 ckpts) + + - \[ALGORITHM\] [GCNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/gcnet) (16 ckpts) + + - \[BACKBONE\] [HRNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/hrnet) (37 ckpts) + + - \[ALGORITHM\] [ICNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/icnet) (12 ckpts) + + - \[ALGORITHM\] [ISANet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/isanet) (16 ckpts) + + - \[ALGORITHM\] [K-Net](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/knet) (7 ckpts) + + - \[BACKBONE\] [MAE](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/mae) (1 ckpts) + + - \[ALGORITHM\] [Mask2Former](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/mask2former) (13 ckpts) + + - \[ALGORITHM\] [MaskFormer](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/maskformer) (4 ckpts) + + - \[BACKBONE\] [MobileNetV2](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/mobilenet_v2) (8 ckpts) + + - \[BACKBONE\] [MobileNetV3](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/mobilenet_v3) (4 ckpts) + + - \[ALGORITHM\] [NonLocal Net](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/nonlocal_net) (16 ckpts) + + - \[ALGORITHM\] [OCRNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/ocrnet) (24 ckpts) + + - \[ALGORITHM\] [PointRend](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/point_rend) (4 ckpts) + + - \[BACKBONE\] [PoolFormer](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/poolformer) (5 ckpts) + + - \[ALGORITHM\] [PSANet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/psanet) (16 ckpts) + + - \[ALGORITHM\] [PSPNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/pspnet) (54 ckpts) + + - \[BACKBONE\] [ResNeSt](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/resnest) (8 ckpts) + + - \[ALGORITHM\] [SegFormer](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/segformer) (13 ckpts) + + - \[ALGORITHM\] [Segmenter](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/segmenter) (5 ckpts) + + - \[ALGORITHM\] [Semantic FPN](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/sem_fpn) (4 ckpts) + + - \[ALGORITHM\] [SETR](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/setr) (7 ckpts) + + - \[ALGORITHM\] [STDC](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/stdc) (4 ckpts) + + - \[BACKBONE\] [Swin Transformer](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/swin) (6 ckpts) + + - \[BACKBONE\] [Twins](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/twins) (12 ckpts) + + - \[ALGORITHM\] [UNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/unet) (25 ckpts) + + - \[ALGORITHM\] [UPerNet](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/upernet) (16 ckpts) + + - \[BACKBONE\] [Vision Transformer](https://github.com/open-mmlab/mmsegmentation/blob/master/configs/vit) (11 ckpts) diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/notes/faq.md b/Seg_All_In_One_MMSeg/docs/zh_cn/notes/faq.md new file mode 100644 index 0000000..aa99c25 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/notes/faq.md @@ -0,0 +1,125 @@ +# 常见问题解答(FAQ) + +我们在这里列出了使用时的一些常见问题及其相应的解决方案。 如果您发现有一些问题被遗漏,请随时提 PR 丰富这个列表。 如果您无法在此获得帮助,请使用 [issue 模板](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/.github/ISSUE_TEMPLATE/error-report.md/)创建问题,但是请在模板中填写所有必填信息,这有助于我们更快定位问题。 + +## 安装 + +兼容的 MMSegmentation 和 MMCV 版本如下。请安装正确版本的 MMCV 以避免安装问题。 + +| MMSegmentation version | MMCV version | MMEngine version | MMClassification (optional) version | MMDetection (optional) version | +| :--------------------: | :----------------------------: | :---------------: | :---------------------------------: | :----------------------------: | +| dev-1.x branch | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| main branch | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.2.2 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.2.1 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.2.0 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.1.2 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.1.1 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.1.0 | mmcv >= 2.0.0 | MMEngine >= 0.7.4 | mmpretrain>=1.0.0rc7 | mmdet >= 3.0.0 | +| 1.0.0 | mmcv >= 2.0.0rc4 | MMEngine >= 0.7.1 | mmcls==1.0.0rc6 | mmdet >= 3.0.0 | +| 1.0.0rc6 | mmcv >= 2.0.0rc4 | MMEngine >= 0.5.0 | mmcls>=1.0.0rc0 | mmdet >= 3.0.0rc6 | +| 1.0.0rc5 | mmcv >= 2.0.0rc4 | MMEngine >= 0.2.0 | mmcls>=1.0.0rc0 | mmdet>=3.0.0rc6 | +| 1.0.0rc4 | mmcv == 2.0.0rc3 | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | mmdet>=3.0.0rc4, \<=3.0.0rc5 | +| 1.0.0rc3 | mmcv == 2.0.0rc3 | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | mmdet>=3.0.0rc4, \<=3.0.0rc5 | +| 1.0.0rc2 | mmcv == 2.0.0rc3 | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | mmdet>=3.0.0rc4, \<=3.0.0rc5 | +| 1.0.0rc1 | mmcv >= 2.0.0rc1, \<=2.0.0rc3> | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | Not required | +| 1.0.0rc0 | mmcv >= 2.0.0rc1, \<=2.0.0rc3> | MMEngine >= 0.1.0 | mmcls>=1.0.0rc0 | Not required | + +如果您已经安装了版本不合适的 mmcv,请先运行`pip uninstall mmcv`卸载已安装的 mmcv,如您先前安装的为 mmcv-full(存在于 OpenMMLab 1.x),请运行`pip uninstall mmcv-full`进行卸载。 + +- 如出现 "No module named 'mmcv'" + 1. 使用`pip uninstall mmcv`卸载环境中现有的 mmcv + 2. 按照[安装说明](../get_started.md)安装对应的 mmcv + +## 如何获知模型训练时需要的显卡数量 + +- 看模型的 config 文件命名。可以参考[了解配置文件](../user_guides/1_config.md)中的`配置文件命名风格`部分。比如,对于名字为`segformer_mit-b0_8xb1-160k_cityscapes-1024x1024.py`的 config 文件,`8xb1`代表训练其对应的模型需要的卡数为 8,每张卡中的 batch size 为 1。 +- 看模型的 log 文件。点开该模型的 log 文件,并在其中搜索`nGPU`,在`nGPU`后的数字个数即训练时所需的卡数。比如,在 log 文件中搜索`nGPU`得到`nGPU 0,1,2,3,4,5,6,7`的记录,则说明训练该模型需要使用八张卡。 + +## auxiliary head 是什么 + +简单来说,这是一个提高准确率的深度监督技术。在训练阶段,`decode_head`用于输出语义分割的结果,`auxiliary_head` 只是增加了一个辅助损失,其产生的分割结果对你的模型结果没有影响,仅在在训练中起作用。您可以阅读这篇[论文](https://arxiv.org/pdf/1612.01105.pdf)了解更多信息。 + +## 运行测试脚本时如何输出绘制分割掩膜的图像 + +在测试脚本中,我们提供了`--out`参数来控制是否输出保存预测的分割掩膜图像。您可以运行以下命令输出测试结果: + +```shell +python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} --out ${OUTPUT_DIR} +``` + +更多用例细节可查阅[文档](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/docs/zh_cn/user_guides/4_train_test.md#%E6%B5%8B%E8%AF%95%E5%B9%B6%E4%BF%9D%E5%AD%98%E5%88%86%E5%89%B2%E7%BB%93%E6%9E%9C),[PR #2712](https://github.com/open-mmlab/mmsegmentation/pull/2712) 以及[迁移文档](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/docs/zh_cn/migration/interface.md#%E6%B5%8B%E8%AF%95%E5%90%AF%E5%8A%A8)了解相关说明。 + +## 如何处理二值分割任务? + +MMSegmentation 使用 `num_classes` 和 `out_channels` 来控制模型最后一层 `self.conv_seg` 的输出。更多细节可以参考 [这里](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/mmseg/models/decode_heads/decode_head.py)。 + +`num_classes` 应该和数据集本身类别个数一致,当是二值分割时,数据集只有前景和背景两类,所以 `num_classes` 为 2. `out_channels` 控制模型最后一层的输出的通道数,通常和 `num_classes` 相等,但当二值分割时候,可以有两种处理方法, 分别是: + +- 设置 `out_channels=2`,在训练时以 Cross Entropy Loss 作为损失函数,在推理时使用 `F.softmax()` 归一化 logits 值,然后通过 `argmax()` 得到每个像素的预测结果。 + +- 设置 `out_channels=1`,在训练时以 Binary Cross Entropy Loss 作为损失函数,在推理时使用 `F.sigmoid()` 和 `threshold` 得到预测结果,`threshold` 默认为 0.3。 + +对于实现上述两种计算二值分割的方法,需要在 `decode_head` 和 `auxiliary_head` 的配置里修改。下面是对样例 [pspnet_unet_s5-d16.py](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/configs/_base_/models/pspnet_unet_s5-d16.py) 做出的对应修改。 + +- (1) `num_classes=2`, `out_channels=2` 并在 `CrossEntropyLoss` 里面设置 `use_sigmoid=False`。 + +```python +decode_head=dict( + type='PSPHead', + in_channels=64, + in_index=4, + num_classes=2, + out_channels=2, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), +auxiliary_head=dict( + type='FCNHead', + in_channels=128, + in_index=3, + num_classes=2, + out_channels=2, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), +``` + +- (2) `num_classes=2`, `out_channels=1` 并在 `CrossEntropyLoss` 里面设置 `use_sigmoid=True`. + +```python +decode_head=dict( + type='PSPHead', + in_channels=64, + in_index=4, + num_classes=2, + out_channels=1, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)), +auxiliary_head=dict( + type='FCNHead', + in_channels=128, + in_index=3, + num_classes=2, + out_channels=1, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=True, loss_weight=0.4)), +``` + +## `reduce_zero_label` 的作用 + +数据集中 `reduce_zero_label` 参数类型为布尔类型,默认为 False,它的功能是为了忽略数据集 label 0。具体做法是将 label 0 改为 255,其余 label 相应编号减 1,同时 decode head 里将 255 设为 ignore index,即不参与 loss 计算。 +以下是 `reduce_zero_label` 具体实现逻辑: + +```python +if self.reduce_zero_label: + # avoid using underflow conversion + gt_semantic_seg[gt_semantic_seg == 0] = 255 + gt_semantic_seg = gt_semantic_seg - 1 + gt_semantic_seg[gt_semantic_seg == 254] = 255 +``` + +关于您的数据集是否需要使用 reduce_zero_label,有以下两类情况: + +- 例如在 [Potsdam](https://github.com/open-mmlab/mmsegmentation/blob/1.x/docs/en/user_guides/2_dataset_prepare.md#isprs-potsdam) 数据集上,有 0-不透水面、1-建筑、2-低矮植被、3-树、4-汽车、5-杂乱,六类。但该数据集提供了两种 RGB 标签,一种为图像边缘处有黑色像素的标签,另一种是没有黑色边缘的标签。对于有黑色边缘的标签,在 [dataset_converters.py](https://github.com/open-mmlab/mmsegmentation/blob/dev-1.x/tools/dataset_converters/potsdam.py)中,其将黑色边缘转换为 label 0,其余标签分别为 1-不透水面、2-建筑、3-低矮植被、4-树、5-汽车、6-杂乱,那么此时,就应该在数据集 [potsdam.py](https://github.com/open-mmlab/mmsegmentation/blob/ff95416c3b5ce8d62b9289f743531398efce534f/mmseg/datasets/potsdam.py#L23) 中将`reduce_zero_label=True`。如果使用的是没有黑色边缘的标签,那么 mask label 中只有 0-5,此时就应该使`reduce_zero_label=False`。需要结合您的实际情况来使用。 +- 例如在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用`reduce_zero_label`的,此时在数据集中应该将其设置为`reduce_zero_label=False` + +**注意:** 使用 `reduce_zero_label` 请确认数据集原始类别个数,如果只有两类,需要关闭 `reduce_zero_label` 即设置 `reduce_zero_label=False`。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/overview.md b/Seg_All_In_One_MMSeg/docs/zh_cn/overview.md new file mode 100644 index 0000000..ed14795 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/overview.md @@ -0,0 +1,75 @@ +# 概述 + +本章节向您介绍 MMSegmentation 框架以及语义分割相关的基本概念。我们还提供了关于 MMSegmentation 的详细教程链接。 + +## 什么是语义分割? + +语义分割是将图像中属于同一目标类别的部分聚类在一起的任务。它也是一种像素级预测任务,因为图像中的每一个像素都将根据类别进行分类。该任务的一些示例基准有 [Cityscapes](https://www.cityscapes-dataset.com/benchmarks/), [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/) 和 [ADE20K](https://groups.csail.mit.edu/vision/datasets/ADE20K/) 。通常用平均交并比 (Mean IoU) 和像素准确率 (Pixel Accuracy) 这两个指标来评估模型。 + +## 什么是 MMSegmentation? + +MMSegmentation 是一个工具箱,它为语义分割任务的统一实现和模型评估提供了一个框架,并且高质量实现了常用的语义分割方法和数据集。 + +MMSeg 主要包含了 apis, structures, datasets, models, engine, evaluation 和 visualization 这七个主要部分。 + +- **apis** 提供了模型推理的高级api + +- **structures** 提供了分割任务的数据结构 `SegDataSample` + +- **datasets** 支持用于语义分割的多种数据集 + + - **transforms** 包含多种数据增强变换 + +- **models** 是分割器最重要的部分,包含了分割器的不同组件 + + - **segmentors** 定义了所有分割模型类 + - **data_preprocessors** 用于预处理模型的输入数据 + - **backbones** 包含各种骨干网络,可将图像映射为特征图 + - **necks** 包含各种模型颈部组件,用于连接分割头和骨干网络 + - **decode_heads** 包含各种分割头,将特征图作为输入,并预测分割结果 + - **losses** 包含各种损失函数 + +- **engine** 是运行时组件的一部分,扩展了 [MMEngine](https://github.com/open-mmlab/mmengine) 的功能 + + - **optimizers** 提供了优化器和优化器封装 + - **hooks** 提供了 runner 的各种钩子 + +- **evaluation** 提供了评估模型性能的不同指标 + +- **visualization** 分割结果的可视化工具 + +## 如何使用本指南? + +以下是详细步骤,将带您一步步学习如何使用 MMSegmentation : + +1. 有关安装说明,请参阅 [开始你的第一步](get_started.md)。 + +2. 对于初学者来说,MMSegmentation 是开始语义分割之旅的最好选择,因为这里实现了许多 SOTA 模型以及经典的模型 [model](model_zoo.md) 。另外,将各类组件和高级 API 結合使用,可以更便捷的执行分割任务。关于 MMSegmentation 的基本用法,请参考下面的教程: + + - [配置](user_guides/1_config.md) + - [数据预处理](user_guides/2_dataset_prepare.md) + - [推理](user_guides/3_inference.md) + - [训练和测试](user_guides/4_train_test.md) + +3. 如果你想了解 MMSegmentation 工作的基本类和功能,请参考下面的教程来深入研究: + + - [数据流](advanced_guides/data_flow.md) + - [结构](advanced_guides/structures.md) + - [模型](advanced_guides/models.md) + - [数据集](advanced_guides/datasets.md) + - [评估](advanced_guides/evaluation.md) + +4. MMSegmentation 也为用户自定义和一些前沿的研究提供了教程,请参考下面的教程来建立你自己的分割项目: + + - [添加新的模型](advanced_guides/add_models.md) + - [添加新的数据集](advanced_guides/add_datasets.md) + - [添加新的 transform](advanced_guides/add_transforms.md) + - [自定义 runtime](advanced_guides/customize_runtime.md) + +5. 如果您更熟悉 MMSegmentation v0.x , 以下是 MMSegmentation v0.x 迁移到 v1.x 的文档 + + - [迁移](migration/index.rst) + +## 参考来源 + +- https://paperswithcode.com/task/semantic-segmentation/codeless#task-home diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/stat.py b/Seg_All_In_One_MMSeg/docs/zh_cn/stat.py new file mode 100644 index 0000000..7a86302 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/stat.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# Copyright (c) OpenMMLab. All rights reserved. +import functools as func +import glob +import os.path as osp +import re + +import numpy as np + +url_prefix = 'https://github.com/open-mmlab/mmsegmentation/blob/master/' + +files = sorted(glob.glob('../../configs/*/README.md')) + +stats = [] +titles = [] +num_ckpts = 0 + +for f in files: + url = osp.dirname(f.replace('../../', url_prefix)) + + with open(f) as content_file: + content = content_file.read() + + title = content.split('\n')[0].replace('#', '').strip() + ckpts = { + x.lower().strip() + for x in re.findall(r'https?://download.*\.pth', content) + if 'mmsegmentation' in x + } + if len(ckpts) == 0: + continue + + _papertype = [ + x for x in re.findall(r'', content) + ] + assert len(_papertype) > 0 + papertype = _papertype[0] + + paper = {(papertype, title)} + + titles.append(title) + num_ckpts += len(ckpts) + statsmsg = f""" +\t* [{papertype}] [{title}]({url}) ({len(ckpts)} ckpts) +""" + stats.append((paper, ckpts, statsmsg)) + +allpapers = func.reduce(lambda a, b: a.union(b), [p for p, _, _ in stats]) +msglist = '\n'.join(x for _, _, x in stats) + +papertypes, papercounts = np.unique([t for t, _ in allpapers], + return_counts=True) +countstr = '\n'.join( + [f' - {t}: {c}' for t, c in zip(papertypes, papercounts)]) + +modelzoo = f""" +# 模型库统计数据 + +* 论文数量: {len(set(titles))} +{countstr} + +* 模型数量: {num_ckpts} +{msglist} +""" + +with open('modelzoo_statistics.md', 'w') as f: + f.write(modelzoo) diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/switch_language.md b/Seg_All_In_One_MMSeg/docs/zh_cn/switch_language.md new file mode 100644 index 0000000..f58efc4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/switch_language.md @@ -0,0 +1,3 @@ +## English + +## 简体中文 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/1_config.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/1_config.md new file mode 100644 index 0000000..dfcf0f9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/1_config.md @@ -0,0 +1,577 @@ +# 教程1:了解配置文件 + +我们将模块化和继承性设计融入到我们的配置文件系统中,方便进行各种实验。如果您想查看配置文件,你可以运行 `python tools/misc/print_config.py /PATH/TO/CONFIG` 来查看完整的配置文件。你也可以通过传递参数 `--cfg-options xxx.yyy=zzz` 来查看更新的配置信息。 + +## 配置文件的结构 + +在 `config/_base_ ` 文件夹下面有4种基本组件类型: 数据集(dataset),模型(model),训练策略(schedule)和运行时的默认设置(default runtime)。许多模型都可以很容易地通过组合这些组件进行实现,比如 DeepLabV3,PSPNet。使用 `_base_` 下的组件构建的配置信息叫做原始配置 (primitive)。 + +对于同一个文件夹下的所有配置文件,建议**只有一个**对应的**原始配置文件**。所有其他的配置文件都应该继承自这个原始配置文件,从而保证每个配置文件的最大继承深度为 3。 + +为了便于理解,我们建议社区贡献者从现有的方法继承。例如,如果您在 DeepLabV3 基础上进行了一些修改,用户可以先通过指定 `_base_ = ../deeplabv3/deeplabv3_r50-d8_4xb2-40k_cityscapes-512x1024.py` 继承基本的 DeepLabV3 结构,然后在配置文件中修改必要的字段。 + +如果你正在构建一个全新的方法,它不与现有的任何方法共享基本组件,您可以在`config`下创建一个新的文件夹`xxxnet` ,详细文档请参考[mmengine](https://mmengine.readthedocs.io/en/latest/tutorials/config.html)。 + +## 配置文件命名风格 + +我们遵循以下格式来命名配置文件,建议社区贡献者遵循相同的风格。 + +```text +{algorithm name}_{model component names [component1]_[component2]_[...]}_{training settings}_{training dataset information}_{testing dataset information} +``` + +配置文件的文件名分为五个部分,组成文件名每一个部分和组件之间都用`_`连接,每个部分或组件中的每个单词都要用`-`连接。 + +- `{algorithm name}`: 算法的名称,如 `deeplabv3`, `pspnet` 等。 +- `{model component names}`: 算法中使用的组件名称,如主干(backbone)、解码头(head)等。例如,`r50-d8 `表示使用ResNet50主干网络,并使用主干网络的8倍下采样输出作为下一级的输入。 +- `{training settings}`: 训练时的参数设置,如 `batch size`、数据增强(augmentation)、损失函数(loss)、学习率调度器(learning rate scheduler)和训练轮数(epochs/iterations)。例如: `4xb4-ce-linearlr-40K` 意味着使用4个gpu,每个gpu4个图像,使用交叉熵损失函数(CrossEntropy),线性学习率调度程序,训练40K iterations。 + 一些缩写: + - `{gpu x batch_per_gpu}`: GPU数量和每个GPU的样本数。`bN ` 表示每个GPU的batch size为N,如 `8xb2` 为8个gpu x 每个gpu2张图像的缩写。如果未提及,则默认使用 `4xb4 `。 + - `{schedule}`: 训练计划,选项有`20k`,`40k`等。`20k ` 和 `40k` 分别表示20000次迭代(iterations)和40000次迭代(iterations)。 +- `{training dataset information}`: 训练数据集名称,如 `cityscapes `, `ade20k ` 等,以及输入分辨率。例如: `cityscapes-768x768 `表示使用 `cityscapes` 数据集进行训练,输入分辨率为`768x768 `。 +- `{testing dataset information}` (可选): 测试数据集名称。当您的模型在一个数据集上训练但在另一个数据集上测试时,请将测试数据集名称添加到此处。如果没有这一部分,则意味着模型是在同一个数据集上进行训练和测试的。 + +## PSPNet 的一个例子 + +为了帮助用户熟悉对这个现代语义分割系统的完整配置文件和模块,我们对使用ResNet50V1c作为主干网络的PSPNet的配置文件作如下的简要注释和说明。要了解更详细的用法和每个模块对应的替换方法,请参阅API文档。 + +```python +_base_ = [ + '../_base_/models/pspnet_r50-d8.py', '../_base_/datasets/cityscapes.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py' +] # 我们可以在基本配置文件的基础上 构建新的配置文件 +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) +``` + +`_base_/models/pspnet_r50-d8.py`是使用ResNet50V1c作为主干网络的PSPNet的基本模型配置文件。 + +```python +# 模型设置 +norm_cfg = dict(type='SyncBN', requires_grad=True) # 分割框架通常使用 SyncBN +data_preprocessor = dict( # 数据预处理的配置项,通常包括图像的归一化和增强 + type='SegDataPreProcessor', # 数据预处理的类型 + mean=[123.675, 116.28, 103.53], # 用于归一化输入图像的平均值 + std=[58.395, 57.12, 57.375], # 用于归一化输入图像的标准差 + bgr_to_rgb=True, # 是否将图像从 BGR 转为 RGB + pad_val=0, # 图像的填充值 + seg_pad_val=255) # 'gt_seg_map'的填充值 +model = dict( + type='EncoderDecoder', # 分割器(segmentor)的名字 + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', # 加载使用 ImageNet 预训练的主干网络 + backbone=dict( + type='ResNetV1c', # 主干网络的类别,更多细节请参考 mmseg/models/backbones/resnet.py + depth=50, # 主干网络的深度,通常为 50 和 101 + num_stages=4, # 主干网络状态(stages)的数目 + out_indices=(0, 1, 2, 3), # 每个状态(stage)产生的特征图输出的索引 + dilations=(1, 1, 2, 4), # 每一层(layer)的空心率(dilation rate) + strides=(1, 2, 1, 1), # 每一层(layer)的步长(stride) + norm_cfg=norm_cfg, # 归一化层(norm layer)的配置项 + norm_eval=False, # 是否冻结 BN 里的统计项 + style='pytorch', # 主干网络的风格,'pytorch' 意思是步长为2的层为 3x3 卷积, 'caffe' 意思是步长为2的层为 1x1 卷积 + contract_dilation=True), # 当空洞率 > 1, 是否压缩第一个空洞层 + decode_head=dict( + type='PSPHead', # 解码头(decode head)的类别。可用选项请参 mmseg/models/decode_heads + in_channels=2048, # 解码头的输入通道数 + in_index=3, # 被选择特征图(feature map)的索引 + channels=512, # 解码头中间态(intermediate)的通道数 + pool_scales=(1, 2, 3, 6), # PSPHead 平均池化(avg pooling)的规模(scales)。 细节请参考文章内容 + dropout_ratio=0.1, # 进入最后分类层(classification layer)之前的 dropout 比例 + num_classes=19, # 分割前景的种类数目。 通常情况下,cityscapes 为19,VOC为21,ADE20k 为150 + norm_cfg=norm_cfg, # 归一化层的配置项 + align_corners=False, # 解码过程中调整大小(resize)的 align_corners 参数 + loss_decode=dict( # 解码头(decode_head)里的损失函数的配置项 + type='CrossEntropyLoss', # 分割时使用的损失函数的类别 + use_sigmoid=False, # 分割时是否使用 sigmoid 激活 + loss_weight=1.0)), # 解码头的损失权重 + auxiliary_head=dict( + type='FCNHead', # 辅助头(auxiliary head)的种类。可用选项请参考 mmseg/models/decode_heads + in_channels=1024, # 辅助头的输入通道数 + in_index=2, # 被选择的特征图(feature map)的索引 + channels=256, # 辅助头中间态(intermediate)的通道数 + num_convs=1, # FCNHead 里卷积(convs)的数目,辅助头中通常为1 + concat_input=False, # 在分类层(classification layer)之前是否连接(concat)输入和卷积的输出 + dropout_ratio=0.1, # 进入最后分类层(classification layer)之前的 dropout 比例 + num_classes=19, # 分割前景的种类数目。 通常情况下,cityscapes 为19,VOC为21,ADE20k 为150 + norm_cfg=norm_cfg, # 归一化层的配置项 + align_corners=False, # 解码过程中调整大小(resize)的 align_corners 参数 + loss_decode=dict( # 辅助头(auxiliary head)里的损失函数的配置项 + type='CrossEntropyLoss', # 分割时使用的损失函数的类别 + use_sigmoid=False, # 分割时是否使用 sigmoid 激活 + loss_weight=0.4)), # 辅助头损失的权重,默认设置为0.4 + # 模型训练和测试设置项 + train_cfg=dict(), # train_cfg 当前仅是一个占位符 + test_cfg=dict(mode='whole')) # 测试模式,可选参数为 'whole' 和 'slide'. 'whole': 在整张图像上全卷积(fully-convolutional)测试。 'slide': 在输入图像上做滑窗预测 +``` + +`_base_/datasets/cityscapes.py`是数据集的基本配置文件。 + +```python +# 数据集设置 +dataset_type = 'CityscapesDataset' # 数据集类型,这将被用来定义数据集 +data_root = 'data/cityscapes/' # 数据的根路径 +crop_size = (512, 1024) # 训练时的裁剪大小 +train_pipeline = [ # 训练流程 + dict(type='LoadImageFromFile'), # 第1个流程,从文件路径里加载图像 + dict(type='LoadAnnotations'), # 第2个流程,对于当前图像,加载它的标注图像 + dict(type='RandomResize', # 调整输入图像大小(resize)和其标注图像的数据增广流程 + scale=(2048, 1024), # 图像裁剪的大小 + ratio_range=(0.5, 2.0), # 数据增广的比例范围 + keep_ratio=True), # 调整图像大小时是否保持纵横比 + dict(type='RandomCrop', # 随机裁剪当前图像和其标注图像的数据增广流程 + crop_size=crop_size, # 随机裁剪的大小 + cat_max_ratio=0.75), # 单个类别可以填充的最大区域的比 + dict(type='RandomFlip', # 翻转图像和其标注图像的数据增广流程 + prob=0.5), # 翻转图像的概率 + dict(type='PhotoMetricDistortion'), # 光学上使用一些方法扭曲当前图像和其标注图像的数据增广流程 + dict(type='PackSegInputs') # 打包用于语义分割的输入数据 +] +test_pipeline = [ + dict(type='LoadImageFromFile'), # 第1个流程,从文件路径里加载图像 + dict(type='Resize', # 使用调整图像大小(resize)增强 + scale=(2048, 1024), # 图像缩放的大小 + keep_ratio=True), # 在调整图像大小时是否保留长宽比 + # 在' Resize '之后添加标注图像 + # 不需要做调整图像大小(resize)的数据变换 + dict(type='LoadAnnotations'), # 加载数据集提供的语义分割标注 + dict(type='PackSegInputs') # 打包用于语义分割的输入数据 +] +train_dataloader = dict( # 训练数据加载器(dataloader)的配置 + batch_size=2, # 每一个GPU的batch size大小 + num_workers=2, # 为每一个GPU预读取数据的进程个数 + persistent_workers=True, # 在一个epoch结束后关闭worker进程,可以加快训练速度 + sampler=dict(type='InfiniteSampler', shuffle=True), # 训练时进行随机洗牌(shuffle) + dataset=dict( # 训练数据集配置 + type=dataset_type, # 数据集类型,详见mmseg/datassets/ + data_root=data_root, # 数据集的根目录 + data_prefix=dict( + img_path='leftImg8bit/train', seg_map_path='gtFine/train'), # 训练数据的前缀 + pipeline=train_pipeline)) # 数据处理流程,它通过之前创建的train_pipeline传递。 +val_dataloader = dict( + batch_size=1, # 每一个GPU的batch size大小 + num_workers=4, # 为每一个GPU预读取数据的进程个数 + persistent_workers=True, # 在一个epoch结束后关闭worker进程,可以加快训练速度 + sampler=dict(type='DefaultSampler', shuffle=False), # 训练时不进行随机洗牌(shuffle) + dataset=dict( # 测试数据集配置 + type=dataset_type, # 数据集类型,详见mmseg/datassets/ + data_root=data_root, # 数据集的根目录 + data_prefix=dict( + img_path='leftImg8bit/val', seg_map_path='gtFine/val'), # 测试数据的前缀 + pipeline=test_pipeline)) # 数据处理流程,它通过之前创建的test_pipeline传递。 +test_dataloader = val_dataloader +# 精度评估方法,我们在这里使用 IoUMetric 进行评估 +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator +``` + +`_base_/schedules/schedule_40k.py` + +```python +# optimizer +optimizer = dict(type='SGD', # 优化器种类,更多细节可参考 https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py + lr=0.01, # 优化器的学习率,参数的使用细节请参照对应的 PyTorch 文档 + momentum=0.9, # 动量大小 (Momentum) + weight_decay=0.0005) # SGD 的权重衰减 (weight decay) +optim_wrapper = dict(type='OptimWrapper', # 优化器包装器(Optimizer wrapper)为更新参数提供了一个公共接口 + optimizer=optimizer, # 用于更新模型参数的优化器(Optimizer) + clip_grad=None) # 如果 'clip_grad' 不是None,它将是 ' torch.nn.utils.clip_grad' 的参数。 +# 学习策略 +param_scheduler = [ + dict( + type='PolyLR', # 调度流程的策略,同样支持 Step, CosineAnnealing, Cyclic 等. 请从 https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py 参考 LrUpdater 的细节 + eta_min=1e-4, # 训练结束时的最小学习率 + power=0.9, # 多项式衰减 (polynomial decay) 的幂 + begin=0, # 开始更新参数的时间步(step) + end=40000, # 停止更新参数的时间步(step) + by_epoch=False) # 是否按照 epoch 计算训练时间 +] +# 40k iteration 的训练计划 +train_cfg = dict(type='IterBasedTrainLoop', max_iters=40000, val_interval=4000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +# 默认钩子(hook)配置 +default_hooks = dict( + timer=dict(type='IterTimerHook'), # 记录迭代过程中花费的时间 + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), # 从'Runner'的不同组件收集和写入日志 + param_scheduler=dict(type='ParamSchedulerHook'), # 更新优化器中的一些超参数,例如学习率 + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), # 定期保存检查点(checkpoint) + sampler_seed=dict(type='DistSamplerSeedHook')) # 用于分布式训练的数据加载采样器 +``` + +in `_base_/default_runtime.py` + +```python +# 将注册表的默认范围设置为mmseg +default_scope = 'mmseg' +# environment +env_cfg = dict( + cudnn_benchmark=True, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) +log_level = 'INFO' +log_processor = dict(by_epoch=False) +load_from = None # 从文件中加载检查点(checkpoint) +resume = False # 是否从已有的模型恢复 +``` + +这些都是用于训练和测试PSPNet的配置文件,要加载和解析它们,我们可以使用[MMEngine](https://github.com/open-mmlab/mmengine)实现的[Config](https://mmengine.readthedocs.io/en/latest/tutorials/config.html)。 + +```python +from mmengine.config import Config + +cfg = Config.fromfile('configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py') +print(cfg.train_dataloader) +``` + +```shell +{'batch_size': 2, + 'num_workers': 2, + 'persistent_workers': True, + 'sampler': {'type': 'InfiniteSampler', 'shuffle': True}, + 'dataset': {'type': 'CityscapesDataset', + 'data_root': 'data/cityscapes/', + 'data_prefix': {'img_path': 'leftImg8bit/train', + 'seg_map_path': 'gtFine/train'}, + 'pipeline': [{'type': 'LoadImageFromFile'}, + {'type': 'LoadAnnotations'}, + {'type': 'RandomResize', + 'scale': (2048, 1024), + 'ratio_range': (0.5, 2.0), + 'keep_ratio': True}, + {'type': 'RandomCrop', 'crop_size': (512, 1024), 'cat_max_ratio': 0.75}, + {'type': 'RandomFlip', 'prob': 0.5}, + {'type': 'PhotoMetricDistortion'}, + {'type': 'PackSegInputs'}]}} +``` + +`cfg `是`mmengine.config.Config `的一个实例。它的接口与dict对象相同,也允许将配置值作为属性访问。更多信息请参见[MMEngine](https://github.com/open-mmlab/mmengine)中的[config tutorial](https://mmengine.readthedocs.io/en/latest/tutorials/config.html)。 + +## FAQ + +### 忽略基础配置文件里的一些字段 + +有时,您可以设置`_delete_=True `来忽略基本配置文件中的某些字段。您可以参考[MMEngine](https://github.com/open-mmlab/mmengine)中的[config tutorial](https://mmengine.readthedocs.io/en/latest/tutorials/config.html)来获得一些简单的指导。 + +例如,在MMSegmentation中,如果您想在下面的配置文件`pspnet.py `中修改PSPNet的主干网络: + +```python +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + type='EncoderDecoder', + pretrained='torchvision://resnet50', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='PSPHead', + in_channels=2048, + in_index=3, + channels=512, + pool_scales=(1, 2, 3, 6), + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0))) +``` + +用以下代码加载并解析配置文件`pspnet.py`: + +```python +from mmengine.config import Config + +cfg = Config.fromfile('pspnet.py') +print(cfg.model) +``` + +```shell +{'type': 'EncoderDecoder', + 'pretrained': 'torchvision://resnet50', + 'backbone': {'type': 'ResNetV1c', + 'depth': 50, + 'num_stages': 4, + 'out_indices': (0, 1, 2, 3), + 'dilations': (1, 1, 2, 4), + 'strides': (1, 2, 1, 1), + 'norm_cfg': {'type': 'SyncBN', 'requires_grad': True}, + 'norm_eval': False, + 'style': 'pytorch', + 'contract_dilation': True}, + 'decode_head': {'type': 'PSPHead', + 'in_channels': 2048, + 'in_index': 3, + 'channels': 512, + 'pool_scales': (1, 2, 3, 6), + 'dropout_ratio': 0.1, + 'num_classes': 19, + 'norm_cfg': {'type': 'SyncBN', 'requires_grad': True}, + 'align_corners': False, + 'loss_decode': {'type': 'CrossEntropyLoss', + 'use_sigmoid': False, + 'loss_weight': 1.0}}} +``` + +`ResNet`和`HRNet`使用不同的关键字构建,编写一个新的配置文件`hrnet.py`,如下所示: + +```python +_base_ = 'pspnet.py' +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + pretrained='open-mmlab://msra/hrnetv2_w32', + backbone=dict( + _delete_=True, + type='HRNet', + norm_cfg=norm_cfg, + extra=dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128)), + stage4=dict( + num_modules=3, + num_branches=4, + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256))))) +``` + +用以下代码加载并解析配置文件`hrnet.py`: + +```python +from mmengine.config import Config +cfg = Config.fromfile('hrnet.py') +print(cfg.model) +``` + +```shell +{'type': 'EncoderDecoder', + 'pretrained': 'open-mmlab://msra/hrnetv2_w32', + 'backbone': {'type': 'HRNet', + 'norm_cfg': {'type': 'SyncBN', 'requires_grad': True}, + 'extra': {'stage1': {'num_modules': 1, + 'num_branches': 1, + 'block': 'BOTTLENECK', + 'num_blocks': (4,), + 'num_channels': (64,)}, + 'stage2': {'num_modules': 1, + 'num_branches': 2, + 'block': 'BASIC', + 'num_blocks': (4, 4), + 'num_channels': (32, 64)}, + 'stage3': {'num_modules': 4, + 'num_branches': 3, + 'block': 'BASIC', + 'num_blocks': (4, 4, 4), + 'num_channels': (32, 64, 128)}, + 'stage4': {'num_modules': 3, + 'num_branches': 4, + 'block': 'BASIC', + 'num_blocks': (4, 4, 4, 4), + 'num_channels': (32, 64, 128, 256)}}}, + 'decode_head': {'type': 'PSPHead', + 'in_channels': 2048, + 'in_index': 3, + 'channels': 512, + 'pool_scales': (1, 2, 3, 6), + 'dropout_ratio': 0.1, + 'num_classes': 19, + 'norm_cfg': {'type': 'SyncBN', 'requires_grad': True}, + 'align_corners': False, + 'loss_decode': {'type': 'CrossEntropyLoss', + 'use_sigmoid': False, + 'loss_weight': 1.0}}} +``` + +`_delete_=True` 将用新的键去替换 `backbone` 字段内所有旧的键。 + +### 使用配置文件里的中间变量 + +配置文件中会使用一些中间变量,例如数据集(datasets)字段里的 `train_pipeline`/`test_pipeline`。 需要注意的是,在子配置文件里修改中间变量时,您需要再次传递这些变量给对应的字段。例如,我们想改变在训练或测试PSPNet时采用的多尺度策略 (multi scale strategy),`train_pipeline`/`test_pipeline` 是我们需要修改的中间变量。 + +```python +_base_ = '../pspnet/pspnet_r50-d8_4xb4-40k_cityscpaes-512x1024.py' +crop_size = (512, 1024) +img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='RandomResize', + img_scale=(2048, 1024), + ratio_range=(1., 2.), + keep_ration=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', flip_ratio=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs'), +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', + scale=(2048, 1024), + keep_ratio=True), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='leftImg8bit/train', seg_map_path='gtFine/train'), + pipeline=train_pipeline) +test_dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='leftImg8bit/val', seg_map_path='gtFine/val'), + pipeline=test_pipeline) +train_dataloader = dict(dataset=train_dataset) +val_dataloader = dict(dataset=test_dataset) +test_dataloader = val_dataloader +``` + +我们首先需要定义新的 `train_pipeline`/`test_pipeline` 然后传递到 `dataset` 里。 + +类似的,如果我们想从 `SyncBN` 切换到 `BN` 或者 `MMSyncBN`,我们需要替换配置文件里的每一个 `norm_cfg`。 + +```python +_base_ = '../pspnet/pspnet_r50-d8_4xb4-40k_cityscpaes-512x1024.py' +norm_cfg = dict(type='BN', requires_grad=True) +model = dict( + backbone=dict(norm_cfg=norm_cfg), + decode_head=dict(norm_cfg=norm_cfg), + auxiliary_head=dict(norm_cfg=norm_cfg)) +``` + +## 通过脚本参数修改配置文件 + +在[training script](https://github.com/open-mmlab/mmsegmentation/blob/1.x/tools/train.py)和[testing script](https://github.com/open-mmlab/mmsegmentation/blob/1.x/tools/test.py)中,我们支持脚本参数 `--cfg-options`,它可以帮助用户覆盖所使用的配置中的一些设置,`xxx=yyy` 格式的键值对将合并到配置文件中。 + +例如,这是一个简化的脚本 `demo_script.py `: + +```python +import argparse + +from mmengine.config import Config, DictAction + +def parse_args(): + parser = argparse.ArgumentParser(description='Script Example') + parser.add_argument('config', help='train config file path') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + args = parser.parse_args() + return args + +def main(): + args = parse_args() + + cfg = Config.fromfile(args.config) + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + print(cfg) + +if __name__ == '__main__': + main() +``` + +一个配置文件示例 `demo_config.py` 如下所示: + +```python +backbone = dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_eval=False, + style='pytorch', + contract_dilation=True) +``` + +运行 `demo_script.py`: + +```shell +python demo_script.py demo_config.py +``` + +```shell +Config (path: demo_config.py): {'backbone': {'type': 'ResNetV1c', 'depth': 50, 'num_stages': 4, 'out_indices': (0, 1, 2, 3), 'dilations': (1, 1, 2, 4), 'strides': (1, 2, 1, 1), 'norm_eval': False, 'style': 'pytorch', 'contract_dilation': True}} +``` + +通过脚本参数修改配置: + +```shell +python demo_script.py demo_config.py --cfg-options backbone.depth=101 +``` + +```shell +Config (path: demo_config.py): {'backbone': {'type': 'ResNetV1c', 'depth': 101, 'num_stages': 4, 'out_indices': (0, 1, 2, 3), 'dilations': (1, 1, 2, 4), 'strides': (1, 2, 1, 1), 'norm_eval': False, 'style': 'pytorch', 'contract_dilation': True}} +``` + +- 更新列表/元组的值。 + + 如果要更新的值是一个 list 或 tuple。例如,需要在配置文件 `demo_config.py ` 的 `backbone ` 中设置 `stride =(1,2,1,1) `。 + 如果您想更改这个键,你可以用两种方式进行指定: + + 1. `--cfg-options backbone.strides="(1, 1, 1, 1)"`. 注意引号 " 是支持 list/tuple 数据类型所必需的。 + + ```shell + python demo_script.py demo_config.py --cfg-options backbone.strides="(1, 1, 1, 1)" + ``` + + ```shell + Config (path: demo_config.py): {'backbone': {'type': 'ResNetV1c', 'depth': 50, 'num_stages': 4, 'out_indices': (0, 1, 2, 3), 'dilations': (1, 1, 2, 4), 'strides': (1, 1, 1, 1), 'norm_eval': False, 'style': 'pytorch', 'contract_dilation': True}} + ``` + + 2. `--cfg-options backbone.strides=1,1,1,1`. 注意,在指定的值中**不允许**有空格。 + + 另外,如果原来的类型是tuple,通过这种方式修改后会自动转换为list。 + + ```shell + python demo_script.py demo_config.py --cfg-options backbone.strides=1,1,1,1 + ``` + + ```shell + Config (path: demo_config.py): {'backbone': {'type': 'ResNetV1c', 'depth': 50, 'num_stages': 4, 'out_indices': (0, 1, 2, 3), 'dilations': (1, 1, 2, 4), 'strides': [1, 1, 1, 1], 'norm_eval': False, 'style': 'pytorch', 'contract_dilation': True}} + ``` + +```{note} + 这种修改方法仅支持修改string、int、float、boolean、None、list和tuple类型的配置项。 + 具体来说,对于list和tuple类型的配置项,它们内部的元素也必须是上述七种类型之一。 +``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/2_dataset_prepare.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/2_dataset_prepare.md new file mode 100644 index 0000000..e32303a --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/2_dataset_prepare.md @@ -0,0 +1,802 @@ +# 教程2:准备数据集 + +我们建议将数据集根目录符号链接到 `$MMSEGMENTATION/data`。 +如果您的目录结构不同,您可能需要更改配置文件中相应的路径。 +对于中国境内的用户,我们也推荐通过开源数据平台 [OpenDataLab](https://opendatalab.com/) 来下载dsdl标准数据,以获得更好的下载和使用体验,这里有一个下载dsdl数据集并进行训练的案例[DSDLReadme](../../../configs/dsdl/README.md),欢迎尝试。 + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── cityscapes +│ │ ├── leftImg8bit +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── gtFine +│ │ │ ├── train +│ │ │ ├── val +│ ├── VOCdevkit +│ │ ├── VOC2012 +│ │ │ ├── JPEGImages +│ │ │ ├── SegmentationClass +│ │ │ ├── ImageSets +│ │ │ │ ├── Segmentation +│ │ ├── VOC2010 +│ │ │ ├── JPEGImages +│ │ │ ├── SegmentationClassContext +│ │ │ ├── ImageSets +│ │ │ │ ├── SegmentationContext +│ │ │ │ │ ├── train.txt +│ │ │ │ │ ├── val.txt +│ │ │ ├── trainval_merged.json +│ │ ├── VOCaug +│ │ │ ├── dataset +│ │ │ │ ├── cls +│ ├── ade +│ │ ├── ADEChallengeData2016 +│ │ │ ├── annotations +│ │ │ │ ├── training +│ │ │ │ ├── validation +│ │ │ ├── images +│ │ │ │ ├── training +│ │ │ │ ├── validation +│ ├── coco_stuff10k +│ │ ├── images +│ │ │ ├── train2014 +│ │ │ ├── test2014 +│ │ ├── annotations +│ │ │ ├── train2014 +│ │ │ ├── test2014 +│ │ ├── imagesLists +│ │ │ ├── train.txt +│ │ │ ├── test.txt +│ │ │ ├── all.txt +│ ├── coco_stuff164k +│ │ ├── images +│ │ │ ├── train2017 +│ │ │ ├── val2017 +│ │ ├── annotations +│ │ │ ├── train2017 +│ │ │ ├── val2017 +│ ├── CHASE_DB1 +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ ├── DRIVE +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ ├── HRF +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ ├── STARE +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +| ├── dark_zurich +| │   ├── gps +| │   │   ├── val +| │   │   └── val_ref +| │   ├── gt +| │   │   └── val +| │   ├── LICENSE.txt +| │   ├── lists_file_names +| │   │   ├── val_filenames.txt +| │   │   └── val_ref_filenames.txt +| │   ├── README.md +| │   └── rgb_anon +| │   | ├── val +| │   | └── val_ref +| ├── NighttimeDrivingTest +| | ├── gtCoarse_daytime_trainvaltest +| | │   └── test +| | │   └── night +| | └── leftImg8bit +| | | └── test +| | | └── night +│ ├── loveDA +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ │ ├── test +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── potsdam +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── vaihingen +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── iSAID +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ │ ├── test +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── synapse +│ │ ├── img_dir +│ │ │ ├── train +│ │ │ ├── val +│ │ ├── ann_dir +│ │ │ ├── train +│ │ │ ├── val +│ ├── REFUGE +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ ├── mapillary +│ │ ├── training +│ │ │ ├── images +│ │ │ ├── v1.2 +| │ │ │ ├── instances +| │ │ │ ├── labels +| │   │   │ └── panoptic +│ │ │ ├── v2.0 +| │ │ │ ├── instances +| │ │ │ ├── labels +| │ │ │ ├── panoptic +| │   │   │ └── polygons +│ │ ├── validation +│ │ │ ├── images +| │ │ ├── v1.2 +| │ │ │ ├── instances +| │ │ │ ├── labels +| │   │   │ └── panoptic +│ │ │ ├── v2.0 +| │ │ │ ├── instances +| │ │ │ ├── labels +| │ │ │ ├── panoptic +| │   │   │ └── polygons +│ ├── bdd100k +│ │ ├── images +│ │ │ └── 10k +| │ │ │ ├── test +| │ │ │ ├── train +| │   │   │ └── val +│ │ └── labels +│ │ │ └── sem_seg +| │ │ │ ├── colormaps +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── masks +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── polygons +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +| │   │   │ └── rles +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +│ ├── nyu +│ │ ├── images +│ │ │ ├── train +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── train +│ │ │ ├── test +│ ├── HSIDrive20 +│ │ ├── images +│ │ │ ├── train +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── train +│ │ │ ├── validation +│ │ │ ├── test +``` + +## 用 MIM 下载数据集 + +通过使用 [OpenXLab](https://openxlab.org.cn/datasets),您可以直接下载开源数据集。通过平台的搜索功能,您可以快速轻松地找到他们正在寻找的数据集。使用平台上的格式化数据集,您可以高效地跨数据集执行任务。 + +如果您使用 MIM 下载,请确保版本大于 v0.3.8。您可以使用以下命令进行更新、安装、登录和数据集下载: + +```shell +# upgrade your MIM +pip install -U openmim + +# install OpenXLab CLI tools +pip install -U openxlab +# log in OpenXLab +openxlab login + +# download ADE20K by MIM +mim download mmsegmentation --dataset ade20k +``` + +## Cityscapes + +Cityscapes [官方网站](https://www.cityscapes-dataset.com/)可以下载 Cityscapes 数据集,按照官网要求注册并登陆后,数据可以在[这里](https://www.cityscapes-dataset.com/downloads/)找到。 + +按照惯例,`**labelTrainIds.png` 用于 cityscapes 训练。 +我们提供了一个基于 [cityscapesscripts](https://github.com/mcordts/cityscapesScripts) 的[脚本](https://github.com/open-mmlab/mmsegmentation/blob/1.x/tools/dataset_converters/cityscapes.py)用于生成 `**labelTrainIds.png`。 + +```shell +# --nproc 表示 8 个转换进程,也可以省略。 +python tools/dataset_converters/cityscapes.py data/cityscapes --nproc 8 +``` + +## Pascal VOC + +Pascal VOC 2012 可从[此处](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar)下载。 +此外,Pascal VOC 数据集的最新工作通常利用额外的增强数据,可以在[这里](http://www.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/semantic_contours/benchmark.tgz)找到。 + +如果您想使用增强的 VOC 数据集,请运行以下命令将增强数据的标注转换为正确的格式。 + +```shell +# --nproc 表示 8 个转换进程,也可以省略。 +python tools/dataset_converters/voc_aug.py data/VOCdevkit data/VOCdevkit/VOCaug --nproc 8 +``` + +请参考[拼接数据集文档](../advanced_guides/add_datasets.md#拼接数据集)及 [voc_aug 配置示例](../../../configs/_base_/datasets/pascal_voc12_aug.py)以详细了解如何将它们拼接并合并训练。 + +## ADE20K + +ADE20K 的训练和验证集可以从这个[链接](http://data.csail.mit.edu/places/ADEchallenge/ADEChallengeData2016.zip)下载。 +如果需要下载测试数据集,可以在[官网](http://host.robots.ox.ac.uk/)注册后,下载[测试集](http://host.robots.ox.ac.uk:8080/eval/downloads/VOC2010test.tar)。 + +## Pascal Context + +Pascal Context 的训练和验证集可以从[此处](http://host.robots.ox.ac.uk/pascal/VOC/voc2010/VOCtrainval_03-May-2010.tar)下载。注册后,您也可以从[此处](http://host.robots.ox.ac.uk:8080/eval/downloads/VOC2010test.tar)下载测试集。 + +从原始数据集中抽出部分数据作为验证集,您可以从[此处](https://codalabuser.blob.core.windows.net/public/trainval_merged.json)下载 trainval_merged.json 文件。 + +请先安装 [Detail](https://github.com/zhanghang1989/detail-api) 工具然后运行以下命令将标注转换为正确的格式。 + +```shell +python tools/dataset_converters/pascal_context.py data/VOCdevkit data/VOCdevkit/VOC2010/trainval_merged.json +``` + +## COCO Stuff 10k + +数据可以通过 wget 在[这里](http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/cocostuff-10k-v1.1.zip)下载。 + +对于 COCO Stuff 10k 数据集,请运行以下命令下载并转换数据集。 + +```shell +# 下载 +mkdir coco_stuff10k && cd coco_stuff10k +wget http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/cocostuff-10k-v1.1.zip + +# 解压 +unzip cocostuff-10k-v1.1.zip + +# --nproc 表示 8 个转换进程,也可以省略。 +python tools/dataset_converters/coco_stuff10k.py /path/to/coco_stuff10k --nproc 8 +``` + +按照惯例,`/path/to/coco_stuff164k/annotations/*2014/*_labelTrainIds.png` 中的 mask 标注用于 COCO Stuff 10k 的训练和测试。 + +## COCO Stuff 164k + +对于 COCO Stuff 164k 数据集,请运行以下命令下载并转换增强的数据集。 + +```shell +# 下载 +mkdir coco_stuff164k && cd coco_stuff164k +wget http://images.cocodataset.org/zips/train2017.zip +wget http://images.cocodataset.org/zips/val2017.zip +wget http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip + +# 解压 +unzip train2017.zip -d images/ +unzip val2017.zip -d images/ +unzip stuffthingmaps_trainval2017.zip -d annotations/ + +# --nproc 表示 8 个转换进程,也可以省略。 +python tools/dataset_converters/coco_stuff164k.py /path/to/coco_stuff164k --nproc 8 +``` + +按照惯例,`/path/to/coco_stuff164k/annotations/*2017/*_labelTrainIds.png` 中的 mask 标注用于 COCO Stuff 164k 的训练和测试。 + +此数据集的详细信息可在[此处](https://github.com/nightrome/cocostuff#downloads)找到。 + +## CHASE DB1 + +CHASE DB1 的训练和验证集可以从[此处](https://staffnet.kingston.ac.uk/~ku15565/CHASE_DB1/assets/CHASEDB1.zip)下载。 + +请运行以下命令,准备 CHASE DB1 数据集: + +```shell +python tools/dataset_converters/chase_db1.py /path/to/CHASEDB1.zip +``` + +该脚本将自动调整数据集目录结构,使其满足 MMSegmentation 数据集加载要求。 + +## DRIVE + +按照[官网](https://drive.grand-challenge.org/)要求,注册并登陆后,便可以下载 DRIVE 的训练和验证数据集。 + +要将 DRIVE 数据集转换为 MMSegmentation 的格式,请运行以下命令: + +```shell +python tools/dataset_converters/drive.py /path/to/training.zip /path/to/test.zip +``` + +该脚本将自动调整数据集目录结构,使其满足 MMSegmentation 数据集加载要求。 + +## HRF + +请下载 [health.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/healthy.zip)、[glaucoma.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/glaucoma.zip)、[diabetic_retinopathy.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/diabetic_retinopathy.zip)、[healthy_manualsegm.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/healthy_manualsegm.zip)、[glaucoma_manualsegm.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/glaucoma_manualsegm.zip) 和 [diabetic_retinopathy_manualsegm.zip](https://www5.cs.fau.de/fileadmin/research/datasets/fundus-images/diabetic_retinopathy_manualsegm.zip),无需解压,可以直接运行以下命令,准备 HRF 数据集: + +```shell +python tools/dataset_converters/hrf.py /path/to/healthy.zip /path/to/healthy_manualsegm.zip /path/to/glaucoma.zip /path/to/glaucoma_manualsegm.zip /path/to/diabetic_retinopathy.zip /path/to/diabetic_retinopathy_manualsegm.zip +``` + +该脚本将自动调整数据集目录结构,使其满足 MMSegmentation 数据集加载要求。 + +## STARE + +请下载 [stare images.tar](http://cecas.clemson.edu/~ahoover/stare/probing/stare-images.tar)、[labels-ah.tar](http://cecas.clemson.edu/~ahoover/stare/probing/labels-ah.tar) 和 [labels-vk.tar](http://cecas.clemson.edu/~ahoover/stare/probing/labels-vk.tar),无需解压,可以直接运行以下命令,准备 STARE 数据集: + +```shell +python tools/dataset_converters/stare.py /path/to/stare-images.tar /path/to/labels-ah.tar /path/to/labels-vk.tar +``` + +该脚本将自动调整数据集目录结构,使其满足 MMSegmentation 数据集加载要求。 + +## Dark Zurich + +由于我们只支持在此数据集上的模型测试,因此您只需要下载并解压[验证数据集](https://data.vision.ee.ethz.ch/csakarid/shared/GCMA_UIoU/Dark_Zurich_val_anon.zip)。 + +## Nighttime Driving + +由于我们只支持在此数据集上的模型测试,因此您只需要下载并解压[验证数据集](http://data.vision.ee.ethz.ch/daid/NighttimeDriving/NighttimeDrivingTest.zip)。 + +## LoveDA + +数据可以从[此处](https://drive.google.com/drive/folders/1ibYV0qwn4yuuh068Rnc-w4tPi0U0c-ti?usp=sharing)下载 LaveDA 数据集。 + +或者可以从 [zenodo](https://zenodo.org/record/5706578#.YZvN7SYRXdF) 下载。下载后,无需解压,直接运行以下命令: + +```shell +# 下载 Train.zip +wget https://zenodo.org/record/5706578/files/Train.zip +# 下载 Val.zip +wget https://zenodo.org/record/5706578/files/Val.zip +# 下载 Test.zip +wget https://zenodo.org/record/5706578/files/Test.zip +``` + +请对于 LoveDA 数据集,请运行以下命令调整数据集目录。 + +```shell +python tools/dataset_converters/loveda.py /path/to/loveDA +``` + +可将模型对 LoveDA 的测试集的预测结果上传至到数据集[测试服务器](https://codalab.lisn.upsaclay.fr/competitions/421),查看评测结果。 + +有关 LoveDA 的更多详细信息,可查看[此处](https://github.com/Junjue-Wang/LoveDA). + +## ISPRS Potsdam + +[Potsdam](https://www.isprs.org/education/benchmarks/UrbanSemLab/2d-sem-label-potsdam.aspx) 城市语义分割数据集用于 2D 语义分割竞赛 —— Potsdam。 + +数据集可以在竞赛[主页](https://www.isprs.org/education/benchmarks/UrbanSemLab/default.aspx)上请求获得。 +这里也提供了[BaiduNetdisk](https://pan.baidu.com/s/1K-cLVZnd1X7d8c26FQ-nGg?pwd=mseg),提取码:mseg、 [Google Drive](https://drive.google.com/drive/folders/1w3EJuyUGet6_qmLwGAWZ9vw5ogeG0zLz?usp=sharing)以及[OpenDataLab](https://opendatalab.com/ISPRS_Potsdam/download)。 +实验中需要下载 '2_Ortho_RGB.zip' 和 '5_Labels_all_noBoundary.zip'。 + +对于 Potsdam 数据集,请运行以下命令调整数据集目录。 + +```shell +python tools/dataset_converters/potsdam.py /path/to/potsdam +``` + +在我们的默认设置中,将生成 3456 张图像用于训练和 2016 张图像用于验证。 + +## ISPRS Vaihingen + +[Vaihingen](https://www.isprs.org/education/benchmarks/UrbanSemLab/2d-sem-label-vaihingen.aspx) 城市语义分割数据集用于 2D 语义分割竞赛 —— Vaihingen。 + +数据集可以在竞赛[主页](https://www.isprs.org/education/benchmarks/UrbanSemLab/default.aspx)上请求获得。 +这里也提供了[BaiduNetdisk](https://pan.baidu.com/s/109D3WLrLafsuYtLeerLiiA?pwd=mseg),提取码:mseg 、 [Google Drive](https://drive.google.com/drive/folders/1w3NhvLVA2myVZqOn2pbiDXngNC7NTP_t?usp=sharing)。 +实验中需要下载 'ISPRS_semantic_labeling_Vaihingen.zip' 和 'ISPRS_semantic_labeling_Vaihingen_ground_truth_eroded_COMPLETE.zip'。 + +对于 Vaihingen 数据集,请运行以下命令调整数据集目录。 + +```shell +python tools/dataset_converters/vaihingen.py /path/to/vaihingen +``` + +在我们的默认设置(`clip_size`=512, `stride_size`=256)中,将生成 344 张图像用于训练和 398 张图像用于验证。 + +## iSAID + +iSAID 数据集可从 [DOTA-v1.0](https://captain-whu.github.io/DOTA/dataset.html) 下载训练/验证/测试数据集的图像数据, + +并从 [iSAID](https://captain-whu.github.io/iSAID/dataset.html)下载训练/验证数据集的标注数据。 + +该数据集是航空图像实例分割和语义分割任务的大规模数据集。 + +下载 iSAID 数据集后,您可能需要按照以下结构进行数据集准备。 + +```none +├── data +│ ├── iSAID +│ │ ├── train +│ │ │ ├── images +│ │ │ │ ├── part1.zip +│ │ │ │ ├── part2.zip +│ │ │ │ ├── part3.zip +│ │ │ ├── Semantic_masks +│ │ │ │ ├── images.zip +│ │ ├── val +│ │ │ ├── images +│ │ │ │ ├── part1.zip +│ │ │ ├── Semantic_masks +│ │ │ │ ├── images.zip +│ │ ├── test +│ │ │ ├── images +│ │ │ │ ├── part1.zip +│ │ │ │ ├── part2.zip +``` + +```shell +python tools/dataset_converters/isaid.py /path/to/iSAID +``` + +在我们的默认设置(`patch_width`=896, `patch_height`=896, `overlap_area`=384)中,将生成 33978 张图像用于训练和 11644 张图像用于验证。 + +## LIP(Look Into Person) dataset + +该数据集可以从[此页面](https://lip.sysuhcp.com/overview.php)下载。 + +请运行以下命令来解压数据集。 + +```shell +unzip LIP.zip +cd LIP +unzip TrainVal_images.zip +unzip TrainVal_parsing_annotations.zip +cd TrainVal_parsing_annotations +unzip TrainVal_parsing_annotations.zip +mv train_segmentations ../ +mv val_segmentations ../ +cd .. +``` + +LIP 数据集的内容包括: + +```none +├── data +│ ├── LIP +│ │ ├── train_images +│   │ │ ├── 1000_1234574.jpg +│   │ │ ├── ... +│ │ ├── train_segmentations +│   │ │ ├── 1000_1234574.png +│   │ │ ├── ... +│ │ ├── val_images +│   │ │ ├── 100034_483681.jpg +│   │ │ ├── ... +│ │ ├── val_segmentations +│   │ │ ├── 100034_483681.png +│   │ │ ├── ... +``` + +## Synapse dataset + +此数据集可以从[此页面](https://www.synapse.org/#!Synapse:syn3193805/wiki/)下载。 + +遵循 [TransUNet](https://arxiv.org/abs/2102.04306) 的数据准备设定,将原始训练集(30 次扫描)拆分为新的训练集(18 次扫描)和验证集(12 次扫描)。请运行以下命令来准备数据集。 + +```shell +unzip RawData.zip +cd ./RawData/Training +``` + +然后创建 `train.txt` 和 `val.txt` 以拆分数据集。 + +根据 TransUnet,以下是数据集的划分。 + +train.txt + +```none +img0005.nii.gz +img0006.nii.gz +img0007.nii.gz +img0009.nii.gz +img0010.nii.gz +img0021.nii.gz +img0023.nii.gz +img0024.nii.gz +img0026.nii.gz +img0027.nii.gz +img0028.nii.gz +img0030.nii.gz +img0031.nii.gz +img0033.nii.gz +img0034.nii.gz +img0037.nii.gz +img0039.nii.gz +img0040.nii.gz +``` + +val.txt + +```none +img0008.nii.gz +img0022.nii.gz +img0038.nii.gz +img0036.nii.gz +img0032.nii.gz +img0002.nii.gz +img0029.nii.gz +img0003.nii.gz +img0001.nii.gz +img0004.nii.gz +img0025.nii.gz +img0035.nii.gz +``` + +synapse 数据集的内容包括: + +```none +├── Training +│ ├── img +│ │ ├── img0001.nii.gz +│ │ ├── img0002.nii.gz +│ │ ├── ... +│ ├── label +│ │ ├── label0001.nii.gz +│ │ ├── label0002.nii.gz +│ │ ├── ... +│ ├── train.txt +│ ├── val.txt +``` + +然后,使用此命令转换 synapse 数据集。 + +```shell +python tools/dataset_converters/synapse.py --dataset-path /path/to/synapse +``` + +注意,MMSegmentation 的默认评估指标(例如 mean dice value)是在 2D 切片图像上计算的,这与 [TransUNet](https://arxiv.org/abs/2102.04306) 等一些论文中的 3D 扫描结果是不同的。 + +## REFUGE + +在 [REFUGE Challenge](https://refuge.grand-challenge.org) 官网上注册并下载 [REFUGE 数据集](https://refuge.grand-challenge.org/REFUGE2Download)。 + +然后,解压 `REFUGE2.zip`,原始数据集的内容包括: + +```none +├── REFUGE2 +│ ├── REFUGE2 +│ │ ├── Annotation-Training400.zip +│ │ ├── REFUGE-Test400.zip +│ │ ├── REFUGE-Test-GT.zip +│ │ ├── REFUGE-Training400.zip +│ │ ├── REFUGE-Validation400.zip +│ │ ├── REFUGE-Validation400-GT.zip +│ ├── __MACOSX +``` + +请运行以下命令转换 REFUGE 数据集: + +```shell +python tools/convert_datasets/refuge.py --raw_data_root=/path/to/refuge/REFUGE2/REFUGE2 +``` + +脚本会将目录结构转换如下: + +```none +│ ├── REFUGE +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +``` + +包含 400 张用于训练的图像、400 张用于验证的图像和 400 张用于测试的图像,这与 REFUGE 2018 数据集相同。 + +## Mapillary Vistas Datasets + +- Mapillary Vistas [官方网站](https://www.mapillary.com/dataset/vistas) 可以下载 Mapillary Vistas 数据集,按照官网要求注册并登陆后,数据可以在[这里](https://www.mapillary.com/dataset/vistas)找到。 + +- Mapillary Vistas 数据集使用 8-bit with color-palette 来存储标签。不需要进行转换操作。 + +- 假设您已将数据集 zip 文件放在 `mmsegmentation/data/mapillary` 中 + +- 请运行以下命令来解压数据集。 + + ```bash + cd data/mapillary + unzip An-ZjB1Zm61yAZG0ozTymz8I8NqI4x0MrYrh26dq7kPgfu8vf9ImrdaOAVOFYbJ2pNAgUnVGBmbue9lTgdBOb5BbKXIpFs0fpYWqACbrQDChAA2fdX0zS9PcHu7fY8c-FOvyBVxPNYNFQuM.zip + ``` + +- 解压后,您将获得类似于此结构的 Mapillary Vistas 数据集。语义分割 mask 标签在 `labels` 文件夹中。 + + ```none + mmsegmentation + ├── mmseg + ├── tools + ├── configs + ├── data + │ ├── mapillary + │ │ ├── training + │ │ │ ├── images + │ │ │ ├── v1.2 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │   │   │ └── panoptic + │ │ │ ├── v2.0 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── panoptic + | │   │   │ └── polygons + │ │ ├── validation + │ │ │ ├── images + | │ │ ├── v1.2 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │   │   │ └── panoptic + │ │ │ ├── v2.0 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── panoptic + | │   │   │ └── polygons + ``` + +- 您可以在配置中使用 `MapillaryDataset_v1` 和 `Mapillary Dataset_v2` 设置数据集版本。 + 在此处 [V1.2](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/_base_/datasets/mapillary_v1.py) 和 [V2.0](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/_base_/datasets/mapillary_v2.py) 查看 Mapillary Vistas 数据集配置文件 + +## LEVIR-CD + +[LEVIR-CD](https://justchenhao.github.io/LEVIR/) 大规模遥感建筑变化检测数据集。 + +数据集可以在[主页](https://justchenhao.github.io/LEVIR/)上请求获得。 + +数据集的补充版本可以在[主页](https://github.com/S2Looking/Dataset)上请求获得。 + +请下载数据集的补充版本,然后解压 `LEVIR-CD+.zip`,数据集的内容包括: + +```none +│ ├── LEVIR-CD+ +│ │ ├── train +│ │ │ ├── A +│ │ │ ├── B +│ │ │ ├── label +│ │ ├── test +│ │ │ ├── A +│ │ │ ├── B +│ │ │ ├── label +``` + +对于 LEVIR-CD 数据集,请运行以下命令无重叠裁剪影像: + +```shell +python tools/dataset_converters/levircd.py --dataset-path /path/to/LEVIR-CD+ --out_dir /path/to/LEVIR-CD +``` + +裁剪后的影像大小为256x256,与原论文保持一致。 + +## BDD100K + +- 可以从[官方网站](https://bdd-data.berkeley.edu/) 下载 BDD100K数据集(语义分割任务主要是10K数据集),按照官网要求注册并登陆后,数据可以在[这里](https://bdd-data.berkeley.edu/portal.html#download)找到。 + +- 图像数据对应的名称是是`10K Images`, 语义分割标注对应的名称是`Segmentation` + +- 下载后,可以使用以下代码进行解压 + + ```bash + unzip ~/bdd100k_images_10k.zip -d ~/mmsegmentation/data/ + unzip ~/bdd100k_sem_seg_labels_trainval.zip -d ~/mmsegmentation/data/ + ``` + +就可以得到以下文件结构了: + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── bdd100k +│ │ ├── images +│ │ │ └── 10k +| │ │ │ ├── test +| │ │ │ ├── train +| │   │   │ └── val +│ │ └── labels +│ │ │ └── sem_seg +| │ │ │ ├── colormaps +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── masks +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── polygons +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +| │   │   │ └── rles +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +``` + +## NYU + +- 您可以从 [这个链接](https://drive.google.com/file/d/1wC-io-14RCIL4XTUrQLk6lBqU2AexLVp/view?usp=share_link) 下载 NYU 数据集 + +- 下载完成后,您可以使用 [tools/dataset_converters/nyu.py](/tools/dataset_converters/nyu.py) 脚本来解压和组织数据到所需的格式 + + ```bash + python tools/dataset_converters/nyu.py nyu.zip + ``` + +## HSI Drive 2.0 + +- 您可以从以下位置下载 HSI Drive 2.0 数据集 [here](https://ipaccess.ehu.eus/HSI-Drive/#download) 刚刚向 gded@ehu.eus 发送主题为“下载 HSI-Drive”的电子邮件后 您将收到解压缩文件的密码. + +- 下载后,按照以下说明解压: + + ```bash + 7z x -p"password" ./HSI_Drive_v2_0_Phyton.zip + + mv ./HSIDrive20 path_to_mmsegmentation/data + mv ./HSI_Drive_v2_0_release_notes_Python_version.md path_to_mmsegmentation/data + mv ./image_numbering.pdf path_to_mmsegmentation/data + ``` + +- 解压后得到: + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── HSIDrive20 +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── images_MF +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── RGB +│ │ ├── training_filenames.txt +│ │ ├── validation_filenames.txt +│ │ ├── test_filenames.txt +│ ├── HSI_Drive_v2_0_release_notes_Python_version.md +│ ├── image_numbering.pdf +``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/3_inference.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/3_inference.md new file mode 100644 index 0000000..0afcb4b --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/3_inference.md @@ -0,0 +1,244 @@ +# 教程3:使用预训练模型推理 + +MMSegmentation 在 [Model Zoo](../Model_Zoo.md) 中为语义分割提供了预训练的模型,并支持多个标准数据集,包括 Cityscapes、ADE20K 等。 +本说明将展示如何使用现有模型对给定图像进行推理。 +关于如何在标准数据集上测试现有模型,请参阅本[指南](./4_train_test.md) + +MMSegmentation 为用户提供了数个接口,以便轻松使用预训练的模型进行推理。 + +- [教程3:使用预训练模型推理](#教程3使用预训练模型推理) + - [推理器](#推理器) + - [基本使用](#基本使用) + - [初始化](#初始化) + - [可视化预测结果](#可视化预测结果) + - [模型列表](#模型列表) + - [推理 API](#推理-api) + - [mmseg.apis.init_model](#mmsegapisinit_model) + - [mmseg.apis.inference_model](#mmsegapisinference_model) + - [mmseg.apis.show_result_pyplot](#mmsegapisshow_result_pyplot) + +## 推理器 + +在 MMSegmentation 中,我们提供了最**方便的**方式 `MMSegInferencer` 来使用模型。您只需 3 行代码就可以获得图像的分割掩膜。 + +### 基本使用 + +以下示例展示了如何使用 `MMSegInferencer` 对单个图像执行推理。 + +``` +>>> from mmseg.apis import MMSegInferencer +>>> # 将模型加载到内存中 +>>> inferencer = MMSegInferencer(model='deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024') +>>> # 推理 +>>> inferencer('demo/demo.png', show=True) +``` + +可视化结果应如下所示: + +

+ +
+ +此外,您可以使用 `MMSegInferencer` 来处理一个包含多张图片的 `list`: + +``` +# 输入一个图片 list +>>> images = [image1, image2, ...] # image1 可以是文件路径或 np.ndarray +>>> inferencer(images, show=True, wait_time=0.5) # wait_time 是延迟时间,0 表示无限 + +# 或输入图像目录 +>>> images = $IMAGESDIR +>>> inferencer(images, show=True, wait_time=0.5) + +# 保存可视化渲染彩色分割图和预测结果 +# out_dir 是保存输出结果的目录,img_out_dir 和 pred_out_dir 为 out_dir 的子目录 +# 以保存可视化渲染彩色分割图和预测结果 +>>> inferencer(images, out_dir='outputs', img_out_dir='vis', pred_out_dir='pred') +``` + +推理器有一个可选参数 `return_datasamples`,其默认值为 False,推理器的返回值默认为 `dict` 类型,包括 'visualization' 和 'predictions' 两个 key。 +如果 `return_datasamples=True` 推理器将返回 [`SegDataSample`](../advanced_guides/structures.md) 或其列表。 + +``` +result = inferencer('demo/demo.png') +# 结果是一个包含 'visualization' 和 'predictions' 两个 key 的 `dict` +# 'visualization' 包含彩色分割图 +print(result['visualization'].shape) +# (512, 683, 3) + +# 'predictions' 包含带有标签索引的分割掩膜 +print(result['predictions'].shape) +# (512, 683) + +result = inferencer('demo/demo.png', return_datasamples=True) +print(type(result)) +# + +# 输入一个图片 list +results = inferencer(images) +# 输出为列表 +print(type(results['visualization']), results['visualization'][0].shape) +# (512, 683, 3) +print(type(results['predictions']), results['predictions'][0].shape) +# (512, 683) + +results = inferencer(images, return_datasamples=True) +# +print(type(results[0])) +# +``` + +### 初始化 + +`MMSegInferencer` 必须使用 `model` 初始化,该 `model` 可以是模型名称或一个 `Config`,甚至可以是配置文件的路径。 +模型名称可以在模型的元文件(configs/xxx/metafile.yaml)中找到,比如 maskformer 的一个模型名称是 `maskformer_r50-d32_8xb2-160k_ade20k-512x512`,如果输入模型名称,模型的权重将自动下载。以下是其他输入参数: + +- weights(str,可选)- 权重的路径。如果未指定,并且模型是元文件中的模型名称,则权重将从元文件加载。默认为 None。 +- classes(list,可选)- 输入类别用于结果渲染,由于分割模型的预测结构是标签索引的分割图,`classes` 是一个相应的标签索引的类别列表。若 classes 没有定义,可视化工具将默认使用 `cityscapes` 的类别。默认为 None。 +- palette(list,可选)- 输入调色盘用于结果渲染,它是对应分类的配色列表。若 palette 没有定义,可视化工具将默认使用 `cityscapes` 的调色盘。默认为 None。 +- dataset_name(str,可选)- [数据集名称或别名](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/utils/class_names.py#L302-L317),可视化工具将使用数据集的元信息,如类别和配色,但 `classes` 和 `palette` 具有更高的优先级。默认为 None。 +- device(str,可选)- 运行推理的设备。如果无,则会自动使用可用的设备。默认为 None。 +- scope(str,可选)- 模型的作用域。默认为 'mmseg'。 + +### 可视化预测结果 + +`MMSegInferencer` 有4个用于可视化预测的参数,您可以在初始化推理器时使用它们: + +- show(bool)- 是否弹出窗口显示图像。默认为 False。 +- wait_time(float)- 显示的间隔。默认值为 0。 +- img_out_dir(str)- `out_dir` 的子目录,用于保存渲染有色分割掩膜,因此如果要保存预测掩膜,则必须定义 `out_dir`。默认为 `vis`。 +- opacity(int,float)- 分割掩膜的透明度。默认值为 0.8。 + +这些参数的示例请参考[基本使用](#基本使用) + +### 模型列表 + +在 MMSegmentation 中有一个非常容易列出所有模型名称的方法 + +``` +>>> from mmseg.apis import MMSegInferencer +# models 是一个模型名称列表,它们将自动打印 +>>> models = MMSegInferencer.list_models('mmseg') +``` + +## 推理 API + +### mmseg.apis.init_model + +从配置文件初始化一个分割器。 + +参数: + +- config(str,`Path` 或 `mmengine.Config`)- 配置文件路径或配置对象。 +- checkpoint(str,可选)- 权重路径。如果为 None,则模型将不会加载任何权重。 +- device(str,可选)- CPU/CUDA 设备选项。默认为 'cuda:0'。 +- cfg_options(dict,可选)- 用于覆盖所用配置中的某些设置的选项。 + +返回值: + +- nn.Module:构建好的分割器。 + +示例: + +```python +from mmseg.apis import init_model + +config_path = 'configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +checkpoint_path = 'checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth' + +# 初始化不带权重的模型 +model = init_model(config_path) + +# 初始化模型并加载权重 +model = init_model(config_path, checkpoint_path) + +# 在 CPU 上的初始化模型并加载权重 +model = init_model(config_path, checkpoint_path, 'cpu') +``` + +### mmseg.apis.inference_model + +使用分割器推理图像。 + +参数: + +- model(nn.Module)- 加载的分割器 +- imgs(str,np.ndarray 或 list\[str/np.ndarray\])- 图像文件或加载的图像 + +返回值: + +- `SegDataSample` 或 list\[`SegDataSample`\]:如果 imgs 是列表或元组,则返回相同长度的列表类型结果,否则直接返回分割结果。 + +**注意:** [SegDataSample](https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/structures/seg_data_sample.py) 是 MMSegmentation 的数据结构接口,用作不同组件之间的接口。`SegDataSample` 实现抽象数据元素 `mmengine.structures.BaseDataElement`,请参阅 [MMEngine](https://github.com/open-mmlab/mmengine) 中的数据元素[文档](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/data_element.html)了解更多信息。 + +`SegDataSample` 中的参数分为几个部分: + +- `gt_sem_seg`(`PixelData`)- 语义分割的标注。 +- `pred_sem_seg`(`PixelData`)- 语义分割的预测。 +- `seg_logits`(`PixelData`)- 模型最后一层的输出结果。 + +**注意:** [PixelData](https://github.com/open-mmlab/mmengine/blob/main/mmengine/structures/pixel_data.py) 是像素级标注或预测的数据结构,请参阅 [MMEngine](https://github.com/open-mmlab/mmengine) 中的 PixelData [文档](https://mmengine.readthedocs.io/en/latest/advanced_tutorials/data_element.html)了解更多信息。 + +示例: + +```python +from mmseg.apis import init_model, inference_model + +config_path = 'configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +checkpoint_path = 'checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth' +img_path = 'demo/demo.png' + + +model = init_model(config_path, checkpoint_path) +result = inference_model(model, img_path) +``` + +### mmseg.apis.show_result_pyplot + +在图像上可视化分割结果。 + +参数: + +- model(nn.Module)- 加载的分割器。 +- img(str 或 np.ndarray)- 图像文件名或加载的图像。 +- result(`SegDataSample`)- SegDataSample 预测结果。 +- opacity(float)- 绘制分割图的不透明度。默认值为 `0.5`,必须在 `(0,1]` 范围内。 +- title(str)- pyplot 图的标题。默认值为 ''。 +- draw_gt(bool)- 是否绘制 GT SegDataSample。默认为 `True`。 +- draw_pred(draws_pred)- 是否绘制预测 SegDataSample。默认为 `True`。 +- wait_time(float)- 显示的间隔,0 是表示“无限”的特殊值。默认为 `0`。 +- show(bool)- 是否展示绘制的图像。默认为 `True`。 +- save_dir(str,可选)- 为所有存储后端保存的文件路径。如果为 `None`,则后端存储将不会保存任何数据。 +- out_file(str,可选)- 输出文件的路径。默认为 `None`。 + +返回值: + +- np.ndarray:通道为 RGB 的绘制图像。 + +示例: + +```python +from mmseg.apis import init_model, inference_model, show_result_pyplot + +config_path = 'configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py' +checkpoint_path = 'checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth' +img_path = 'demo/demo.png' + + +# 从配置文件和权重文件构建模型 +model = init_model(config_path, checkpoint_path, device='cuda:0') + +# 推理给定图像 +result = inference_model(model, img_path) + +# 展示分割结果 +vis_image = show_result_pyplot(model, img_path, result) + +# 保存可视化结果,输出图像将在 `workdirs/result.png` 路径下找到 +vis_iamge = show_result_pyplot(model, img_path, result, out_file='work_dirs/result.png') + +# 修改展示图像的时间,注意 0 是表示“无限”的特殊值 +vis_image = show_result_pyplot(model, img_path, result, wait_time=5) +``` + +**注意:** 如果当前设备没有图形用户界面,建议将 `show` 设置为 `False`,并指定 `out_file` 或 `save_dir` 来保存结果。如果您想在窗口上显示结果,则不需要特殊设置。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/4_train_test.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/4_train_test.md new file mode 100644 index 0000000..f821aca --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/4_train_test.md @@ -0,0 +1,317 @@ +# 教程4:使用现有模型进行训练和测试 + +MMSegmentation 支持在多种设备上训练和测试模型。如下文,具体方式分别为单GPU、分布式以及计算集群的训练和测试。通过本教程,您将知晓如何用 MMSegmentation 提供的脚本进行训练和测试。 + +## 在单GPU上训练和测试 + +### 在单GPU上训练 + +`tools/train.py` 文件提供了在单GPU上部署训练任务的方法。 + +基础用法如下: + +```shell +python tools/train.py ${配置文件} [可选参数] +``` + +- `--work-dir ${工作路径}`: 重新指定工作路径 +- `--amp`: 使用自动混合精度计算 +- `--resume`: 从工作路径中保存的最新检查点文件(checkpoint)恢复训练 +- `--cfg-options ${需更覆盖的配置}`: 覆盖已载入的配置中的部分设置,并且 以 xxx=yyy 格式的键值对 将被合并到配置文件中。 + 比如: '--cfg-option model.encoder.in_channels=6', 更多细节请看[指导](./1_config.md#Modify-config-through-script-arguments)。 + +下面是对于多GPU测试的可选参数: + +- `--launcher`: 执行器的启动方式。允许选择的参数值有 `none`, `pytorch`, `slurm`, `mpi`。特别的,如果设置为none,测试将非分布式模式下进行。 +- `--local_rank`: 分布式中进程的序号。如果没有指定,默认设置为0。 + +**注意:** 命令行参数 `--resume` 和在配置文件中的参数 `load_from` 的不同之处: + +`--resume` 只决定是否继续使用工作路径中最新的检查点,它常常用于恢复被意外打断的训练。 + +`load_from` 会明确指定被载入的检查点文件,且训练迭代器将从0开始,通常用于微调模型。 + +如果您希望从指定的检查点上恢复训练您可以使用: + +```python +python tools/train.py ${配置文件} --resume --cfg-options load_from=${检查点} +``` + +**在 CPU 上训练**: 如果机器没有 GPU,则在 CPU 上训练的过程是与单GPU训练一致的。如果机器有 GPU 但是不希望使用它们,我们只需要在训练前通过以下方式关闭 GPU 训练功能。 + +```shell +export CUDA_VISIBLE_DEVICES=-1 +``` + +然后运行[上方](###在单GPU上训练)脚本。 + +### 在单GPU上测试 + +`tools/test.py` 文件提供了在单 GPU 上启动测试任务的方法。 + +基础用法如下: + +```shell +python tools/test.py ${配置文件} ${模型权重文件} [可选参数] +``` + +这个工具有几个可选参数,包括: + +- `--work-dir`: 如果指定了路径,结果会保存在该路径下。如果没有指定则会保存在 `work_dirs/{配置文件名}` 路径下. +- `--show`: 当 `--show-dir` 没有指定时,可以使用该参数,在程序运行过程中显示预测结果。 +- `--show-dir`: 绘制了分割掩膜图片的存储文件夹。如果指定了该参数,则可视化的分割掩膜将被保存到 `work_dir/timestamp/{指定路径}`. +- `--wait-time`: 多次可视化结果的时间间隔。当 `--show` 为激活状态时发挥作用。默认为2。 +- `--cfg-options`: 如果被具体指定,以 xxx=yyy 形式的键值对将被合并入配置文件中。 + +**在CPU上测试**: 如果机器没有GPU,则在CPU上训练的过程是与单GPU训练一致的。如果机器有GPU,但是不希望使用它们,我们只需要在训练前通过以下方式关闭GPUs训练功能。 + +```shell +export CUDA_VISIBLE_DEVICES=-1 +``` + +然后运行[上方](###在单GPU上测试)脚本。 + +## 多GPU、多机器上训练和测试 + +### 在多GPU上训练 + +OpenMMLab2.0 通过 `MMDistributedDataParallel`实现 **分布式** 训练。 + +`tools/dist_train.sh` 文件提供了在在多GPU上部署训练任务的方法。 + +基础用法如下: + +```shell +sh tools/dist_train.sh ${配置文件} ${GPU数量} [可选参数] +``` + +可选参数与[上方](###在单GPU上训练)相同并且还增加了可以指定gpu数量的参数。 + +示例: + +```shell +# 模型训练的检查点和日志保存在这个路径下: WORK_DIR=work_dirs/pspnet_r50-d8_4xb4-80k_ade20k-512x512/ +# 如果工作路径没有被设定,它将会被自动生成。 +sh tools/dist_train.sh configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py 8 --work-dir work_dirs/pspnet_r50-d8_4xb4-80k_ade20k-512x512 +``` + +**注意**: 在训练过程中,检查点和日志保存在`work_dirs/`下的配置文件的相同文件夹结构下。 +不推荐自定义的工作路径,因为评估脚本依赖于源自配置文件名的路径。如果您希望将权重保存在其他地方,请用符号链接,例如: + +```shell +ln -s ${您的工作路径} ${MMSEG 路径}/work_dirs +``` + +### 在多GPU上测试 + +`tools/dist_test.sh` 文件提供了在多GPU上启动测试任务的方法。 + +基础用法如下: + +```shell +sh tools/dist_test.sh ${配置文件} ${检查点文件} ${GPU数量} [可选参数] +``` + +可选参数与[上方](###在单GPU上测试)相同并且增加了可以指定 gpu 数量的参数。 + +示例: + +```shell +./tools/dist_test.sh configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py \ + checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth 4 +``` + +### 在单台机器上启动多个任务 + +如果您在单个机器上运行多个任务,比如:在8卡GPU的单个机器上执行2个各需4卡GPU的训练任务,您需要为每个任务具体指定不同端口(默认29500),从而避免通讯冲突。否则,会有报错信息——`RuntimeError: Address already in use`(运行错误:地址被使用)。 + +如果您使用 `dist_train.sh` 来启动训练任务,您可以通过环境变量 `PORT` 设置端口。 + +```shell +CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 sh tools/dist_train.sh ${配置文件} 4 +CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 sh tools/dist_train.sh ${配置文件} 4 +``` + +### 在多台机器上训练 + +MMSegmentation 的分布式训练依赖 `torch.distributed`。 +因此, 可以通过 PyTorch 的 [运行工具 launch utility](https://pytorch.org/docs/stable/distributed.html#launch-utility) 来进行分布式训练。 + +如果您启动的多台机器简单地通过以太网连接,您可以直接运行下方命令: + +在第一个机器上: + +```shell +NNODES=2 NODE_RANK=0 PORT=${主节点端口} MASTER_ADDR=${主节点地址} sh tools/dist_train.sh ${配置文件} ${GPUS} +``` + +在第二个机器上: + +```shell +NNODES=2 NODE_RANK=1 PORT=${主节点端口} MASTER_ADDR=${主节点地址} sh tools/dist_train.sh ${配置文件} ${GPUS} +``` + +通常,如果您没有使用像无限带宽一类的高速网络,这个会过程比较慢。 + +## 通过 Slurm 管理任务 + +[Slurm](https://slurm.schedmd.com/) 是一个很好的计算集群作业调度系统。 + +### 通过 Slurm 在集群上训练 + +在一个由Slurm管理的集群上,您可以使用`slurm_train.sh`来启动训练任务。它同时支持单节点和多节点的训练。 + +基础用法如下: + +```shell +[GPUS=${GPUS}] sh tools/slurm_train.sh ${分区} ${任务名} ${配置文件} [可选参数] +``` + +下方是一个通过名为 `dev` 的 Slurm 分区,调用4个 GPU 来训练 PSPNet,并设置工作路径为共享文件系统。 + +```shell +GPUS=4 sh tools/slurm_train.sh dev pspnet configs/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes.py --work-dir work_dir/pspnet +``` + +您可以检查 [源码](../../../tools/slurm_train.sh) 来查看全部的参数和环境变量。 + +### 通过 Slurm 在集群上测试 + +与训练任务相同, MMSegmentation 提供 `slurm_test.sh` 文件来启动测试任务。 + +基础用法如下: + +```shell +[GPUS=${GPUS}] sh tools/slurm_test.sh ${分区} ${任务名} ${配置文件} ${检查点文件} [可选参数] +``` + +您可以通过 [源码](../../../tools/slurm_test.sh) 来查看全部的参数和环境变量。 + +**注意:** 使用 Slurm 时,需要设置端口,可从以下方式中选取一种。 + +1. 我们更推荐的通过`--cfg-options`设置端口,因为这不会改变原始配置: + + ```shell + GPUS=4 GPUS_PER_NODE=4 sh tools/slurm_train.sh ${分区} ${任务名} config1.py ${工作路径} --cfg-options env_cfg.dist_cfg.port=29500 + GPUS=4 GPUS_PER_NODE=4 sh tools/slurm_train.sh ${任务名} ${工作路径} config2.py ${工作路径} --cfg-options env_cfg.dist_cfg.port=29501 + ``` + +2. 通过修改配置文件设置不同的通讯端口: + + 在 `config1.py`中: + + ```python + enf_cfg = dict(dist_cfg=dict(backend='nccl', port=29500)) + ``` + + 在 `config2.py`中: + + ```python + enf_cfg = dict(dist_cfg=dict(backend='nccl', port=29501)) + ``` + + 然后您可以通过 config1.py 和 config2.py 同时启动两个任务: + + ```shell + CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 sh tools/slurm_train.sh ${分区} ${任务名} config1.py ${工作路径} + CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 sh tools/slurm_train.sh ${分区} ${任务名} config2.py ${工作路径} + ``` + +3. 在命令行中通过环境变量 `MASTER_PORT` 设置端口 : + +```shell +CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 MASTER_PORT=29500 sh tools/slurm_train.sh ${分区} ${任务名} config1.py ${工作路径} +CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 MASTER_PORT=29501 sh tools/slurm_train.sh ${分区} ${任务名} config2.py ${工作路径} +``` + +## 测试并保存分割结果 + +### 基础使用 + +当需要保存测试输出的分割结果,用 `--out` 指定分割结果输出路径 + +```shell +python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} --out ${OUTPUT_DIR} +``` + +以保存模型 `fcn_r50-d8_4xb4-80k_ade20k-512x512` 在 ADE20K 验证数据集上的结果为例: + +```shell +python tools/test.py configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py ckpt/fcn_r50-d8_512x512_80k_ade20k_20200614_144016-f8ac5082.pth --out work_dirs/format_results +``` + +或者通过配置文件定义 `output_dir`。例如在 `configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py` 添加 `test_evaluator` 定义: + +```python +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU'], output_dir='work_dirs/format_results') +``` + +然后执行相同功能的命令不需要再使用 `--out`: + +```shell +python tools/test.py configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py ckpt/fcn_r50-d8_512x512_80k_ade20k_20200614_144016-f8ac5082.pth +``` + +当测试的数据集没有提供标注,评测时没有真值可以参与计算,因此需要设置 `format_only=True`, +同时需要修改 `test_dataloader`,由于没有标注,我们需要在数据增强变换中删掉 `dict(type='LoadAnnotations')`,以下是一个配置示例: + +```python +test_evaluator = dict( + type='IoUMetric', + iou_metrics=['mIoU'], + format_only=True, + output_dir='work_dirs/format_results') +test_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type = 'ADE20KDataset' + data_root='data/ade/release_test', + data_prefix=dict(img_path='testing'), + # 测试数据变换中没有加载标注 + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + dict(type='PackSegInputs') + ])) +``` + +然后执行测试命令: + +```shell +python tools/test.py configs/fcn/fcn_r50-d8_4xb4-80k_ade20k-512x512.py ckpt/fcn_r50-d8_512x512_80k_ade20k_20200614_144016-f8ac5082.pth +``` + +### 测试 Cityscapes 数据集并保存输出分割结果 + +推荐使用 `CityscapesMetric` 来保存模型在 Cityscapes 数据集上的测试结果,以下是一个配置示例: + +```python +test_evaluator = dict( + type='CityscapesMetric', + format_only=True, + keep_results=True, + output_dir='work_dirs/format_results') +test_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='CityscapesDataset', + data_root='data/cityscapes/', + data_prefix=dict(img_path='leftImg8bit/test'), + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + dict(type='PackSegInputs') + ])) +``` + +然后执行相同的命令,例如: + +```shell +python tools/test.py configs/fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py ckpt/fcn_r18-d8_512x1024_80k_cityscapes_20201225_021327-6c50f8b4.pth +``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/5_deployment.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/5_deployment.md new file mode 100644 index 0000000..b2bec02 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/5_deployment.md @@ -0,0 +1,243 @@ +# 教程5:模型部署 + +# MMSegmentation 模型部署 + +- [教程5:模型部署](#教程5模型部署) +- [MMSegmentation 模型部署](#mmsegmentation-模型部署) + - [安装](#安装) + - [安装 mmseg](#安装-mmseg) + - [安装 mmdeploy](#安装-mmdeploy) + - [模型转换](#模型转换) + - [模型规范](#模型规范) + - [模型推理](#模型推理) + - [后端模型推理](#后端模型推理) + - [SDK 模型推理](#sdk-模型推理) + - [模型支持列表](#模型支持列表) + - [注意事项](#注意事项) + +______________________________________________________________________ + +[MMSegmentation](https://github.com/open-mmlab/mmsegmentation/tree/main) 又称`mmseg`,是一个基于 PyTorch 的开源对象分割工具箱。它是 [OpenMMLab](https://openmmlab.com/) 项目的一部分。 + +## 安装 + +### 安装 mmseg + +请参考[官网安装指南](https://mmsegmentation.readthedocs.io/en/latest/get_started.html)。 + +### 安装 mmdeploy + +mmdeploy 有以下几种安装方式: + +**方式一:** 安装预编译包 + +请参考[安装概述](https://mmdeploy.readthedocs.io/zh_CN/latest/get_started.html#mmdeploy) + +**方式二:** 一键式脚本安装 + +如果部署平台是 **Ubuntu 18.04 及以上版本**, 请参考[脚本安装说明](../01-how-to-build/build_from_script.md),完成安装过程。 +比如,以下命令可以安装 mmdeploy 以及配套的推理引擎——`ONNX Runtime`. + +```shell +git clone --recursive -b main https://github.com/open-mmlab/mmdeploy.git +cd mmdeploy +python3 tools/scripts/build_ubuntu_x64_ort.py $(nproc) +export PYTHONPATH=$(pwd)/build/lib:$PYTHONPATH +export LD_LIBRARY_PATH=$(pwd)/../mmdeploy-dep/onnxruntime-linux-x64-1.8.1/lib/:$LD_LIBRARY_PATH +``` + +**说明**: + +- 把 `$(pwd)/build/lib` 添加到 `PYTHONPATH`,目的是为了加载 mmdeploy SDK python 包 `mmdeploy_runtime`,在章节 [SDK模型推理](#sdk模型推理)中讲述其用法。 +- 在[使用 ONNX Runtime推理后端模型](#后端模型推理)时,需要加载自定义算子库,需要把 ONNX Runtime 库的路径加入环境变量 `LD_LIBRARY_PATH`中。 + +**方式三:** 源码安装 + +在方式一、二都满足不了的情况下,请参考[源码安装说明](../01-how-to-build/build_from_source.md) 安装 mmdeploy 以及所需推理引擎。 + +## 模型转换 + +你可以使用 [tools/deploy.py](https://github.com/open-mmlab/mmdeploy/tree/main/tools/deploy.py) 把 mmseg 模型一键式转换为推理后端模型。 +该工具的详细使用说明请参考[这里](https://github.com/open-mmlab/mmdeploy/tree/main/docs/en/02-how-to-run/convert_model.md#usage). + +以下,我们将演示如何把 `unet` 转换为 onnx 模型。 + +```shell +cd mmdeploy + +# download unet model from mmseg model zoo +mim download mmsegmentation --config unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024 --dest . + +# convert mmseg model to onnxruntime model with dynamic shape +python tools/deploy.py \ + configs/mmseg/segmentation_onnxruntime_dynamic.py \ + unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py \ + fcn_unet_s5-d16_4x4_512x1024_160k_cityscapes_20211210_145204-6860854e.pth \ + demo/resources/cityscapes.png \ + --work-dir mmdeploy_models/mmseg/ort \ + --device cpu \ + --show \ + --dump-info +``` + +转换的关键之一是使用正确的配置文件。项目中已内置了各后端部署[配置文件](https://github.com/open-mmlab/mmdeploy/tree/main/configs/mmseg)。 +文件的命名模式是: + +``` +segmentation_{backend}-{precision}_{static | dynamic}_{shape}.py +``` + +其中: + +- **{backend}:** 推理后端名称。比如,onnxruntime、tensorrt、pplnn、ncnn、openvino、coreml 等等 +- **{precision}:** 推理精度。比如,fp16、int8。不填表示 fp32 +- **{static | dynamic}:** 动态、静态 shape +- **{shape}:** 模型输入的 shape 或者 shape 范围 + +在上例中,你也可以把 `unet` 转为其他后端模型。比如使用`segmentation_tensorrt-fp16_dynamic-512x1024-2048x2048.py`,把模型转为 tensorrt-fp16 模型。 + +```{tip} +当转 tensorrt 模型时, --device 需要被设置为 "cuda" +``` + +## 模型规范 + +在使用转换后的模型进行推理之前,有必要了解转换结果的结构。 它存放在 `--work-dir` 指定的路路径下。 + +上例中的`mmdeploy_models/mmseg/ort`,结构如下: + +``` +mmdeploy_models/mmseg/ort +├── deploy.json +├── detail.json +├── end2end.onnx +└── pipeline.json +``` + +重要的是: + +- **end2end.onnx**: 推理引擎文件。可用 ONNX Runtime 推理 +- \***.json**: mmdeploy SDK 推理所需的 meta 信息 + +整个文件夹被定义为**mmdeploy SDK model**。换言之,**mmdeploy SDK model**既包括推理引擎,也包括推理 meta 信息。 + +## 模型推理 + +### 后端模型推理 + +以上述模型转换后的 `end2end.onnx` 为例,你可以使用如下代码进行推理: + +```python +from mmdeploy.apis.utils import build_task_processor +from mmdeploy.utils import get_input_shape, load_config +import torch + +deploy_cfg = 'configs/mmseg/segmentation_onnxruntime_dynamic.py' +model_cfg = './unet-s5-d16_fcn_4xb4-160k_cityscapes-512x1024.py' +device = 'cpu' +backend_model = ['./mmdeploy_models/mmseg/ort/end2end.onnx'] +image = './demo/resources/cityscapes.png' + +# read deploy_cfg and model_cfg +deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg) + +# build task and backend model +task_processor = build_task_processor(model_cfg, deploy_cfg, device) +model = task_processor.build_backend_model(backend_model) + +# process input image +input_shape = get_input_shape(deploy_cfg) +model_inputs, _ = task_processor.create_input(image, input_shape) + +# do model inference +with torch.no_grad(): + result = model.test_step(model_inputs) + +# visualize results +task_processor.visualize( + image=image, + model=model, + result=result[0], + window_name='visualize', + output_file='./output_segmentation.png') +``` + +### SDK 模型推理 + +你也可以参考如下代码,对 SDK model 进行推理: + +```python +from mmdeploy_runtime import Segmentor +import cv2 +import numpy as np + +img = cv2.imread('./demo/resources/cityscapes.png') + +# create a classifier +segmentor = Segmentor(model_path='./mmdeploy_models/mmseg/ort', device_name='cpu', device_id=0) +# perform inference +seg = segmentor(img) + +# visualize inference result +## random a palette with size 256x3 +palette = np.random.randint(0, 256, size=(256, 3)) +color_seg = np.zeros((seg.shape[0], seg.shape[1], 3), dtype=np.uint8) +for label, color in enumerate(palette): + color_seg[seg == label, :] = color +# convert to BGR +color_seg = color_seg[..., ::-1] +img = img * 0.5 + color_seg * 0.5 +img = img.astype(np.uint8) +cv2.imwrite('output_segmentation.png', img) +``` + +除了python API,mmdeploy SDK 还提供了诸如 C、C++、C#、Java等多语言接口。 +你可以参考[样例](https://github.com/open-mmlab/mmdeploy/tree/main/demo)学习其他语言接口的使用方法。 + +## 模型支持列表 + +| Model | TorchScript | OnnxRuntime | TensorRT | ncnn | PPLNN | OpenVino | +| :-------------------------------------------------------------------------------------------------------- | :---------: | :---------: | :------: | :--: | :---: | :------: | +| [FCN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/fcn) | Y | Y | Y | Y | Y | Y | +| [PSPNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/pspnet)[\*](#static_shape) | Y | Y | Y | Y | Y | Y | +| [DeepLabV3](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/deeplabv3) | Y | Y | Y | Y | Y | Y | +| [DeepLabV3+](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/deeplabv3plus) | Y | Y | Y | Y | Y | Y | +| [Fast-SCNN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/fastscnn)[\*](#static_shape) | Y | Y | Y | N | Y | Y | +| [UNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/unet) | Y | Y | Y | Y | Y | Y | +| [ANN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/ann)[\*](#static_shape) | Y | Y | Y | N | N | N | +| [APCNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/apcnet) | Y | Y | Y | Y | N | N | +| [BiSeNetV1](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/bisenetv1) | Y | Y | Y | Y | N | Y | +| [BiSeNetV2](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/bisenetv2) | Y | Y | Y | Y | N | Y | +| [CGNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/cgnet) | Y | Y | Y | Y | N | Y | +| [DMNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/dmnet) | ? | Y | N | N | N | N | +| [DNLNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/dnlnet) | ? | Y | Y | Y | N | Y | +| [EMANet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/emanet) | Y | Y | Y | N | N | Y | +| [EncNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/encnet) | Y | Y | Y | N | N | Y | +| [ERFNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/erfnet) | Y | Y | Y | Y | N | Y | +| [FastFCN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/fastfcn) | Y | Y | Y | Y | N | Y | +| [GCNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/gcnet) | Y | Y | Y | N | N | N | +| [ICNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/icnet)[\*](#static_shape) | Y | Y | Y | N | N | Y | +| [ISANet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/isanet)[\*](#static_shape) | N | Y | Y | N | N | Y | +| [NonLocal Net](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/nonlocal_net) | ? | Y | Y | Y | N | Y | +| [OCRNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/ocrnet) | Y | Y | Y | Y | N | Y | +| [PointRend](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/point_rend)[\*](#static_shape) | Y | Y | Y | N | N | N | +| [Semantic FPN](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/sem_fpn) | Y | Y | Y | Y | N | Y | +| [STDC](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/stdc) | Y | Y | Y | Y | N | Y | +| [UPerNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/upernet)[\*](#static_shape) | N | Y | Y | N | N | N | +| [DANet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/danet) | ? | Y | Y | N | N | Y | +| [Segmenter](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/segmenter)[\*](#static_shape) | N | Y | Y | Y | N | Y | +| [SegFormer](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/segformer)[\*](#static_shape) | ? | Y | Y | N | N | Y | +| [SETR](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/setr) | ? | Y | N | N | N | Y | +| [CCNet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/ccnet) | ? | N | N | N | N | N | +| [PSANet](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/psanet) | ? | N | N | N | N | N | +| [DPT](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/dpt) | ? | N | N | N | N | N | + +## 注意事项 + +- 所有 mmseg 模型仅支持 "whole" 推理模式。 + +- PSPNet,Fast-SCNN 仅支持静态输入,因为多数推理框架的 [nn.AdaptiveAvgPool2d](https://github.com/open-mmlab/mmsegmentation/blob/0c87f7a0c9099844eff8e90fa3db5b0d0ca02fee/mmseg/models/decode_heads/psp_head.py#L38) 不支持动态输入。 + +- 对于仅支持静态形状的模型,应使用静态形状的部署配置文件,例如 `configs/mmseg/segmentation_tensorrt_static-1024x2048.py` + +- 对于喜欢部署模型生成概率特征图的用户,将 `codebase_config = dict(with_argmax=False)` 放在部署配置中就足够了。 diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/deploy_jetson.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/deploy_jetson.md new file mode 100644 index 0000000..6cebd9c --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/deploy_jetson.md @@ -0,0 +1,372 @@ +# 将 MMSeg 模型调优及部署到 NVIDIA Jetson 平台教程 + +- 请先查阅[MMSegmentation 模型部署](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/user_guides/5_deployment.md)文档。 +- **本教程所用 mmsegmentation 版本: v1.1.2** +- **本教程所用 NVIDIA Jetson 设备: NVIDIA Jetson AGX Orin 64G** + +
+ Smiley face +
+ +## 1 配置 [mmsegmentation](https://github.com/open-mmlab/mmsegmentation) + +- 根据[安装和验证](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/get_started.md)文档,完成开发 [mmsegmentation](https://github.com/open-mmlab/mmsegmentation) 所需的 [`pytorch`](https://pytorch.org/get-started/locally/)、[`mmcv`](https://github.com/open-mmlab/mmcv)、[`mmengine`](https://github.com/open-mmlab/mmengine) 等环境依赖安装。 +- 从 GitHub 使用 git clone 命令完成 [mmsegmentation](https://github.com/open-mmlab/mmsegmentation) 下载。网络不好的同学,可通过 [MMSeg GitHub](https://github.com/open-mmlab/mmsegmentation) 页面进行 zip 的下载。 + ```bash + git clone https://github.com/open-mmlab/mmsegmentation.git + ``` +- 使用 `pip install -v -e.` 命令动态安装 mmsegmentation 。 + ```bash + cd mmsegmentation + pip install -v -e . + ``` + 提示成功安装后,可通过 `pip list` 命令查看到 mmsegmentation 已通过本地安装方式安装到了您的环境中。 + ![mmseg-install](https://github.com/AI-Tianlong/Useful-Tools/assets/50650583/a9c7bcc9-cdcc-40a4-bd7b-8153195549c8) + +## 2 准备您的数据集 + +- 本教程使用遥感图像语义分割数据集 [potsdam](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/user_guides/2_dataset_prepare.md#isprs-potsdam) 作为示例。 +- 根据 [potsdam 数据准备](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/user_guides/2_dataset_prepare.md#isprs-potsdam)文档,进行数据集下载及 MMSeg 格式的准备。 +- 数据集介绍: potsdam 数据集是以德国一个典型的历史城市 Potsdam 命名的,该城市有着大建筑群、狭窄的街道和密集的建筑结构。 potsdam 数据集包含 38 幅 6000x6000 像素的图像,空间分辨率为 5cm,数据集的示例如下图: + ![potsdam-img](https://github.com/AI-Tianlong/Useful-Tools/assets/50650583/3bc0a75b-1693-4ae6-aeea-ad502e955068) + +## 3 从 config 页面下载模型的 pth 权重文件 + +这里以 [`deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py`](../../configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py) 配置文件举例,在 [configs](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/deeplabv3plus#potsdam) 页面下载权重文件, +![pth](https://github.com/AI-Tianlong/Useful-Tools/assets/50650583/8f747362-caf4-406c-808d-4ca72babb209) + +## 4 通过 [OpenMMLab deployee](https://platform.openmmlab.com/deploee) 以交互式方式进行模型转换及测速 + +### 4.1 模型转换 + +在该部分中,[OpenMMLab 官网](https://platform.openmmlab.com/deploee)提供了模型转换及模型测速的交互界面,无需任何代码,即可通过选择对应选项完成模型 ONNX 格式`xxxx.onnx` 和 TensorRT `.engine`格式的转换。 +如您的自定义 config 文件中有相对引用关系,如: + +```python +# xxxx.py +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/potsdam.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +``` + +您可以使用以下代码消除相对引用关系,以生成完整的 config 文件。 + +```python +import mmengine + +mmengine.Config.fromfile("configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py").dump("My_config.py") +``` + +使用上述代码后,您能够看到,在`My_config.py`包含着完整的配置文件,无相对引用。这时,上传模型 config 至网页内对应处。 + +#### 创建转换任务 + +按照下图提示及自己的需求,创建转换任务并提交。 + +
+ NVIDIA-Jetson +
+ +### 4.2 模型测速 + +在完成模型转换后可通过**模型测速**界面,完成在真实设备上的模型测速。 + +#### 创建测速任务 + +
+ NVIDIA-Jetson +
+ +
+ NVIDIA-Jetson +
+ +测速完成后,可在页面生成完整的测速报告。[查看测速报告示例](https://openmmlab-deploee.oss-cn-shanghai.aliyuncs.com/tmp/profile_speed/4352f5.txt) + +## 5 通过 OpenMMLab mmdeploy 以命令行将模型转换为ONNX格式 + +该部分可以通过 mmdeploy 库对 mmseg 训练好的模型进行推理格式的转换。这里给出一个示例,具体文档可见[ mmdeploy 模型转换文档](../../docs/zh_cn/user_guides/5_deployment.md)。 + +### 5.1 通过源码构建 mmdeploy 库 + +在您安装 mmsegmentation 库的虚拟环境下,通过 `git clone`命令从 GitHub 克隆 [mmdeploy](https://github.com/open-mmlab/mmdeploy) + +### 5.2 模型转换 + +如您的 config 中含有相对引用,仍需进行消除,如[4.1 模型转换](#4.1-模型转换)所述, +进入 mmdeploy 文件夹,执行以下命令,即可完成模型转换。 + +```bash +python tools/deploy.py \ + configs/mmseg/segmentation_onnxruntime_static-512x512.py \ + ../atl_config.py \ + ../deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601-75fd5bc3.pth \ + ../2_13_1024_5488_1536_6000.png \ + --work-dir ../atl_models \ + --device cpu \ + --show \ + --dump-info +``` + +```bash +# 使用方法 +python ./tools/deploy.py \ + ${部署配置文件路径} \ + ${模型配置文件路径} \ + ${模型权重路径} \ + ${输入图像路径} \ + --work-dir ${用来保存日志和模型文件路径} \ + --device ${cpu/cuda:0} \ + --show \ # 是否显示检测的结果 + --dump-info # 是否输出 SDK 信息 + +``` + +执行成功后,您将能够看到以下提示,即为转换成功。 + +```bash +10/08 17:40:44 - mmengine - INFO - visualize pytorch model success. +10/08 17:40:44 - mmengine - INFO - All process success. +``` + +
+ NVIDIA-Jetson +
+ +# 6 在 Jetson 平台进行转换及部署 + +## 6.1 环境准备 + +参考[如何在 Jetson 模组上安装 MMDeploy](https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/01-how-to-build/jetsons.md)文档,完成在 Jetson 上的环境准备工作。 +**注**:安装 Pytorch,可查阅 [NVIDIA Jetson Pytorch 安装文档](https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/01-how-to-build/jetsons.md)安装最新的 Pytorch。 + +### 6.1.1 创建虚拟环境 + +```bash +conda create -n {您虚拟环境的名字} python={python版本} +``` + +### 6.1.2 虚拟环境内安装Pytorch + +注意:这里不要安装最新的 pytorch 2.0,因为 pyTorch 1.11 是最后一个使用 USE_DISTRIBUTED 构建的wheel,否则会在用mmdeploy进行模型转换的时候提示`AttributeError: module 'torch.distributed' has no attribute 'ReduceOp'`的错误。参考以下链接:https://forums.developer.nvidia.com/t/module-torch-distributed-has-no-attribute-reduceop/256581/6 +下载`torch-1.11.0-cp38-cp38-linux_aarch64.whl`并安装 + +```bash +pip install torch-1.11.0-cp38-cp38-linux_aarch64.whl +``` + +执行以上命令后,您将能看到以下提示,即为安装成功。 + +```bash +Processing ./torch-1.11.0-cp38-cp38-linux_aarch64.whl +Requirement already satisfied: typing-extensions in /home/sirs/miniconda3/envs/openmmlab/lib/python3.8/site-packages (from torch==1.11.0) (4.7.1) +Installing collected packages: torch +Successfully installed torch-1.11.0 +``` + +### 6.1.3 将 Jetson Pack 自带的 tensorrt 拷贝至虚拟环境下 + +请参考[配置 TensorRT](https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/01-how-to-build/jetsons.md#%E9%85%8D%E7%BD%AE-tensorrt)。 +JetPack SDK 自带 TensorRT。 但是为了能够在 Conda 环境中成功导入,我们需要将 TensorRT 拷贝进先前创建的 Conda 环境中。 + +```bash +export PYTHON_VERSION=`python3 --version | cut -d' ' -f 2 | cut -d'.' -f1,2` +cp -r /usr/lib/python${PYTHON_VERSION}/dist-packages/tensorrt* ~/miniconda/envs/{您的虚拟环境名字}/lib/python${PYTHON_VERSION}/site-packages/ +``` + +### 6.1.4 安装 MMCV + +通过`mim install mmcv`或从源码对其进行编译。 + +```bash +pip install openmim +mim install mmcv +``` + +或者从源码对其进行编译。 + +```bash +sudo apt-get install -y libssl-dev +git clone https://github.com/open-mmlab/mmcv.git +cd mmcv +pip install -e . +``` + +注:pytorch版本发生变动后,需要重新编译mmcv。 + +### 6.1.5 安装 ONNX + +注:以下方式二选一 + +- conda + ```bash + conda install -c conda-forge onnx + ``` +- pip + ```bash + python3 -m pip install onnx + ``` + +### 6.1.6 安装 ONNX Runtime + +根据网页 [ONNX Runtime](https://elinux.org/Jetson_Zoo#ONNX_Runtime) 选择合适的ONNX Runtime版本进行下载安装。 +示例: + +```bash +# Install pip wheel +$ pip3 install onnxruntime_gpu-1.10.0-cp38-cp38-linux_aarch64.whl + +``` + +## 6.2 在 Jetson AGX Orin 进行模型转换及推理 + +### 6.2.1 ONNX 模型转换 + +同[4.1 模型转换](#4.1-模型转换)相同,在 Jetson 平台下进入安装好的虚拟环境,以及mmdeploy 目录,进行模型ONNX转换。 + +```bash +python tools/deploy.py \ + configs/mmseg/segmentation_onnxruntime_static-512x512.py \ + ../atl_config.py \ + ../deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601-75fd5bc3.pth \ + ../2_13_3584_2560_4096_3072.png \ + --work-dir ../atl_models \ + --device cpu \ + --show \ + --dump-info + +``` + +注: 如果报错提示内容: + +```none +AttributeError: module 'torch.distributed' has no attribute 'ReduceOp' +``` + +可参考以下链接进行解决:https://forums.developer.nvidia.com/t/module-torch-distributed-has-no-attribute-reduceop/256581/6,即安装 pytorch 1.11.0 版本。 + +转换成功后,您将会看到如下信息以及包含 ONNX 模型的文件夹: + +```bash +10/09 19:58:22 - mmengine - INFO - visualize pytorch model success. +10/09 19:58:22 - mmengine - INFO - All process success. +``` + +
+ NVIDIA-Jetson + NVIDIA-Jetson +
+ +### 6.2.2 TensorRT 模型转换 + +更换部署trt配置文件,进行 TensorRT 模型转换。 + +```bash +python tools/deploy.py \ + configs/mmseg/segmentation_tensorrt_static-512x512.py \ + ../atl_config.py \ + ../deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601-75fd5bc3.pth \ + ../2_13_3584_2560_4096_3072.png \ + --work-dir ../atl_trt_models \ + --device cuda:0 \ + --show \ + --dump-info + +``` + +转换成功后您将看到以下信息及 TensorRT 模型文件夹: + +```bash +10/09 20:15:50 - mmengine - INFO - visualize pytorch model success. +10/09 20:15:50 - mmengine - INFO - All process success. +``` + +
+ NVIDIA-Jetson + NVIDIA-Jetson +
+ +## 6.3 模型测速 + +执行以下命令完成模型测速,详细内容请查看[ profiler ](https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/02-how-to-run/useful_tools.md#profiler) + +```bash +python tools/profiler.py \ + ${DEPLOY_CFG} \ + ${MODEL_CFG} \ + ${IMAGE_DIR} \ + --model ${MODEL} \ + --device ${DEVICE} \ + --shape ${SHAPE} \ + --num-iter ${NUM_ITER} \ + --warmup ${WARMUP} \ + --cfg-options ${CFG_OPTIONS} \ + --batch-size ${BATCH_SIZE} \ + --img-ext ${IMG_EXT} +``` + +示例: + +```bash +python tools/profiler.py \ + configs/mmseg/segmentation_tensorrt_static-512x512.py \ + ../atl_config.py \ + ../atl_demo_img \ + --model /home/sirs/AI-Tianlong/OpenMMLab/atl_trt_models/end2end.engine \ + --device cuda:0 \ + --shape 512x512 \ + --num-iter 100 +``` + +测速结果 + +![image](https://github.com/AI-Tianlong/Useful-Tools/assets/50650583/874e9742-ee10-490c-9e69-17da0096c49b) + +## 6.4 模型推理 + +根据[6.2.2](#6.2.2-TensorRT-模型转换)中生成的TensorRT模型文件夹,进行模型推理。 + +```python +from mmdeploy.apis.utils import build_task_processor +from mmdeploy.utils import get_input_shape, load_config +import torch + +deploy_cfg='./mmdeploy/configs/mmseg/segmentation_tensorrt_static-512x512.py' +model_cfg='./atl_config.py' +device='cuda:0' +backend_model = ['./atl_trt_models/end2end.engine'] +image = './atl_demo_img/2_13_2048_1024_2560_1536.png' + +# read deploy_cfg and model_cfg +deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg) + +# build task and backend model +task_processor = build_task_processor(model_cfg, deploy_cfg, device) +model = task_processor.build_backend_model(backend_model) + +# process input image +input_shape = get_input_shape(deploy_cfg) +model_inputs, _ = task_processor.create_input(image, input_shape) + +# do model inference +with torch.no_grad(): + result = model.test_step(model_inputs) + +# visualize results +task_processor.visualize( + image=image, + model=model, + result=result[0], + window_name='visualize', + output_file='./output_segmentation.png') +``` + +即可得到推理结果: + +
+ NVIDIA-Jetson + NVIDIA-Jetson +
diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/index.rst b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/index.rst new file mode 100644 index 0000000..d0a313d --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/index.rst @@ -0,0 +1,21 @@ +训练 & 测试 +************** + +.. toctree:: + :maxdepth: 1 + + 1_config.md + 2_dataset_prepare.md + 3_inference.md + 4_train_test.md + +实用工具 +************* + +.. toctree:: + :maxdepth: 2 + + visualization.md + useful_tools.md + deployment.md + visualization_feature_map.md diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/useful_tools.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/useful_tools.md new file mode 100644 index 0000000..acbacb9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/useful_tools.md @@ -0,0 +1,368 @@ +## 常用工具(待更新) + +除了训练和测试的脚本,我们在 `tools/` 文件夹路径下还提供许多有用的工具。 + +### 计算参数量(params)和计算量( FLOPs) (试验性) + +我们基于 [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) +提供了一个用于计算给定模型参数量和计算量的脚本。 + +```shell +python tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}] +``` + +您将得到如下的结果: + +```none +============================== +Input shape: (3, 2048, 1024) +Flops: 1429.68 GMac +Params: 48.98 M +============================== +``` + +**注意**: 这个工具仍然是试验性的,我们无法保证数字是正确的。您可以拿这些结果做简单的实验的对照,在写技术文档报告或者论文前您需要再次确认一下。 + +(1) 计算量与输入的形状有关,而参数量与输入的形状无关,默认的输入形状是 (1, 3, 1280, 800); +(2) 一些运算操作,如 GN 和其他定制的运算操作没有加入到计算量的计算中。 + +### 发布模型 + +在您上传一个模型到云服务器之前,您需要做以下几步: +(1) 将模型权重转成 CPU 张量; +(2) 删除记录优化器状态 (optimizer states)的相关信息; +(3) 计算检查点文件 (checkpoint file) 的哈希编码(hash id)并且将哈希编码加到文件名中。 + +```shell +python tools/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME} +``` + +例如, + +```shell +python tools/publish_model.py work_dirs/pspnet/latest.pth psp_r50_hszhao_200ep.pth +``` + +最终输出文件将是 `psp_r50_512x1024_40ki_cityscapes-{hash id}.pth`。 + +### 导出 ONNX (试验性) + +我们提供了一个脚本来导出模型到 [ONNX](https://github.com/onnx/onnx) 格式。被转换的模型可以通过工具 [Netron](https://github.com/lutzroeder/netron) +来可视化。除此以外,我们同样支持对 PyTorch 和 ONNX 模型的输出结果做对比。 + +```bash +python tools/pytorch2onnx.py \ + ${CONFIG_FILE} \ + --checkpoint ${CHECKPOINT_FILE} \ + --output-file ${ONNX_FILE} \ + --input-img ${INPUT_IMG} \ + --shape ${INPUT_SHAPE} \ + --rescale-shape ${RESCALE_SHAPE} \ + --show \ + --verify \ + --dynamic-export \ + --cfg-options \ + model.test_cfg.mode="whole" +``` + +各个参数的描述: + +- `config` : 模型配置文件的路径 +- `--checkpoint` : 模型检查点文件的路径 +- `--output-file`: 输出的 ONNX 模型的路径。如果没有专门指定,它默认是 `tmp.onnx` +- `--input-img` : 用来转换和可视化的一张输入图像的路径 +- `--shape`: 模型的输入张量的高和宽。如果没有专门指定,它将被设置成 `test_pipeline` 的 `img_scale` +- `--rescale-shape`: 改变输出的形状。设置这个值来避免 OOM,它仅在 `slide` 模式下可以用 +- `--show`: 是否打印输出模型的结构。如果没有被专门指定,它将被设置成 `False` +- `--verify`: 是否验证一个输出模型的正确性 (correctness)。如果没有被专门指定,它将被设置成 `False` +- `--dynamic-export`: 是否导出形状变化的输入与输出的 ONNX 模型。如果没有被专门指定,它将被设置成 `False` +- `--cfg-options`: 更新配置选项 + +**注意**: 这个工具仍然是试验性的,目前一些自定义操作还没有被支持 + +### 评估 ONNX 模型 + +我们提供 `tools/deploy_test.py` 去评估不同后端的 ONNX 模型。 + +#### 先决条件 + +- 安装 onnx 和 onnxruntime-gpu + + ```shell + pip install onnx onnxruntime-gpu + ``` + +- 参考 [如何在 MMCV 里构建 tensorrt 插件](https://mmcv.readthedocs.io/en/latest/tensorrt_plugin.html#how-to-build-tensorrt-plugins-in-mmcv) 安装TensorRT (可选) + +#### 使用方法 + +```bash +python tools/deploy_test.py \ + ${CONFIG_FILE} \ + ${MODEL_FILE} \ + ${BACKEND} \ + --out ${OUTPUT_FILE} \ + --eval ${EVALUATION_METRICS} \ + --show \ + --show-dir ${SHOW_DIRECTORY} \ + --cfg-options ${CFG_OPTIONS} \ + --eval-options ${EVALUATION_OPTIONS} \ + --opacity ${OPACITY} \ +``` + +各个参数的描述: + +- `config`: 模型配置文件的路径 +- `model`: 被转换的模型文件的路径 +- `backend`: 推理的后端,可选项:`onnxruntime`, `tensorrt` +- `--out`: 输出结果成 pickle 格式文件的路径 +- `--format-only` : 不评估直接给输出结果的格式。通常用在当您想把结果输出成一些测试服务器需要的特定格式时。如果没有被专门指定,它将被设置成 `False`。 注意这个参数是用 `--eval` 来 **手动添加** +- `--eval`: 评估指标,取决于每个数据集的要求,例如 "mIoU" 是大多数据集的指标而 "cityscapes" 仅针对 Cityscapes 数据集。注意这个参数是用 `--format-only` 来 **手动添加** +- `--show`: 是否展示结果 +- `--show-dir`: 涂上结果的图像被保存的文件夹的路径 +- `--cfg-options`: 重写配置文件里的一些设置,`xxx=yyy` 格式的键值对将被覆盖到配置文件里 +- `--eval-options`: 自定义的评估的选项, `xxx=yyy` 格式的键值对将成为 `dataset.evaluate()` 函数的参数变量 +- `--opacity`: 涂上结果的分割图的透明度,范围在 (0, 1\] 之间 + +#### 结果和模型 + +| 模型 | 配置文件 | 数据集 | 评价指标 | PyTorch | ONNXRuntime | TensorRT-fp32 | TensorRT-fp16 | +| :--------: | :---------------------------------------------: | :--------: | :------: | :-----: | :---------: | :-----------: | :-----------: | +| FCN | fcn_r50-d8_512x1024_40k_cityscapes.py | cityscapes | mIoU | 72.2 | 72.2 | 72.2 | 72.2 | +| PSPNet | pspnet_r50-d8_512x1024_40k_cityscapes.py | cityscapes | mIoU | 77.8 | 77.8 | 77.8 | 77.8 | +| deeplabv3 | deeplabv3_r50-d8_512x1024_40k_cityscapes.py | cityscapes | mIoU | 79.0 | 79.0 | 79.0 | 79.0 | +| deeplabv3+ | deeplabv3plus_r50-d8_512x1024_40k_cityscapes.py | cityscapes | mIoU | 79.6 | 79.5 | 79.5 | 79.5 | +| PSPNet | pspnet_r50-d8_769x769_40k_cityscapes.py | cityscapes | mIoU | 78.2 | 78.1 | | | +| deeplabv3 | deeplabv3_r50-d8_769x769_40k_cityscapes.py | cityscapes | mIoU | 78.5 | 78.3 | | | +| deeplabv3+ | deeplabv3plus_r50-d8_769x769_40k_cityscapes.py | cityscapes | mIoU | 78.9 | 78.7 | | | + +**注意**: TensorRT 仅在使用 `whole mode` 测试模式时的配置文件里可用。 + +### 导出 TorchScript (试验性) + +我们同样提供一个脚本去把模型导出成 [TorchScript](https://pytorch.org/docs/stable/jit.html) 格式。您可以使用 pytorch C++ API [LibTorch](https://pytorch.org/docs/stable/cpp_index.html) 去推理训练好的模型。 +被转换的模型能被像 [Netron](https://github.com/lutzroeder/netron) 的工具来可视化。此外,我们还支持 PyTorch 和 TorchScript 模型的输出结果的比较。 + +```shell +python tools/pytorch2torchscript.py \ + ${CONFIG_FILE} \ + --checkpoint ${CHECKPOINT_FILE} \ + --output-file ${ONNX_FILE} + --shape ${INPUT_SHAPE} + --verify \ + --show +``` + +各个参数的描述: + +- `config` : pytorch 模型的配置文件的路径 +- `--checkpoint` : pytorch 模型的检查点文件的路径 +- `--output-file`: TorchScript 模型输出的路径,如果没有被专门指定,它将被设置成 `tmp.pt` +- `--input-img` : 用来转换和可视化的输入图像的路径 +- `--shape`: 模型的输入张量的宽和高。如果没有被专门指定,它将被设置成 `512 512` +- `--show`: 是否打印输出模型的追踪图 (traced graph),如果没有被专门指定,它将被设置成 `False` +- `--verify`: 是否验证一个输出模型的正确性 (correctness),如果没有被专门指定,它将被设置成 `False` + +**注意**: 目前仅支持 PyTorch>=1.8.0 版本 + +**注意**: 这个工具仍然是试验性的,一些自定义操作符目前还不被支持 + +例子: + +- 导出 PSPNet 在 cityscapes 数据集上的 pytorch 模型 + + ```shell + python tools/pytorch2torchscript.py configs/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes.py \ + --checkpoint checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth \ + --output-file checkpoints/pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pt \ + --shape 512 1024 + ``` + +### 导出 TensorRT (试验性) + +一个导出 [ONNX](https://github.com/onnx/onnx) 模型成 [TensorRT](https://developer.nvidia.com/tensorrt) 格式的脚本 + +先决条件 + +- 按照 [ONNXRuntime in mmcv](https://mmcv.readthedocs.io/en/latest/deployment/onnxruntime_op.html) 和 [TensorRT plugin in mmcv](https://github.com/open-mmlab/mmcv/blob/master/docs/en/deployment/tensorrt_plugin.md) ,用 ONNXRuntime 自定义运算 (custom ops) 和 TensorRT 插件安装 `mmcv-full` +- 使用 [pytorch2onnx](#convert-to-onnx-experimental) 将模型从 PyTorch 转成 ONNX + +使用方法 + +```bash +python ${MMSEG_PATH}/tools/onnx2tensorrt.py \ + ${CFG_PATH} \ + ${ONNX_PATH} \ + --trt-file ${OUTPUT_TRT_PATH} \ + --min-shape ${MIN_SHAPE} \ + --max-shape ${MAX_SHAPE} \ + --input-img ${INPUT_IMG} \ + --show \ + --verify +``` + +各个参数的描述: + +- `config` : 模型的配置文件 +- `model` : 输入的 ONNX 模型的路径 +- `--trt-file` : 输出的 TensorRT 引擎的路径 +- `--max-shape` : 模型的输入的最大形状 +- `--min-shape` : 模型的输入的最小形状 +- `--fp16` : 做 fp16 模型转换 +- `--workspace-size` : 在 GiB 里的最大工作空间大小 (Max workspace size) +- `--input-img` : 用来可视化的图像 +- `--show` : 做结果的可视化 +- `--dataset` : Palette provider, 默认为 `CityscapesDataset` +- `--verify` : 验证 ONNXRuntime 和 TensorRT 的输出 +- `--verbose` : 当创建 TensorRT 引擎时,是否详细做信息日志。默认为 False + +**注意**: 仅在全图测试模式 (whole mode) 下测试过 + +## 其他内容 + +### 打印完整的配置文件 + +`tools/print_config.py` 会逐字逐句的打印整个配置文件,展开所有的导入。 + +```shell +python tools/print_config.py \ + ${CONFIG} \ + --graph \ + --cfg-options ${OPTIONS [OPTIONS...]} \ +``` + +各个参数的描述: + +- `config` : pytorch 模型的配置文件的路径 +- `--graph` : 是否打印模型的图 (models graph) +- `--cfg-options`: 自定义替换配置文件的选项 + +### 对训练日志 (training logs) 画图 + +`tools/analyze_logs.py` 会画出给定的训练日志文件的 loss/mIoU 曲线,首先需要 `pip install seaborn` 安装依赖包。 + +```shell +python tools/analyze_logs.py xxx.log.json [--keys ${KEYS}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}] +``` + +示例: + +- 对 mIoU, mAcc, aAcc 指标画图 + + ```shell + python tools/analyze_logs.py log.json --keys mIoU mAcc aAcc --legend mIoU mAcc aAcc + ``` + +- 对 loss 指标画图 + + ```shell + python tools/analyze_logs.py log.json --keys loss --legend loss + ``` + +### 转换其他仓库的权重 + +`tools/model_converters/` 提供了若干个预训练权重转换脚本,支持将其他仓库的预训练权重的 key 转换为与 MMSegmentation 相匹配的 key。 + +#### ViT Swin MiT Transformer 模型 + +- ViT + +`tools/model_converters/vit2mmseg.py` 将 timm 预训练模型转换到 MMSegmentation。 + +```shell +python tools/model_converters/vit2mmseg.py ${SRC} ${DST} +``` + +- Swin + + `tools/model_converters/swin2mmseg.py` 将官方预训练模型转换到 MMSegmentation。 + + ```shell + python tools/model_converters/swin2mmseg.py ${SRC} ${DST} + ``` + +- SegFormer + + `tools/model_converters/mit2mmseg.py` 将官方预训练模型转换到 MMSegmentation。 + + ```shell + python tools/model_converters/mit2mmseg.py ${SRC} ${DST} + ``` + +## 模型服务 + +为了用 [`TorchServe`](https://pytorch.org/serve/) 服务 `MMSegmentation` 的模型 , 您可以遵循如下流程: + +### 1. 将 model 从 MMSegmentation 转换到 TorchServe + +```shell +python tools/mmseg2torchserve.py ${CONFIG_FILE} ${CHECKPOINT_FILE} \ +--output-folder ${MODEL_STORE} \ +--model-name ${MODEL_NAME} +``` + +**注意**: ${MODEL_STORE} 需要设置为某个文件夹的绝对路径 + +### 2. 构建 `mmseg-serve` 容器镜像 (docker image) + +```shell +docker build -t mmseg-serve:latest docker/serve/ +``` + +### 3. 运行 `mmseg-serve` + +请查阅官方文档: [使用容器运行 TorchServe](https://github.com/pytorch/serve/blob/master/docker/README.md#running-torchserve-in-a-production-docker-environment) + +为了在 GPU 环境下使用, 您需要安装 [nvidia-docker](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html). 若在 CPU 环境下使用,您可以忽略添加 `--gpus` 参数。 + +示例: + +```shell +docker run --rm \ +--cpus 8 \ +--gpus device=0 \ +-p8080:8080 -p8081:8081 -p8082:8082 \ +--mount type=bind,source=$MODEL_STORE,target=/home/model-server/model-store \ +mmseg-serve:latest +``` + +阅读关于推理 (8080), 管理 (8081) 和指标 (8082) APIs 的 [文档](https://github.com/pytorch/serve/blob/072f5d088cce9bb64b2a18af065886c9b01b317b/docs/rest_api.md) 。 + +### 4. 测试部署 + +```shell +curl -O https://raw.githubusercontent.com/open-mmlab/mmsegmentation/master/resources/3dogs.jpg +curl http://127.0.0.1:8080/predictions/${MODEL_NAME} -T 3dogs.jpg -o 3dogs_mask.png +``` + +得到的响应将是一个 ".png" 的分割掩码. + +您可以按照如下方法可视化输出: + +```python +import matplotlib.pyplot as plt +import mmcv +plt.imshow(mmcv.imread("3dogs_mask.png", "grayscale")) +plt.show() +``` + +看到的东西将会和下图类似: + +![3dogs_mask](../../resources/3dogs_mask.png) + +然后您可以使用 `test_torchserve.py` 比较 torchserve 和 pytorch 的结果,并将它们可视化。 + +```shell +python tools/torchserve/test_torchserve.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} ${MODEL_NAME} +[--inference-addr ${INFERENCE_ADDR}] [--result-image ${RESULT_IMAGE}] [--device ${DEVICE}] +``` + +示例: + +```shell +python tools/torchserve/test_torchserve.py \ +demo/demo.png \ +configs/fcn/fcn_r50-d8_512x1024_40k_cityscapes.py \ +checkpoint/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608-efe53f0d.pth \ +fcn +``` diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/visualization.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/visualization.md new file mode 100644 index 0000000..2ef020b --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/visualization.md @@ -0,0 +1,173 @@ +# 可视化 + +MMSegmentation 1.x 提供了简便的方式监控训练时的状态以及可视化在模型预测时的数据。 + +## 训练状态监控 + +MMSegmentation 1.x 使用 TensorBoard 来监控训练时候的状态。 + +### TensorBoard 的配置 + +安装 TensorBoard 的过程可以按照 [官方安装指南](https://www.tensorflow.org/install) ,具体的步骤如下: + +```shell +pip install tensorboardX +pip install future tensorboard +``` + +在配置文件 `default_runtime.py` 的 `vis_backend` 中添加 `TensorboardVisBackend`。 + +```python +vis_backends = [dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +``` + +### 检查 TensorBoard 中的标量 + +启动训练实验的命令如下 + +```shell +python tools/train.py configs/pspnet/pspnet_r50-d8_4xb4-80k_ade20k-512x512.py --work-dir work_dir/test_visual +``` + +开始训练后找到 `work_dir` 中的 `vis_data` 路径,例如:本次特定测试的 vis_data 路径如下所示: + +```shell +work_dirs/test_visual/20220810_115248/vis_data +``` + +vis_data 路径中的标量文件包括了学习率、损失函数和 data_time 等,还记录了指标结果,您可以参考 MMEngine 中的 [记录日志教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/logging.html) 中的日志教程来帮助记录自己定义的数据。 Tensorboard 的可视化结果使用下面的命令执行: + +```shell +tensorboard --logdir work_dirs/test_visual/20220810_115248/vis_data +``` + +## 数据和结果的可视化 + +### 模型测试或验证期间的可视化数据样本 + +MMSegmentation 提供了 `SegVisualizationHook` ,它是一个可以用于可视化 ground truth 和在模型测试和验证期间的预测分割结果的[钩子](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/hook.html) 。 它的配置在 `default_hooks` 中,更多详细信息请参见 [执行器教程](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/runner.html)。 + +例如,在 `_base_/schedules/schedule_20k.py` 中,修改 `SegVisualizationHook` 配置,将 `draw` 设置为 `True` 以启用网络推理结果的存储,`interval` 表示预测结果的采样间隔, 设置为 1 时,将保存网络的每个推理结果。 `interval` 默认设置为 50: + +```python +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=2000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook', draw=True, interval=1)) + +``` + +启动训练实验后,可视化结果将在 validation loop 存储到本地文件夹中,或者在一个数据集上启动评估模型时,预测结果将存储在本地。本地的可视化的存储结果保存在 `$WORK_DIRS/vis_data` 下的 `vis_image` 中,例如: + +```shell +work_dirs/test_visual/20220810_115248/vis_data/vis_image +``` + +另外,如果在 `vis_backends` 中添加 `TensorboardVisBackend` ,如 [TensorBoard 的配置](###TensorBoard的配置),我们还可以运行下面的命令在 TensorBoard 中查看它们: + +```shell +tensorboard --logdir work_dirs/test_visual/20220810_115248/vis_data +``` + +### 可视化单个数据样本 + +如果你想可视化单个样本数据,我们建议使用 `SegLocalVisualizer` 。 + +`SegLocalVisualizer`是继承自 MMEngine 中`Visualizer` 类的子类,适用于 MMSegmentation 可视化,有关`Visualizer`的详细信息请参考在 MMEngine 中的[可视化教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/visualization.html) 。 + +以下是一个关于 `SegLocalVisualizer` 的示例,首先你可以使用下面的命令下载这个案例中的数据: + +
+ +
+ +```shell +wget https://user-images.githubusercontent.com/24582831/189833109-eddad58f-f777-4fc0-b98a-6bd429143b06.png --output-document aachen_000000_000019_leftImg8bit.png +wget https://user-images.githubusercontent.com/24582831/189833143-15f60f8a-4d1e-4cbb-a6e7-5e2233869fac.png --output-document aachen_000000_000019_gtFine_labelTrainIds.png +``` + +然后你可以找到他们本地的路径和使用下面的脚本文件对其进行可视化: + +```python +import mmcv +import os.path as osp +import torch + +# `PixelData` 是 MMEngine 中用于定义像素级标注或预测的数据结构。 +# 请参考下面的MMEngine数据结构教程文件: +# https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/data_element.html#pixeldata + +from mmengine.structures import PixelData + +# `SegDataSample` 是在 MMSegmentation 中定义的不同组件之间的数据结构接口, +# 它包括 ground truth、语义分割的预测结果和预测逻辑。 +# 详情请参考下面的 `SegDataSample` 教程文件: +# https://github.com/open-mmlab/mmsegmentation/blob/1.x/docs/en/advanced_guides/structures.md + +from mmseg.structures import SegDataSample +from mmseg.visualization import SegLocalVisualizer + +out_file = 'out_file_cityscapes' +save_dir = './work_dirs' + +image = mmcv.imread( + osp.join( + osp.dirname(__file__), + './aachen_000000_000019_leftImg8bit.png' + ), + 'color') +sem_seg = mmcv.imread( + osp.join( + osp.dirname(__file__), + './aachen_000000_000019_gtFine_labelTrainIds.png' # noqa + ), + 'unchanged') +sem_seg = torch.from_numpy(sem_seg) +gt_sem_seg_data = dict(data=sem_seg) +gt_sem_seg = PixelData(**gt_sem_seg_data) +data_sample = SegDataSample() +data_sample.gt_sem_seg = gt_sem_seg + +seg_local_visualizer = SegLocalVisualizer( + vis_backends=[dict(type='LocalVisBackend')], + save_dir=save_dir) + +# 数据集的元信息通常包括类名的 `classes` 和 +# 用于可视化每个前景颜色的 `palette` 。 +# 所有类名和调色板都在此文件中定义: +# https://github.com/open-mmlab/mmsegmentation/blob/1.x/mmseg/utils/class_names.py + +seg_local_visualizer.dataset_meta = dict( + classes=('road', 'sidewalk', 'building', 'wall', 'fence', + 'pole', 'traffic light', 'traffic sign', + 'vegetation', 'terrain', 'sky', 'person', 'rider', + 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle'), + palette=[[128, 64, 128], [244, 35, 232], [70, 70, 70], + [102, 102, 156], [190, 153, 153], [153, 153, 153], + [250, 170, 30], [220, 220, 0], [107, 142, 35], + [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], + [0, 60, 100], [0, 80, 100], [0, 0, 230], + [119, 11, 32]]) + +# 当`show=True`时,直接显示结果, +# 当 `show=False`时,结果将保存在本地文件夹中。 + +seg_local_visualizer.add_datasample(out_file, image, + data_sample, show=False) +``` + +可视化后的图像结果和它的对应的 ground truth 图像可以在 `./work_dirs/vis_data/vis_image/` 路径找到,文件名字是:`out_file_cityscapes_0.png` : + +
+ +
+ +如果你想知道更多的关于可视化的使用指引,你可以参考 MMEngine 中的[可视化教程](<[https://mmengine.readthedocs.io/en/latest/advanced_tutorials/visualization.html](https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/advanced_tutorials/visualization.md)>) diff --git a/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/visualization_feature_map.md b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/visualization_feature_map.md new file mode 100644 index 0000000..fda99bb --- /dev/null +++ b/Seg_All_In_One_MMSeg/docs/zh_cn/user_guides/visualization_feature_map.md @@ -0,0 +1,201 @@ +# wandb记录特征图可视化 + +MMSegmentation 1.x 提供了 Weights & Biases 的后端支持,方便对项目代码结果的可视化和管理。 + +## Wandb的配置 + +安装 Weights & Biases 的过程可以参考 [官方安装指南](https://docs.wandb.ai/quickstart),具体的步骤如下: + +```shell +pip install wandb +wandb login +``` + +在 `vis_backend` 中添加 `WandbVisBackend`。 + +```python +vis_backends=[dict(type='LocalVisBackend'), + dict(type='TensorboardVisBackend'), + dict(type='WandbVisBackend')] +``` + +## 测试数据和结果及特征图的可视化 + +`SegLocalVisualizer` 是继承自 MMEngine 中 `Visualizer` 类的子类,适用于 MMSegmentation 可视化,有关 `Visualizer` 的详细信息请参考在 MMEngine 中的[可视化教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/visualization.html) 。 + +以下是一个关于 `SegLocalVisualizer` 的示例,首先你可以使用下面的命令下载这个案例中的数据: + +
+ +
+ +```shell +wget https://user-images.githubusercontent.com/24582831/189833109-eddad58f-f777-4fc0-b98a-6bd429143b06.png --output-document aachen_000000_000019_leftImg8bit.png +wget https://user-images.githubusercontent.com/24582831/189833143-15f60f8a-4d1e-4cbb-a6e7-5e2233869fac.png --output-document aachen_000000_000019_gtFine_labelTrainIds.png + +wget https://download.openmmlab.com/mmsegmentation/v0.5/ann/ann_r50-d8_512x1024_40k_cityscapes/ann_r50-d8_512x1024_40k_cityscapes_20200605_095211-049fc292.pth + +``` + +```python +# Copyright (c) OpenMMLab. All rights reserved. +from argparse import ArgumentParser +from typing import Type + +import mmcv +import torch +import torch.nn as nn + +from mmengine.model import revert_sync_batchnorm +from mmengine.structures import PixelData +from mmseg.apis import inference_model, init_model +from mmseg.structures import SegDataSample +from mmseg.utils import register_all_modules +from mmseg.visualization import SegLocalVisualizer + + +class Recorder: + """record the forward output feature map and save to data_buffer.""" + + def __init__(self) -> None: + self.data_buffer = list() + + def __enter__(self, ): + self._data_buffer = list() + + def record_data_hook(self, model: nn.Module, input: Type, output: Type): + self.data_buffer.append(output) + + def __exit__(self, *args, **kwargs): + pass + + +def visualize(args, model, recorder, result): + seg_visualizer = SegLocalVisualizer( + vis_backends=[dict(type='WandbVisBackend')], + save_dir='temp_dir', + alpha=0.5) + seg_visualizer.dataset_meta = dict( + classes=model.dataset_meta['classes'], + palette=model.dataset_meta['palette']) + + image = mmcv.imread(args.img, 'color') + + seg_visualizer.add_datasample( + name='predict', + image=image, + data_sample=result, + draw_gt=False, + draw_pred=True, + wait_time=0, + out_file=None, + show=False) + + # add feature map to wandb visualizer + for i in range(len(recorder.data_buffer)): + feature = recorder.data_buffer[i][0] # remove the batch + drawn_img = seg_visualizer.draw_featmap( + feature, image, channel_reduction='select_max') + seg_visualizer.add_image(f'feature_map{i}', drawn_img) + + if args.gt_mask: + sem_seg = mmcv.imread(args.gt_mask, 'unchanged') + sem_seg = torch.from_numpy(sem_seg) + gt_mask = dict(data=sem_seg) + gt_mask = PixelData(**gt_mask) + data_sample = SegDataSample() + data_sample.gt_sem_seg = gt_mask + + seg_visualizer.add_datasample( + name='gt_mask', + image=image, + data_sample=data_sample, + draw_gt=True, + draw_pred=False, + wait_time=0, + out_file=None, + show=False) + + seg_visualizer.add_image('image', image) + + +def main(): + parser = ArgumentParser( + description='Draw the Feature Map During Inference') + parser.add_argument('img', help='Image file') + parser.add_argument('config', help='Config file') + parser.add_argument('checkpoint', help='Checkpoint file') + parser.add_argument('--gt_mask', default=None, help='Path of gt mask file') + parser.add_argument('--out-file', default=None, help='Path to output file') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + parser.add_argument( + '--opacity', + type=float, + default=0.5, + help='Opacity of painted segmentation map. In (0, 1] range.') + parser.add_argument( + '--title', default='result', help='The image identifier.') + args = parser.parse_args() + + register_all_modules() + + # build the model from a config file and a checkpoint file + model = init_model(args.config, args.checkpoint, device=args.device) + if args.device == 'cpu': + model = revert_sync_batchnorm(model) + + # show all named module in the model and use it in source list below + for name, module in model.named_modules(): + print(name) + + source = [ + 'decode_head.fusion.stages.0.query_project.activate', + 'decode_head.context.stages.0.key_project.activate', + 'decode_head.context.bottleneck.activate' + ] + source = dict.fromkeys(source) + + count = 0 + recorder = Recorder() + # registry the forward hook + for name, module in model.named_modules(): + if name in source: + count += 1 + module.register_forward_hook(recorder.record_data_hook) + if count == len(source): + break + + with recorder: + # test a single image, and record feature map to data_buffer + result = inference_model(model, args.img) + + visualize(args, model, recorder, result) + + +if __name__ == '__main__': + main() + +``` + +将上述代码保存为 feature_map_visual.py,在终端执行如下代码 + +```shell +python feature_map_visual.py ${图像} ${配置文件} ${检查点文件} [可选参数] +``` + +样例 + +```shell +python feature_map_visual.py \ +aachen_000000_000019_leftImg8bit.png \ +configs/ann/ann_r50-d8_4xb2-40k_cityscapes-512x1024.py \ +ann_r50-d8_512x1024_40k_cityscapes_20200605_095211-049fc292.pth \ +--gt_mask aachen_000000_000019_gtFine_labelTrainIds.png +``` + +可视化后的图像结果和它的对应的 feature map图像会出现在wandb账户中 + +
+ +
diff --git a/Seg_All_In_One_MMSeg/mmseg/.mim/configs b/Seg_All_In_One_MMSeg/mmseg/.mim/configs new file mode 120000 index 0000000..5992d10 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/.mim/configs @@ -0,0 +1 @@ +../../configs \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/mmseg/.mim/dataset-index.yml b/Seg_All_In_One_MMSeg/mmseg/.mim/dataset-index.yml new file mode 120000 index 0000000..49d87a8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/.mim/dataset-index.yml @@ -0,0 +1 @@ +../../dataset-index.yml \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/mmseg/.mim/model-index.yml b/Seg_All_In_One_MMSeg/mmseg/.mim/model-index.yml new file mode 120000 index 0000000..a18c0b3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/.mim/model-index.yml @@ -0,0 +1 @@ +../../model-index.yml \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/mmseg/.mim/tools b/Seg_All_In_One_MMSeg/mmseg/.mim/tools new file mode 120000 index 0000000..31941e9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/.mim/tools @@ -0,0 +1 @@ +../../tools \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/mmseg/__init__.py b/Seg_All_In_One_MMSeg/mmseg/__init__.py new file mode 100644 index 0000000..7b5e072 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/__init__.py @@ -0,0 +1,73 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import mmcv +import mmengine +from packaging.version import parse + +from .version import __version__, version_info + +MMCV_MIN = '2.0.0rc4' +MMCV_MAX = '2.2.0' +MMENGINE_MIN = '0.5.0' +MMENGINE_MAX = '1.0.0' + + +def digit_version(version_str: str, length: int = 4): + """Convert a version string into a tuple of integers. + + This method is usually used for comparing two versions. For pre-release + versions: alpha < beta < rc. + + Args: + version_str (str): The version string. + length (int): The maximum number of version levels. Default: 4. + + Returns: + tuple[int]: The version info in digits (integers). + """ + version = parse(version_str) + assert version.release, f'failed to parse version {version_str}' + release = list(version.release) + release = release[:length] + if len(release) < length: + release = release + [0] * (length - len(release)) + if version.is_prerelease: + mapping = {'a': -3, 'b': -2, 'rc': -1} + val = -4 + # version.pre can be None + if version.pre: + if version.pre[0] not in mapping: + warnings.warn(f'unknown prerelease version {version.pre[0]}, ' + 'version checking may go wrong') + else: + val = mapping[version.pre[0]] + release.extend([val, version.pre[-1]]) + else: + release.extend([val, 0]) + + elif version.is_postrelease: + release.extend([1, version.post]) + else: + release.extend([0, 0]) + return tuple(release) + + +mmcv_min_version = digit_version(MMCV_MIN) +mmcv_max_version = digit_version(MMCV_MAX) +mmcv_version = digit_version(mmcv.__version__) + +assert (mmcv_min_version <= mmcv_version <= mmcv_max_version), \ + f'MMCV=={mmcv.__version__} is used but incompatible. ' \ + f'Please install mmcv>=2.0.0rc4.' + +mmengine_min_version = digit_version(MMENGINE_MIN) +mmengine_max_version = digit_version(MMENGINE_MAX) +mmengine_version = digit_version(mmengine.__version__) + +assert (mmengine_min_version <= mmengine_version < mmengine_max_version), \ + f'MMEngine=={mmengine.__version__} is used but incompatible. ' \ + f'Please install mmengine>={mmengine_min_version}, '\ + f'<{mmengine_max_version}.' + +__all__ = ['__version__', 'version_info', 'digit_version'] diff --git a/Seg_All_In_One_MMSeg/mmseg/apis/__init__.py b/Seg_All_In_One_MMSeg/mmseg/apis/__init__.py new file mode 100644 index 0000000..b50a266 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/apis/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .inference import inference_model, init_model, show_result_pyplot +from .mmseg_inferencer import MMSegInferencer +from .remote_sense_inferencer import RSImage, RSInferencer + +__all__ = [ + 'init_model', 'inference_model', 'show_result_pyplot', 'MMSegInferencer', + 'RSInferencer', 'RSImage' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/apis/inference.py b/Seg_All_In_One_MMSeg/mmseg/apis/inference.py new file mode 100644 index 0000000..aab11d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/apis/inference.py @@ -0,0 +1,189 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from pathlib import Path +from typing import Optional, Union + +import mmcv +import numpy as np +import torch +from mmengine import Config +from mmengine.registry import init_default_scope +from mmengine.runner import load_checkpoint +from mmengine.utils import mkdir_or_exist + +from mmseg.models import BaseSegmentor +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample +from mmseg.utils import SampleList, dataset_aliases, get_classes, get_palette +from mmseg.visualization import SegLocalVisualizer +from .utils import ImageType, _preprare_data + + +def init_model(config: Union[str, Path, Config], + checkpoint: Optional[str] = None, + device: str = 'cuda:0', + cfg_options: Optional[dict] = None): + """Initialize a segmentor from config file. + + Args: + config (str, :obj:`Path`, or :obj:`mmengine.Config`): Config file path, + :obj:`Path`, or the config object. + checkpoint (str, optional): Checkpoint path. If left as None, the model + will not load any weights. + device (str, optional) CPU/CUDA device option. Default 'cuda:0'. + Use 'cpu' for loading model on CPU. + cfg_options (dict, optional): Options to override some settings in + the used config. + Returns: + nn.Module: The constructed segmentor. + """ + if isinstance(config, (str, Path)): + config = Config.fromfile(config) + elif not isinstance(config, Config): + raise TypeError('config must be a filename or Config object, ' + 'but got {}'.format(type(config))) + if cfg_options is not None: + config.merge_from_dict(cfg_options) + if config.model.type == 'EncoderDecoder': + if 'init_cfg' in config.model.backbone: + config.model.backbone.init_cfg = None + elif config.model.type == 'MultimodalEncoderDecoder': + for k, v in config.model.items(): + if isinstance(v, dict) and 'init_cfg' in v: + config.model[k].init_cfg = None + config.model.pretrained = None + config.model.train_cfg = None + init_default_scope(config.get('default_scope', 'mmseg')) + + model = MODELS.build(config.model) + if checkpoint is not None: + checkpoint = load_checkpoint(model, checkpoint, map_location='cpu') + dataset_meta = checkpoint['meta'].get('dataset_meta', None) + # save the dataset_meta in the model for convenience + if 'dataset_meta' in checkpoint.get('meta', {}): + # mmseg 1.x + model.dataset_meta = dataset_meta + elif 'CLASSES' in checkpoint.get('meta', {}): + # < mmseg 1.x + classes = checkpoint['meta']['CLASSES'] + palette = checkpoint['meta']['PALETTE'] + model.dataset_meta = {'classes': classes, 'palette': palette} + else: + warnings.simplefilter('once') + warnings.warn( + 'dataset_meta or class names are not saved in the ' + 'checkpoint\'s meta data, classes and palette will be' + 'set according to num_classes ') + num_classes = model.decode_head.num_classes + dataset_name = None + for name in dataset_aliases.keys(): + if len(get_classes(name)) == num_classes: + dataset_name = name + break + if dataset_name is None: + warnings.warn( + 'No suitable dataset found, use Cityscapes by default') + dataset_name = 'cityscapes' + model.dataset_meta = { + 'classes': get_classes(dataset_name), + 'palette': get_palette(dataset_name) + } + model.cfg = config # save the config in the model for convenience + model.to(device) + model.eval() + return model + + +def inference_model(model: BaseSegmentor, + img: ImageType) -> Union[SegDataSample, SampleList]: + """Inference image(s) with the segmentor. + + Args: + model (nn.Module): The loaded segmentor. + imgs (str/ndarray or list[str/ndarray]): Either image files or loaded + images. + + Returns: + :obj:`SegDataSample` or list[:obj:`SegDataSample`]: + If imgs is a list or tuple, the same length list type results + will be returned, otherwise return the segmentation results directly. + """ + # prepare data + data, is_batch = _preprare_data(img, model) + + # forward the model + with torch.no_grad(): + results = model.test_step(data) + + return results if is_batch else results[0] + + +def show_result_pyplot(model: BaseSegmentor, + img: Union[str, np.ndarray], + result: SegDataSample, + opacity: float = 0.5, + title: str = '', + draw_gt: bool = True, + draw_pred: bool = True, + wait_time: float = 0, + show: bool = True, + with_labels: Optional[bool] = True, + save_dir=None, + out_file=None): + """Visualize the segmentation results on the image. + + Args: + model (nn.Module): The loaded segmentor. + img (str or np.ndarray): Image filename or loaded image. + result (SegDataSample): The prediction SegDataSample result. + opacity(float): Opacity of painted segmentation map. + Default 0.5. Must be in (0, 1] range. + title (str): The title of pyplot figure. + Default is ''. + draw_gt (bool): Whether to draw GT SegDataSample. Default to True. + draw_pred (bool): Whether to draw Prediction SegDataSample. + Defaults to True. + wait_time (float): The interval of show (s). 0 is the special value + that means "forever". Defaults to 0. + show (bool): Whether to display the drawn image. + Default to True. + with_labels(bool, optional): Add semantic labels in visualization + result, Default to True. + save_dir (str, optional): Save file dir for all storage backends. + If it is None, the backend storage will not save any data. + out_file (str, optional): Path to output file. Default to None. + + + + Returns: + np.ndarray: the drawn image which channel is RGB. + """ + if hasattr(model, 'module'): + model = model.module + if isinstance(img, str): + image = mmcv.imread(img, channel_order='rgb') + else: + image = img + if save_dir is not None: + mkdir_or_exist(save_dir) + # init visualizer + visualizer = SegLocalVisualizer( + vis_backends=[dict(type='LocalVisBackend')], + save_dir=save_dir, + alpha=opacity) + visualizer.dataset_meta = dict( + classes=model.dataset_meta['classes'], + palette=model.dataset_meta['palette']) + visualizer.add_datasample( + name=title, + image=image, + data_sample=result, + draw_gt=draw_gt, + draw_pred=draw_pred, + wait_time=wait_time, + out_file=out_file, + show=show, + with_labels=with_labels) + vis_img = visualizer.get_image() + + return vis_img diff --git a/Seg_All_In_One_MMSeg/mmseg/apis/mmseg_inferencer.py b/Seg_All_In_One_MMSeg/mmseg/apis/mmseg_inferencer.py new file mode 100644 index 0000000..02a198b --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/apis/mmseg_inferencer.py @@ -0,0 +1,382 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import warnings +from typing import List, Optional, Sequence, Union + +import mmcv +import mmengine +import numpy as np +import torch +import torch.nn as nn +from mmcv.transforms import Compose +from mmengine.infer.infer import BaseInferencer, ModelType +from mmengine.model import revert_sync_batchnorm +from mmengine.registry import init_default_scope +from mmengine.runner.checkpoint import _load_checkpoint_to_model +from PIL import Image + +from mmseg.structures import SegDataSample +from mmseg.utils import ConfigType, SampleList, get_classes, get_palette +from mmseg.visualization import SegLocalVisualizer + +InputType = Union[str, np.ndarray] +InputsType = Union[InputType, Sequence[InputType]] +PredType = Union[SegDataSample, SampleList] + + +class MMSegInferencer(BaseInferencer): + """Semantic segmentation inferencer, provides inference and visualization + interfaces. Note: MMEngine >= 0.5.0 is required. + + Args: + model (str, optional): Path to the config file or the model name + defined in metafile. Take the `mmseg metafile `_ + as an example the `model` could be + "fcn_r50-d8_4xb2-40k_cityscapes-512x1024", and the weights of model + will be download automatically. If use config file, like + "configs/fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py", the + `weights` should be defined. + weights (str, optional): Path to the checkpoint. If it is not specified + and model is a model name of metafile, the weights will be loaded + from metafile. Defaults to None. + classes (list, optional): Input classes for result rendering, as the + prediction of segmentation model is a segment map with label + indices, `classes` is a list which includes items responding to the + label indices. If classes is not defined, visualizer will take + `cityscapes` classes by default. Defaults to None. + palette (list, optional): Input palette for result rendering, which is + a list of color palette responding to the classes. If palette is + not defined, visualizer will take `cityscapes` palette by default. + Defaults to None. + dataset_name (str, optional): `Dataset name or alias `_ + visulizer will use the meta information of the dataset i.e. classes + and palette, but the `classes` and `palette` have higher priority. + Defaults to None. + device (str, optional): Device to run inference. If None, the available + device will be automatically used. Defaults to None. + scope (str, optional): The scope of the model. Defaults to 'mmseg'. + """ # noqa + + preprocess_kwargs: set = set() + forward_kwargs: set = {'mode', 'out_dir'} + visualize_kwargs: set = { + 'show', 'wait_time', 'img_out_dir', 'opacity', 'return_vis', + 'with_labels' + } + postprocess_kwargs: set = {'pred_out_dir', 'return_datasample'} + + def __init__(self, + model: Union[ModelType, str], + weights: Optional[str] = None, + classes: Optional[Union[str, List]] = None, + palette: Optional[Union[str, List]] = None, + dataset_name: Optional[str] = None, + device: Optional[str] = None, + scope: Optional[str] = 'mmseg') -> None: + # A global counter tracking the number of images processes, for + # naming of the output images + self.num_visualized_imgs = 0 + self.num_pred_imgs = 0 + init_default_scope(scope if scope else 'mmseg') + super().__init__( + model=model, weights=weights, device=device, scope=scope) + + if device == 'cpu' or not torch.cuda.is_available(): + self.model = revert_sync_batchnorm(self.model) + + assert isinstance(self.visualizer, SegLocalVisualizer) + self.visualizer.set_dataset_meta(classes, palette, dataset_name) + + def _load_weights_to_model(self, model: nn.Module, + checkpoint: Optional[dict], + cfg: Optional[ConfigType]) -> None: + """Loading model weights and meta information from cfg and checkpoint. + + Subclasses could override this method to load extra meta information + from ``checkpoint`` and ``cfg`` to model. + + Args: + model (nn.Module): Model to load weights and meta information. + checkpoint (dict, optional): The loaded checkpoint. + cfg (Config or ConfigDict, optional): The loaded config. + """ + + if checkpoint is not None: + _load_checkpoint_to_model(model, checkpoint) + checkpoint_meta = checkpoint.get('meta', {}) + # save the dataset_meta in the model for convenience + if 'dataset_meta' in checkpoint_meta: + # mmsegmentation 1.x + model.dataset_meta = { + 'classes': checkpoint_meta['dataset_meta'].get('classes'), + 'palette': checkpoint_meta['dataset_meta'].get('palette') + } + elif 'CLASSES' in checkpoint_meta: + # mmsegmentation 0.x + classes = checkpoint_meta['CLASSES'] + palette = checkpoint_meta.get('PALETTE', None) + model.dataset_meta = {'classes': classes, 'palette': palette} + else: + warnings.warn( + 'dataset_meta or class names are not saved in the ' + 'checkpoint\'s meta data, use classes of Cityscapes by ' + 'default.') + model.dataset_meta = { + 'classes': get_classes('cityscapes'), + 'palette': get_palette('cityscapes') + } + else: + warnings.warn('Checkpoint is not loaded, and the inference ' + 'result is calculated by the randomly initialized ' + 'model!') + warnings.warn( + 'weights is None, use cityscapes classes by default.') + model.dataset_meta = { + 'classes': get_classes('cityscapes'), + 'palette': get_palette('cityscapes') + } + + def __call__(self, + inputs: InputsType, + return_datasamples: bool = False, + batch_size: int = 1, + return_vis: bool = False, + show: bool = False, + wait_time: int = 0, + out_dir: str = '', + img_out_dir: str = 'vis', + pred_out_dir: str = 'pred', + **kwargs) -> dict: + """Call the inferencer. + + Args: + inputs (Union[list, str, np.ndarray]): Inputs for the inferencer. + return_datasamples (bool): Whether to return results as + :obj:`SegDataSample`. Defaults to False. + batch_size (int): Batch size. Defaults to 1. + show (bool): Whether to display the rendering color segmentation + mask in a popup window. Defaults to False. + wait_time (float): The interval of show (s). Defaults to 0. + out_dir (str): Output directory of inference results. Defaults + to ''. + img_out_dir (str): Subdirectory of `out_dir`, used to save + rendering color segmentation mask, so `out_dir` must be defined + if you would like to save predicted mask. Defaults to 'vis'. + pred_out_dir (str): Subdirectory of `out_dir`, used to save + predicted mask file, so `out_dir` must be defined if you would + like to save predicted mask. Defaults to 'pred'. + + **kwargs: Other keyword arguments passed to :meth:`preprocess`, + :meth:`forward`, :meth:`visualize` and :meth:`postprocess`. + Each key in kwargs should be in the corresponding set of + ``preprocess_kwargs``, ``forward_kwargs``, ``visualize_kwargs`` + and ``postprocess_kwargs``. + + + Returns: + dict: Inference and visualization results. + """ + + if out_dir != '': + pred_out_dir = osp.join(out_dir, pred_out_dir) + img_out_dir = osp.join(out_dir, img_out_dir) + else: + pred_out_dir = '' + img_out_dir = '' + + return super().__call__( + inputs=inputs, + return_datasamples=return_datasamples, + batch_size=batch_size, + show=show, + wait_time=wait_time, + img_out_dir=img_out_dir, + pred_out_dir=pred_out_dir, + return_vis=return_vis, + **kwargs) + + def visualize(self, + inputs: list, + preds: List[dict], + return_vis: bool = False, + show: bool = False, + wait_time: int = 0, + img_out_dir: str = '', + opacity: float = 0.8, + with_labels: Optional[bool] = True) -> List[np.ndarray]: + """Visualize predictions. + + Args: + inputs (list): Inputs preprocessed by :meth:`_inputs_to_list`. + preds (Any): Predictions of the model. + show (bool): Whether to display the image in a popup window. + Defaults to False. + wait_time (float): The interval of show (s). Defaults to 0. + img_out_dir (str): Output directory of rendering prediction i.e. + color segmentation mask. Defaults: '' + opacity (int, float): The transparency of segmentation mask. + Defaults to 0.8. + + Returns: + List[np.ndarray]: Visualization results. + """ + if not show and img_out_dir == '' and not return_vis: + return None + if self.visualizer is None: + raise ValueError('Visualization needs the "visualizer" term' + 'defined in the config, but got None.') + + self.visualizer.set_dataset_meta(**self.model.dataset_meta) + self.visualizer.alpha = opacity + + results = [] + + for single_input, pred in zip(inputs, preds): + if isinstance(single_input, str): + img_bytes = mmengine.fileio.get(single_input) + img = mmcv.imfrombytes(img_bytes) + img = img[:, :, ::-1] + img_name = osp.basename(single_input) + elif isinstance(single_input, np.ndarray): + img = single_input.copy() + img_num = str(self.num_visualized_imgs).zfill(8) + '_vis' + img_name = f'{img_num}.jpg' + else: + raise ValueError('Unsupported input type:' + f'{type(single_input)}') + + out_file = osp.join(img_out_dir, img_name) if img_out_dir != ''\ + else None + + self.visualizer.add_datasample( + img_name, + img, + pred, + show=show, + wait_time=wait_time, + draw_gt=False, + draw_pred=True, + out_file=out_file, + with_labels=with_labels) + if return_vis: + results.append(self.visualizer.get_image()) + self.num_visualized_imgs += 1 + + return results if return_vis else None + + def postprocess(self, + preds: PredType, + visualization: List[np.ndarray], + return_datasample: bool = False, + pred_out_dir: str = '') -> dict: + """Process the predictions and visualization results from ``forward`` + and ``visualize``. + + This method should be responsible for the following tasks: + + 1. Pack the predictions and visualization results and return them. + 2. Save the predictions, if it needed. + + Args: + preds (List[Dict]): Predictions of the model. + visualization (List[np.ndarray]): The list of rendering color + segmentation mask. + return_datasample (bool): Whether to return results as datasamples. + Defaults to False. + pred_out_dir: File to save the inference results w/o + visualization. If left as empty, no file will be saved. + Defaults to ''. + + Returns: + dict: Inference and visualization results with key ``predictions`` + and ``visualization`` + + - ``visualization (Any)``: Returned by :meth:`visualize` + - ``predictions`` (List[np.ndarray], np.ndarray): Returned by + :meth:`forward` and processed in :meth:`postprocess`. + If ``return_datasample=False``, it will be the segmentation mask + with label indice. + """ + if return_datasample: + if len(preds) == 1: + return preds[0] + else: + return preds + + results_dict = {} + + results_dict['predictions'] = [] + results_dict['visualization'] = [] + + for i, pred in enumerate(preds): + pred_data = dict() + if 'pred_sem_seg' in pred.keys(): + pred_data['sem_seg'] = pred.pred_sem_seg.numpy().data[0] + elif 'pred_depth_map' in pred.keys(): + pred_data['depth_map'] = pred.pred_depth_map.numpy().data[0] + + if visualization is not None: + vis = visualization[i] + results_dict['visualization'].append(vis) + if pred_out_dir != '': + mmengine.mkdir_or_exist(pred_out_dir) + for key, data in pred_data.items(): + post_fix = '_pred.png' if key == 'sem_seg' else '_pred.npy' + img_name = str(self.num_pred_imgs).zfill(8) + post_fix + img_path = osp.join(pred_out_dir, img_name) + if key == 'sem_seg': + output = Image.fromarray(data.astype(np.uint8)) + output.save(img_path) + else: + np.save(img_path, data) + pred_data = next(iter(pred_data.values())) + results_dict['predictions'].append(pred_data) + self.num_pred_imgs += 1 + + if len(results_dict['predictions']) == 1: + results_dict['predictions'] = results_dict['predictions'][0] + if visualization is not None: + results_dict['visualization'] = \ + results_dict['visualization'][0] + return results_dict + + def _init_pipeline(self, cfg: ConfigType) -> Compose: + """Initialize the test pipeline. + + Return a pipeline to handle various input data, such as ``str``, + ``np.ndarray``. It is an abstract method in BaseInferencer, and should + be implemented in subclasses. + + The returned pipeline will be used to process a single data. + It will be used in :meth:`preprocess` like this: + + .. code-block:: python + def preprocess(self, inputs, batch_size, **kwargs): + ... + dataset = map(self.pipeline, dataset) + ... + """ + pipeline_cfg = cfg.test_dataloader.dataset.pipeline + # Loading annotations is also not applicable + for transform in ('LoadAnnotations', 'LoadDepthAnnotation'): + idx = self._get_transform_idx(pipeline_cfg, transform) + if idx != -1: + del pipeline_cfg[idx] + + load_img_idx = self._get_transform_idx(pipeline_cfg, + 'LoadImageFromFile') + if load_img_idx == -1: + raise ValueError( + 'LoadImageFromFile is not found in the test pipeline') + pipeline_cfg[load_img_idx]['type'] = 'InferencerLoader' + return Compose(pipeline_cfg) + + def _get_transform_idx(self, pipeline_cfg: ConfigType, name: str) -> int: + """Returns the index of the transform in a pipeline. + + If the transform is not found, returns -1. + """ + for i, transform in enumerate(pipeline_cfg): + if transform['type'] == name: + return i + return -1 diff --git a/Seg_All_In_One_MMSeg/mmseg/apis/remote_sense_inferencer.py b/Seg_All_In_One_MMSeg/mmseg/apis/remote_sense_inferencer.py new file mode 100644 index 0000000..6726c6a --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/apis/remote_sense_inferencer.py @@ -0,0 +1,279 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import threading +from queue import Queue +from typing import List, Optional, Tuple + +import numpy as np +import torch +from mmengine import Config +from mmengine.model import BaseModel +from mmengine.registry import init_default_scope +from mmengine.runner import load_checkpoint + +try: + from osgeo import gdal +except ImportError: + gdal = None + +from mmseg.registry import MODELS +from .utils import _preprare_data + + +class RSImage: + """Remote sensing image class. + + Args: + img (str or gdal.Dataset): Image file path or gdal.Dataset. + """ + + def __init__(self, image): + self.dataset = gdal.Open(image, gdal.GA_ReadOnly) if isinstance( + image, str) else image + assert isinstance(self.dataset, gdal.Dataset), \ + f'{image} is not a image' + self.width = self.dataset.RasterXSize + self.height = self.dataset.RasterYSize + self.channel = self.dataset.RasterCount + self.trans = self.dataset.GetGeoTransform() + self.proj = self.dataset.GetProjection() + self.band_list = [] + self.band_list.extend( + self.dataset.GetRasterBand(c + 1) for c in range(self.channel)) + self.grids = [] + + def read(self, grid: Optional[List] = None) -> np.ndarray: + """Read image data. If grid is None, read the whole image. + + Args: + grid (Optional[List], optional): Grid to read. Defaults to None. + Returns: + np.ndarray: Image data. + """ + if grid is None: + return np.einsum('ijk->jki', self.dataset.ReadAsArray()) + assert len( + grid) >= 4, 'grid must be a list containing at least 4 elements' + data = self.dataset.ReadAsArray(*grid[:4]) + if data.ndim == 2: + data = data[np.newaxis, ...] + return np.einsum('ijk->jki', data) + + def write(self, data: Optional[np.ndarray], grid: Optional[List] = None): + """Write image data. + + Args: + grid (Optional[List], optional): Grid to write. Defaults to None. + data (Optional[np.ndarray], optional): Data to write. + Defaults to None. + + Raises: + ValueError: Either grid or data must be provided. + """ + if grid is not None: + assert len(grid) == 8, 'grid must be a list of 8 elements' + for band in self.band_list: + band.WriteArray( + data[grid[5]:grid[5] + grid[7], grid[4]:grid[4] + grid[6]], + grid[0] + grid[4], grid[1] + grid[5]) + elif data is not None: + for i in range(self.channel): + self.band_list[i].WriteArray(data[..., i]) + else: + raise ValueError('Either grid or data must be provided.') + + def create_seg_map(self, output_path: Optional[str] = None): + if output_path is None: + output_path = 'output_label.tif' + driver = gdal.GetDriverByName('GTiff') + seg_map = driver.Create(output_path, self.width, self.height, 1, + gdal.GDT_Byte) + seg_map.SetGeoTransform(self.trans) + seg_map.SetProjection(self.proj) + seg_map_img = RSImage(seg_map) + seg_map_img.path = output_path + return seg_map_img + + def create_grids(self, + window_size: Tuple[int, int], + stride: Tuple[int, int] = (0, 0)): + """Create grids for image inference. + + Args: + window_size (Tuple[int, int]): the size of the sliding window. + stride (Tuple[int, int], optional): the stride of the sliding + window. Defaults to (0, 0). + + Raises: + AssertionError: window_size must be a tuple of 2 elements. + AssertionError: stride must be a tuple of 2 elements. + """ + assert len( + window_size) == 2, 'window_size must be a tuple of 2 elements' + assert len(stride) == 2, 'stride must be a tuple of 2 elements' + win_w, win_h = window_size + stride_x, stride_y = stride + + stride_x = win_w if stride_x == 0 else stride_x + stride_y = win_h if stride_y == 0 else stride_y + + x_half_overlap = (win_w - stride_x + 1) // 2 + y_half_overlap = (win_h - stride_y + 1) // 2 + + for y in range(0, self.height, stride_y): + y_end = y + win_h >= self.height + y_offset = self.height - win_h if y_end else y + y_size = win_h + y_crop_off = 0 if y_offset == 0 else y_half_overlap + y_crop_size = y_size if y_end else win_h - y_crop_off + + for x in range(0, self.width, stride_x): + x_end = x + win_w >= self.width + x_offset = self.width - win_w if x_end else x + x_size = win_w + x_crop_off = 0 if x_offset == 0 else x_half_overlap + x_crop_size = x_size if x_end else win_w - x_crop_off + + self.grids.append([ + x_offset, y_offset, x_size, y_size, x_crop_off, y_crop_off, + x_crop_size, y_crop_size + ]) + + +class RSInferencer: + """Remote sensing inference class. + + Args: + model (BaseModel): The loaded model. + batch_size (int, optional): Batch size. Defaults to 1. + thread (int, optional): Number of threads. Defaults to 1. + """ + + def __init__(self, model: BaseModel, batch_size: int = 1, thread: int = 1): + self.model = model + self.batch_size = batch_size + self.END_FLAG = object() + self.read_buffer = Queue(self.batch_size) + self.write_buffer = Queue(self.batch_size) + self.thread = thread + + @classmethod + def from_config_path(cls, + config_path: str, + checkpoint_path: str, + batch_size: int = 1, + thread: int = 1, + device: Optional[str] = 'cpu'): + """Initialize a segmentor from config file. + + Args: + config_path (str): Config file path. + checkpoint_path (str): Checkpoint path. + batch_size (int, optional): Batch size. Defaults to 1. + """ + init_default_scope('mmseg') + cfg = Config.fromfile(config_path) + model = MODELS.build(cfg.model) + model.cfg = cfg + load_checkpoint(model, checkpoint_path, map_location='cpu') + model.to(device) + model.eval() + return cls(model, batch_size, thread) + + @classmethod + def from_model(cls, + model: BaseModel, + checkpoint_path: Optional[str] = None, + batch_size: int = 1, + thread: int = 1, + device: Optional[str] = 'cpu'): + """Initialize a segmentor from model. + + Args: + model (BaseModel): The loaded model. + checkpoint_path (Optional[str]): Checkpoint path. + batch_size (int, optional): Batch size. Defaults to 1. + """ + if checkpoint_path is not None: + load_checkpoint(model, checkpoint_path, map_location='cpu') + model.to(device) + return cls(model, batch_size, thread) + + def read(self, + image: RSImage, + window_size: Tuple[int, int], + strides: Tuple[int, int] = (0, 0)): + """Load image data to read buffer. + + Args: + image (RSImage): The image to read. + window_size (Tuple[int, int]): The size of the sliding window. + strides (Tuple[int, int], optional): The stride of the sliding + window. Defaults to (0, 0). + """ + image.create_grids(window_size, strides) + for grid in image.grids: + self.read_buffer.put([grid, image.read(grid=grid)]) + self.read_buffer.put(self.END_FLAG) + + def inference(self): + """Inference image data from read buffer and put the result to write + buffer.""" + while True: + item = self.read_buffer.get() + if item == self.END_FLAG: + self.read_buffer.put(self.END_FLAG) + self.write_buffer.put(item) + break + data, _ = _preprare_data(item[1], self.model) + with torch.no_grad(): + result = self.model.test_step(data) + item[1] = result[0].pred_sem_seg.cpu().data.numpy()[0] + self.write_buffer.put(item) + self.read_buffer.task_done() + + def write(self, image: RSImage, output_path: Optional[str] = None): + """Write image data from write buffer. + + Args: + image (RSImage): The image to write. + output_path (Optional[str], optional): The path to save the + segmentation map. Defaults to None. + """ + seg_map = image.create_seg_map(output_path) + while True: + item = self.write_buffer.get() + if item == self.END_FLAG: + break + seg_map.write(data=item[1], grid=item[0]) + self.write_buffer.task_done() + + def run(self, + image: RSImage, + window_size: Tuple[int, int], + strides: Tuple[int, int] = (0, 0), + output_path: Optional[str] = None): + """Run inference with multi-threading. + + Args: + image (RSImage): The image to inference. + window_size (Tuple[int, int]): The size of the sliding window. + strides (Tuple[int, int], optional): The stride of the sliding + window. Defaults to (0, 0). + output_path (Optional[str], optional): The path to save the + segmentation map. Defaults to None. + """ + read_thread = threading.Thread( + target=self.read, args=(image, window_size, strides)) + read_thread.start() + inference_threads = [] + for _ in range(self.thread): + inference_thread = threading.Thread(target=self.inference) + inference_thread.start() + inference_threads.append(inference_thread) + write_thread = threading.Thread( + target=self.write, args=(image, output_path)) + write_thread.start() + read_thread.join() + for inference_thread in inference_threads: + inference_thread.join() + write_thread.join() diff --git a/Seg_All_In_One_MMSeg/mmseg/apis/utils.py b/Seg_All_In_One_MMSeg/mmseg/apis/utils.py new file mode 100644 index 0000000..4cf8775 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/apis/utils.py @@ -0,0 +1,41 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections import defaultdict +from typing import Sequence, Union + +import numpy as np +from mmengine.dataset import Compose +from mmengine.model import BaseModel + +ImageType = Union[str, np.ndarray, Sequence[str], Sequence[np.ndarray]] + + +def _preprare_data(imgs: ImageType, model: BaseModel): + + cfg = model.cfg + for t in cfg.test_pipeline: + if t.get('type') == 'LoadAnnotations': + cfg.test_pipeline.remove(t) + + is_batch = True + if not isinstance(imgs, (list, tuple)): + imgs = [imgs] + is_batch = False + + if isinstance(imgs[0], np.ndarray): + cfg.test_pipeline[0]['type'] = 'LoadImageFromNDArray' + + # TODO: Consider using the singleton pattern to avoid building + # a pipeline for each inference + pipeline = Compose(cfg.test_pipeline) + + data = defaultdict(list) + for img in imgs: + if isinstance(img, np.ndarray): + data_ = dict(img=img) + else: + data_ = dict(img_path=img) + data_ = pipeline(data_) + data['inputs'].append(data_['inputs']) + data['data_samples'].append(data_['data_samples']) + + return data, is_batch diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/datasets/loveda.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/datasets/loveda.py new file mode 100644 index 0000000..eb3d358 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/datasets/loveda.py @@ -0,0 +1,79 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import (RandomFlip, RandomResize, Resize, + TestTimeAug) +from mmengine.dataset.sampler import DefaultSampler, InfiniteSampler + +from mmseg.datasets.loveda import LoveDADataset +from mmseg.datasets.transforms.formatting import PackSegInputs +from mmseg.datasets.transforms.loading import LoadAnnotations +from mmseg.datasets.transforms.transforms import (PhotoMetricDistortion, + RandomCrop) +from mmseg.evaluation import IoUMetric + +# dataset settings +dataset_type = LoveDADataset +data_root = 'data/loveDA' +crop_size = (512, 512) +train_pipeline = [ + dict(type=LoadImageFromFile), + dict(type=LoadAnnotations, reduce_zero_label=True), + dict( + type=RandomResize, + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type=RandomCrop, crop_size=crop_size, cat_max_ratio=0.75), + dict(type=RandomFlip, prob=0.5), + dict(type=PhotoMetricDistortion), + dict(type=PackSegInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile), + dict(type=Resize, scale=(1024, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type=LoadAnnotations, reduce_zero_label=True), + dict(type=PackSegInputs) +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type=LoadImageFromFile, backend_args=None), + dict( + type=TestTimeAug, + transforms=[[ + dict(type=Resize, scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type=RandomFlip, prob=0., direction='horizontal'), + dict(type=RandomFlip, prob=1., direction='horizontal') + ], [dict(type=LoadAnnotations)], + [dict(type=PackSegInputs)]]) +] +train_dataloader = dict( + batch_size=2, + num_workers=12, + persistent_workers=True, + sampler=dict(type=InfiniteSampler, shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='img_dir/train', seg_map_path='ann_dir/train'), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'), + pipeline=test_pipeline)) + +test_dataloader = val_dataloader +val_evaluator = dict(type=IoUMetric, iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/datasets/potsdam.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/datasets/potsdam.py new file mode 100644 index 0000000..33a4ebf --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/datasets/potsdam.py @@ -0,0 +1,81 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.transforms.loading import LoadImageFromFile +from mmcv.transforms.processing import (RandomFlip, RandomResize, Resize, + TestTimeAug) +from mmengine.dataset.sampler import DefaultSampler, InfiniteSampler + +from mmseg.datasets.potsdam import PotsdamDataset +from mmseg.datasets.transforms.formatting import PackSegInputs +from mmseg.datasets.transforms.loading import LoadAnnotations +from mmseg.datasets.transforms.transforms import (PhotoMetricDistortion, + RandomCrop) +from mmseg.evaluation import IoUMetric + +# dataset settings +dataset_type = PotsdamDataset +data_root = 'data/potsdam' +crop_size = (512, 512) +train_pipeline = [ + dict(type=LoadImageFromFile), + dict(type=LoadAnnotations, reduce_zero_label=True), + dict( + type=RandomResize, + scale=(512, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type=RandomCrop, crop_size=crop_size, cat_max_ratio=0.75), + dict(type=RandomFlip, prob=0.5), + dict(type=PhotoMetricDistortion), + dict(type=PackSegInputs) +] +test_pipeline = [ + dict(type=LoadImageFromFile), + dict(type=Resize, scale=(512, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type=LoadAnnotations, reduce_zero_label=True), + dict(type=PackSegInputs) +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type=LoadImageFromFile, backend_args=None), + dict( + type=TestTimeAug, + transforms=[[ + dict(type=Resize, scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type=RandomFlip, prob=0., direction='horizontal'), + dict(type=RandomFlip, prob=1., direction='horizontal') + ], [dict(type=LoadAnnotations)], + [dict(type=PackSegInputs)]]) +] + +train_dataloader = dict( + batch_size=2, + num_workers=4, + persistent_workers=True, + sampler=dict(type=InfiniteSampler, shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='img_dir/train', seg_map_path='ann_dir/train'), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type=DefaultSampler, shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type=IoUMetric, iou_metrics=['mIoU']) # 'mDice', 'mFscore' +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/default_runtime.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/default_runtime.py new file mode 100644 index 0000000..c905020 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/default_runtime.py @@ -0,0 +1,22 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +from mmengine.visualization import LocalVisBackend + +from mmseg.models import SegTTAModel +from mmseg.visualization import SegLocalVisualizer + +env_cfg = dict( + cudnn_benchmark=False, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) +vis_backends = [dict(type=LocalVisBackend)] +visualizer = dict( + type=SegLocalVisualizer, vis_backends=vis_backends, name='visualizer') +log_processor = dict(by_epoch=False) +log_level = 'INFO' +load_from = None +resume = False + +tta_model = dict(type=SegTTAModel) +default_scope = None diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_160k.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_160k.py new file mode 100644 index 0000000..294d6ee --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_160k.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import PolyLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim.sgd import SGD + +from mmseg.engine import SegVisualizationHook + +# optimizer +optimizer = dict( + type=SGD, + # lr=0.01, + # momentum=0.9, + # weight_decay=0.0005 +) + +optim_wrapper = dict(type=OptimWrapper, optimizer=optimizer, clip_grad=None) + +# learning policy +param_scheduler = [ + dict( + type=PolyLR, + eta_min=1e-4, + power=0.9, + begin=0, + end=160000, + by_epoch=False) +] +# training schedule for 160k + +train_cfg = dict(type=IterBasedTrainLoop, max_iters=160000, val_interval=8000) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +default_hooks = dict( + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook, interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict(type=CheckpointHook, by_epoch=False, interval=8000), + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=SegVisualizationHook)) diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_20k.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_20k.py new file mode 100644 index 0000000..255300a --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_20k.py @@ -0,0 +1,36 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import PolyLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim.sgd import SGD + +from mmseg.engine import SegVisualizationHook + +# optimizer +optimizer = dict(type=SGD, lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type=OptimWrapper, optimizer=optimizer, clip_grad=None) + +# learning policy +param_scheduler = [ + dict( + type=PolyLR, + eta_min=1e-4, + power=0.9, + begin=0, + end=20000, + by_epoch=False) +] +# training schedule for 20k +train_cfg = dict(type=IterBasedTrainLoop, max_iters=20000, val_interval=2000) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +default_hooks = dict( + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook, interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict(type=CheckpointHook, by_epoch=False, interval=2000), + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=SegVisualizationHook)) diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_240k.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_240k.py new file mode 100644 index 0000000..cf9e5d3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_240k.py @@ -0,0 +1,34 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import PolyLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +# from mmengine.runner.loops import EpochBasedTrainLoop +from torch.optim.sgd import SGD + +from mmseg.engine import SegVisualizationHook + +optimizer = dict(type=SGD, lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type=OptimWrapper, optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type=PolyLR, + eta_min=1e-4, + power=0.9, + begin=0, + end=240000, + by_epoch=False) +] +# training schedule for 240k +train_cfg = dict(type=IterBasedTrainLoop, max_iters=240000, val_interval=24000) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) +default_hooks = dict( + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook, interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict(type=CheckpointHook, by_epoch=False, interval=24000), + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=SegVisualizationHook)) diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_25k.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_25k.py new file mode 100644 index 0000000..8a3ebf4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_25k.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import ConstantLR, LinearLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +# from mmengine.runner.loops import EpochBasedTrainLoop +from torch.optim.adamw import AdamW + +from mmseg.engine import SegVisualizationHook +from mmseg.engine.schedulers import PolyLRRatio + +# optimizer +optimizer = dict(type=AdamW, lr=0.01, weight_decay=0.1) + +optim_wrapper = dict(type=OptimWrapper, optimizer=optimizer, clip_grad=None) +# learning policy + +# learning policy +param_scheduler = [ + dict(type=LinearLR, start_factor=3e-2, begin=0, end=12000, by_epoch=False), + dict( + type=PolyLRRatio, + eta_min_ratio=3e-2, + power=0.9, + begin=12000, + end=24000, + by_epoch=False), + dict(type=ConstantLR, by_epoch=False, factor=1, begin=24000, end=25000) +] + +# training schedule for 25k +train_cfg = dict(type=IterBasedTrainLoop, max_iters=25000, val_interval=1000) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +default_hooks = dict( + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook, interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict(type=CheckpointHook, by_epoch=False, interval=1000), + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=SegVisualizationHook)) diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_300e.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_300e.py new file mode 100644 index 0000000..266dcf9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_300e.py @@ -0,0 +1,56 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.optim import AmpOptimWrapper # 导入 AmpOptimWrapper +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import PolyLR +# 导入 EpochBasedTrainLoop +from mmengine.runner.loops import EpochBasedTrainLoop, TestLoop, ValLoop +from torch.optim.adamw import AdamW # 推荐使用 AdamW 优化器 + +from mmseg.engine import SegVisualizationHook + +# --- 修改部分 --- + +# 1. 优化器 (Optimizer) +# 推荐使用 AdamW,它通常比 SGD 效果更好且更稳定 +optimizer = dict( + type=AdamW, + lr=0.0001, # AdamW 的学习率通常设置得比 SGD 小 + betas=(0.9, 0.999), + weight_decay=0.01) + +# 优化器封装 (OptimWrapper) +# clip_grad 用于梯度裁剪,防止梯度爆炸,可以根据需要设置 +optim_wrapper = dict( + type=OptimWrapper, optimizer=optimizer, clip_grad=dict(max_norm=1, norm_type=2)) + +# 2. 学习率调度器 (Learning Rate Scheduler) +# 总轮次设为 200 epochs +max_epochs = 200 +param_scheduler = [ + dict( + type=PolyLR, + eta_min=1e-5, # 学习率最小值 + power=0.9, + begin=0, + end=max_epochs, # 关键修改:结束点改为总 epochs + by_epoch=True) # 关键修改:改为按 epoch 更新学习率 +] + +# 3. 训练、验证和测试的配置 +# 关键修改:使用 EpochBasedTrainLoop +train_cfg = dict(type=EpochBasedTrainLoop, max_epochs=max_epochs, val_interval=1) # 每 1 个 epoch 验证一次 +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +# 4. 默认钩子 (Default Hooks) +default_hooks = dict( + timer=dict(type=IterTimerHook), + # 关键修改:让日志按 epoch 记录 + logger=dict(type=LoggerHook, interval=300, log_metric_by_epoch=True), + param_scheduler=dict(type=ParamSchedulerHook), + # 关键修改:让检查点(模型权重)按 epoch 保存 + checkpoint=dict(type=CheckpointHook, by_epoch=True, interval=10), # 每 10 个 epoch 保存一次 + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=SegVisualizationHook)) \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_320k.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_320k.py new file mode 100644 index 0000000..dae323e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_320k.py @@ -0,0 +1,36 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import PolyLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +# from mmengine.runner.loops import EpochBasedTrainLoop +from torch.optim.sgd import SGD + +from mmseg.engine import SegVisualizationHook + +# optimizer +optimizer = dict(type=SGD, lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type=OptimWrapper, optimizer=optimizer, clip_grad=None) + +# learning policy +param_scheduler = [ + dict( + type=PolyLR, + eta_min=1e-4, + power=0.9, + begin=0, + end=320000, + by_epoch=False) +] +# training schedule for 320k +train_cfg = dict(type=IterBasedTrainLoop, max_iters=320000, val_interval=32000) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) +default_hooks = dict( + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook, interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict(type=CheckpointHook, by_epoch=False, interval=32000), + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=SegVisualizationHook)) diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_40k.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_40k.py new file mode 100644 index 0000000..b4b2ea4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_40k.py @@ -0,0 +1,34 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import PolyLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim.sgd import SGD + +from mmseg.engine import SegVisualizationHook + +# optimizer +optimizer = dict(type=SGD, lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type=OptimWrapper, optimizer=optimizer, clip_grad=None) + +param_scheduler = [ + dict( + type=PolyLR, + eta_min=1e-4, + power=0.9, + begin=0, + end=40000, + by_epoch=False) +] +# training schedule for 40k +train_cfg = dict(type=IterBasedTrainLoop, max_iters=40000, val_interval=4000) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) +default_hooks = dict( + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook, interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict(type=CheckpointHook, by_epoch=False, interval=4000), + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=SegVisualizationHook)) diff --git a/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_80k.py b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_80k.py new file mode 100644 index 0000000..3e711ca --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/configs/_base_/schedules/schedule_80k.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, IterTimerHook, + LoggerHook, ParamSchedulerHook) +from mmengine.optim.optimizer.optimizer_wrapper import OptimWrapper +from mmengine.optim.scheduler.lr_scheduler import PolyLR +from mmengine.runner.loops import IterBasedTrainLoop, TestLoop, ValLoop +from torch.optim.sgd import SGD + +from mmseg.engine import SegVisualizationHook + +# optimizer +optimizer = dict( + type=SGD, + # lr=0.01, + # momentum=0.9, + # weight_decay=0.0005 +) + +optim_wrapper = dict(type=OptimWrapper, optimizer=optimizer, clip_grad=None) + +# learning policy +param_scheduler = [ + dict( + type=PolyLR, + eta_min=1e-4, + power=0.9, + begin=0, + end=80000, + by_epoch=False) +] +# training schedule for 80k +train_cfg = dict(type=IterBasedTrainLoop, max_iters=80000, val_interval=8000) +val_cfg = dict(type=ValLoop) +test_cfg = dict(type=TestLoop) + +default_hooks = dict( + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook, interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict(type=CheckpointHook, by_epoch=False, interval=8000), + sampler_seed=dict(type=DistSamplerSeedHook), + visualization=dict(type=SegVisualizationHook)) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/__init__.py b/Seg_All_In_One_MMSeg/mmseg/datasets/__init__.py new file mode 100644 index 0000000..4c84c5c --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/__init__.py @@ -0,0 +1,77 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# yapf: disable +from .ade import ADE20KDataset +from .basesegdataset import BaseCDDataset, BaseSegDataset +from .bdd100k import BDD100KDataset +from .chase_db1 import ChaseDB1Dataset +from .cityscapes import CityscapesDataset +from .coco_stuff import COCOStuffDataset +from .dark_zurich import DarkZurichDataset +from .dataset_wrappers import MultiImageMixDataset +from .decathlon import DecathlonDataset +from .drive import DRIVEDataset +from .dsdl import DSDLSegDataset +from .hrf import HRFDataset +from .hsi_drive import HSIDrive20Dataset +from .isaid import iSAIDDataset +from .isprs import ISPRSDataset +from .levir import LEVIRCDDataset +from .lip import LIPDataset +from .loveda import LoveDADataset +from .mapillary import MapillaryDataset_v1, MapillaryDataset_v2 +from .night_driving import NightDrivingDataset +from .nyu import NYUDataset +from .pascal_context import PascalContextDataset, PascalContextDataset59 +from .potsdam import PotsdamDataset +from .refuge import REFUGEDataset +from .stare import STAREDataset +from .synapse import SynapseDataset +from .publicdataset_cholecseg8k import PublicDataSet_CholecSeg8k # TODO +from .my_dataset_model import MyDataset_model # TODO +from .publicdataset_autolaparo import PublicDataSet_AutoLaparo # TODO +from .publicdataset_endovis_2017 import PublicDataSet_Endovis_2017 # TODO +from .publicdataset_dresden import PublicDataSet_Dresden # TODO +from .publicdataset_endovis_2018 import PublicDataSet_Endovis_2018 # TODO +# yapf: disable +from .transforms import (CLAHE, AdjustGamma, Albu, BioMedical3DPad, + BioMedical3DRandomCrop, BioMedical3DRandomFlip, + BioMedicalGaussianBlur, BioMedicalGaussianNoise, + BioMedicalRandomGamma, ConcatCDInput, GenerateEdge, + LoadAnnotations, LoadBiomedicalAnnotation, + LoadBiomedicalData, LoadBiomedicalImageFromFile, + LoadImageFromNDArray, LoadMultipleRSImageFromFile, + LoadSingleRSImageFromFile, PackSegInputs, + PhotoMetricDistortion, RandomCrop, RandomCutOut, + RandomMosaic, RandomRotate, RandomRotFlip, Rerange, + ResizeShortestEdge, ResizeToMultiple, RGB2Gray, + SegRescale) +from .voc import PascalVOCDataset + +# yapf: enable +__all__ = [ + 'PublicDataSet_CholecSeg8k', # TODO + 'MyDataset_model', # TODO + 'PublicDataSet_AutoLaparo', # TODO + 'PublicDataSet_Endovis_2017', # TODO + 'PublicDataSet_Dresden', # TODO + 'PublicDataSet_Endovis_2018', # TODO + 'BaseSegDataset', 'BioMedical3DRandomCrop', 'BioMedical3DRandomFlip', + 'CityscapesDataset', 'PascalVOCDataset', 'ADE20KDataset', + 'PascalContextDataset', 'PascalContextDataset59', 'ChaseDB1Dataset', + 'DRIVEDataset', 'HRFDataset', 'STAREDataset', 'DarkZurichDataset', + 'NightDrivingDataset', 'COCOStuffDataset', 'LoveDADataset', + 'MultiImageMixDataset', 'iSAIDDataset', 'ISPRSDataset', 'PotsdamDataset', + 'LoadAnnotations', 'RandomCrop', 'SegRescale', 'PhotoMetricDistortion', + 'RandomRotate', 'AdjustGamma', 'CLAHE', 'Rerange', 'RGB2Gray', + 'RandomCutOut', 'RandomMosaic', 'PackSegInputs', 'ResizeToMultiple', + 'LoadImageFromNDArray', 'LoadBiomedicalImageFromFile', + 'LoadBiomedicalAnnotation', 'LoadBiomedicalData', 'GenerateEdge', + 'DecathlonDataset', 'LIPDataset', 'ResizeShortestEdge', + 'BioMedicalGaussianNoise', 'BioMedicalGaussianBlur', + 'BioMedicalRandomGamma', 'BioMedical3DPad', 'RandomRotFlip', + 'SynapseDataset', 'REFUGEDataset', 'MapillaryDataset_v1', + 'MapillaryDataset_v2', 'Albu', 'LEVIRCDDataset', + 'LoadMultipleRSImageFromFile', 'LoadSingleRSImageFromFile', + 'ConcatCDInput', 'BaseCDDataset', 'DSDLSegDataset', 'BDD100KDataset', + 'NYUDataset', 'HSIDrive20Dataset' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/ade.py b/Seg_All_In_One_MMSeg/mmseg/datasets/ade.py new file mode 100644 index 0000000..e9bdae7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/ade.py @@ -0,0 +1,92 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class ADE20KDataset(BaseSegDataset): + """ADE20K dataset. + + In segmentation map annotation for ADE20K, 0 stands for background, which + is not included in 150 categories. ``reduce_zero_label`` is fixed to True. + The ``img_suffix`` is fixed to '.jpg' and ``seg_map_suffix`` is fixed to + '.png'. + """ + METAINFO = dict( + classes=('wall', 'building', 'sky', 'floor', 'tree', 'ceiling', 'road', + 'bed ', 'windowpane', 'grass', 'cabinet', 'sidewalk', + 'person', 'earth', 'door', 'table', 'mountain', 'plant', + 'curtain', 'chair', 'car', 'water', 'painting', 'sofa', + 'shelf', 'house', 'sea', 'mirror', 'rug', 'field', 'armchair', + 'seat', 'fence', 'desk', 'rock', 'wardrobe', 'lamp', + 'bathtub', 'railing', 'cushion', 'base', 'box', 'column', + 'signboard', 'chest of drawers', 'counter', 'sand', 'sink', + 'skyscraper', 'fireplace', 'refrigerator', 'grandstand', + 'path', 'stairs', 'runway', 'case', 'pool table', 'pillow', + 'screen door', 'stairway', 'river', 'bridge', 'bookcase', + 'blind', 'coffee table', 'toilet', 'flower', 'book', 'hill', + 'bench', 'countertop', 'stove', 'palm', 'kitchen island', + 'computer', 'swivel chair', 'boat', 'bar', 'arcade machine', + 'hovel', 'bus', 'towel', 'light', 'truck', 'tower', + 'chandelier', 'awning', 'streetlight', 'booth', + 'television receiver', 'airplane', 'dirt track', 'apparel', + 'pole', 'land', 'bannister', 'escalator', 'ottoman', 'bottle', + 'buffet', 'poster', 'stage', 'van', 'ship', 'fountain', + 'conveyer belt', 'canopy', 'washer', 'plaything', + 'swimming pool', 'stool', 'barrel', 'basket', 'waterfall', + 'tent', 'bag', 'minibike', 'cradle', 'oven', 'ball', 'food', + 'step', 'tank', 'trade name', 'microwave', 'pot', 'animal', + 'bicycle', 'lake', 'dishwasher', 'screen', 'blanket', + 'sculpture', 'hood', 'sconce', 'vase', 'traffic light', + 'tray', 'ashcan', 'fan', 'pier', 'crt screen', 'plate', + 'monitor', 'bulletin board', 'shower', 'radiator', 'glass', + 'clock', 'flag'), + palette=[[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50], + [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255], + [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7], + [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82], + [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3], + [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255], + [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220], + [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224], + [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255], + [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7], + [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153], + [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255], + [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0], + [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255], + [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255], + [11, 200, 200], [255, 82, 0], [0, 255, 245], [0, 61, 255], + [0, 255, 112], [0, 255, 133], [255, 0, 0], [255, 163, 0], + [255, 102, 0], [194, 255, 0], [0, 143, 255], [51, 255, 0], + [0, 82, 255], [0, 255, 41], [0, 255, 173], [10, 0, 255], + [173, 255, 0], [0, 255, 153], [255, 92, 0], [255, 0, 255], + [255, 0, 245], [255, 0, 102], [255, 173, 0], [255, 0, 20], + [255, 184, 184], [0, 31, 255], [0, 255, 61], [0, 71, 255], + [255, 0, 204], [0, 255, 194], [0, 255, 82], [0, 10, 255], + [0, 112, 255], [51, 0, 255], [0, 194, 255], [0, 122, 255], + [0, 255, 163], [255, 153, 0], [0, 255, 10], [255, 112, 0], + [143, 255, 0], [82, 0, 255], [163, 255, 0], [255, 235, 0], + [8, 184, 170], [133, 0, 255], [0, 255, 92], [184, 0, 255], + [255, 0, 31], [0, 184, 255], [0, 214, 255], [255, 0, 112], + [92, 255, 0], [0, 224, 255], [112, 224, 255], [70, 184, 160], + [163, 0, 255], [153, 0, 255], [71, 255, 0], [255, 0, 163], + [255, 204, 0], [255, 0, 143], [0, 255, 235], [133, 255, 0], + [255, 0, 235], [245, 0, 255], [255, 0, 122], [255, 245, 0], + [10, 190, 212], [214, 255, 0], [0, 204, 255], [20, 0, 255], + [255, 255, 0], [0, 153, 255], [0, 41, 255], [0, 255, 204], + [41, 0, 255], [41, 255, 0], [173, 0, 255], [0, 245, 255], + [71, 0, 255], [122, 0, 255], [0, 255, 184], [0, 92, 255], + [184, 255, 0], [0, 133, 255], [255, 214, 0], [25, 194, 194], + [102, 255, 0], [92, 0, 255]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + reduce_zero_label=True, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/basesegdataset.py b/Seg_All_In_One_MMSeg/mmseg/datasets/basesegdataset.py new file mode 100644 index 0000000..9c4668c --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/basesegdataset.py @@ -0,0 +1,552 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from typing import Callable, Dict, List, Optional, Sequence, Union + +import mmengine +import mmengine.fileio as fileio +import numpy as np +from mmengine.dataset import BaseDataset, Compose + +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class BaseSegDataset(BaseDataset): + """Custom dataset for semantic segmentation. An example of file structure + is as followed. + + .. code-block:: none + + ├── data + │ ├── my_dataset + │ │ ├── img_dir + │ │ │ ├── train + │ │ │ │ ├── xxx{img_suffix} + │ │ │ │ ├── yyy{img_suffix} + │ │ │ │ ├── zzz{img_suffix} + │ │ │ ├── val + │ │ ├── ann_dir + │ │ │ ├── train + │ │ │ │ ├── xxx{seg_map_suffix} + │ │ │ │ ├── yyy{seg_map_suffix} + │ │ │ │ ├── zzz{seg_map_suffix} + │ │ │ ├── val + + The img/gt_semantic_seg pair of BaseSegDataset should be of the same + except suffix. A valid img/gt_semantic_seg filename pair should be like + ``xxx{img_suffix}`` and ``xxx{seg_map_suffix}`` (extension is also included + in the suffix). If split is given, then ``xxx`` is specified in txt file. + Otherwise, all files in ``img_dir/``and ``ann_dir`` will be loaded. + Please refer to ``docs/en/tutorials/new_dataset.md`` for more details. + + + Args: + ann_file (str): Annotation file path. Defaults to ''. + metainfo (dict, optional): Meta information for dataset, such as + specify classes to load. Defaults to None. + data_root (str, optional): The root directory for ``data_prefix`` and + ``ann_file``. Defaults to None. + data_prefix (dict, optional): Prefix for training data. Defaults to + dict(img_path=None, seg_map_path=None). + img_suffix (str): Suffix of images. Default: '.jpg' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + filter_cfg (dict, optional): Config for filter data. Defaults to None. + indices (int or Sequence[int], optional): Support using first few + data in annotation file to facilitate training/testing on a smaller + dataset. Defaults to None which means using all ``data_infos``. + serialize_data (bool, optional): Whether to hold memory using + serialized objects, when enabled, data loader workers can use + shared RAM from master process instead of making a copy. Defaults + to True. + pipeline (list, optional): Processing pipeline. Defaults to []. + test_mode (bool, optional): ``test_mode=True`` means in test phase. + Defaults to False. + lazy_init (bool, optional): Whether to load annotation during + instantiation. In some cases, such as visualization, only the meta + information of the dataset is needed, which is not necessary to + load annotation file. ``Basedataset`` can skip load annotations to + save time by set ``lazy_init=True``. Defaults to False. + max_refetch (int, optional): If ``Basedataset.prepare_data`` get a + None img. The maximum extra number of cycles to get a valid + image. Defaults to 1000. + ignore_index (int): The label index to be ignored. Default: 255 + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See https://mmengine.readthedocs.io/en/latest/api/fileio.htm + for details. Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + METAINFO: dict = dict() + + def __init__(self, + ann_file: str = '', + img_suffix='.jpg', + seg_map_suffix='.png', + metainfo: Optional[dict] = None, + data_root: Optional[str] = None, + data_prefix: dict = dict(img_path='', seg_map_path=''), + filter_cfg: Optional[dict] = None, + indices: Optional[Union[int, Sequence[int]]] = None, + serialize_data: bool = True, + pipeline: List[Union[dict, Callable]] = [], + test_mode: bool = False, + lazy_init: bool = False, + max_refetch: int = 1000, + ignore_index: int = 255, + reduce_zero_label: bool = False, + backend_args: Optional[dict] = None) -> None: + + self.img_suffix = img_suffix + self.seg_map_suffix = seg_map_suffix + self.ignore_index = ignore_index + self.reduce_zero_label = reduce_zero_label + self.backend_args = backend_args.copy() if backend_args else None + + self.data_root = data_root + self.data_prefix = copy.copy(data_prefix) + self.ann_file = ann_file + self.filter_cfg = copy.deepcopy(filter_cfg) + self._indices = indices + self.serialize_data = serialize_data + self.test_mode = test_mode + self.max_refetch = max_refetch + self.data_list: List[dict] = [] + self.data_bytes: np.ndarray + + # Set meta information. + self._metainfo = self._load_metainfo(copy.deepcopy(metainfo)) + + # Get label map for custom classes + new_classes = self._metainfo.get('classes', None) + self.label_map = self.get_label_map(new_classes) + self._metainfo.update( + dict( + label_map=self.label_map, + reduce_zero_label=self.reduce_zero_label)) + + # Update palette based on label map or generate palette + # if it is not defined + updated_palette = self._update_palette() + self._metainfo.update(dict(palette=updated_palette)) + + # Join paths. + if self.data_root is not None: + self._join_prefix() + + # Build pipeline. + self.pipeline = Compose(pipeline) + # Full initialize the dataset. + if not lazy_init: + self.full_init() + + if test_mode: + assert self._metainfo.get('classes') is not None, \ + 'dataset metainfo `classes` should be specified when testing' + + @classmethod + def get_label_map(cls, + new_classes: Optional[Sequence] = None + ) -> Union[Dict, None]: + """Require label mapping. + + The ``label_map`` is a dictionary, its keys are the old label ids and + its values are the new label ids, and is used for changing pixel + labels in load_annotations. If and only if old classes in cls.METAINFO + is not equal to new classes in self._metainfo and nether of them is not + None, `label_map` is not None. + + Args: + new_classes (list, tuple, optional): The new classes name from + metainfo. Default to None. + + + Returns: + dict, optional: The mapping from old classes in cls.METAINFO to + new classes in self._metainfo + """ + old_classes = cls.METAINFO.get('classes', None) + if (new_classes is not None and old_classes is not None + and list(new_classes) != list(old_classes)): + + label_map = {} + if not set(new_classes).issubset(cls.METAINFO['classes']): + raise ValueError( + f'new classes {new_classes} is not a ' + f'subset of classes {old_classes} in METAINFO.') + for i, c in enumerate(old_classes): + if c not in new_classes: + label_map[i] = 255 + else: + label_map[i] = new_classes.index(c) + return label_map + else: + return None + + def _update_palette(self) -> list: + """Update palette after loading metainfo. + + If length of palette is equal to classes, just return the palette. + If palette is not defined, it will randomly generate a palette. + If classes is updated by customer, it will return the subset of + palette. + + Returns: + Sequence: Palette for current dataset. + """ + palette = self._metainfo.get('palette', []) + classes = self._metainfo.get('classes', []) + # palette does match classes + if len(palette) == len(classes): + return palette + + if len(palette) == 0: + # Get random state before set seed, and restore + # random state later. + # It will prevent loss of randomness, as the palette + # may be different in each iteration if not specified. + # See: https://github.com/open-mmlab/mmdetection/issues/5844 + state = np.random.get_state() + np.random.seed(42) + # random palette + new_palette = np.random.randint( + 0, 255, size=(len(classes), 3)).tolist() + np.random.set_state(state) + elif len(palette) >= len(classes) and self.label_map is not None: + new_palette = [] + # return subset of palette + for old_id, new_id in sorted( + self.label_map.items(), key=lambda x: x[1]): + if new_id != 255: + new_palette.append(palette[old_id]) + new_palette = type(palette)(new_palette) + else: + raise ValueError('palette does not match classes ' + f'as metainfo is {self._metainfo}.') + return new_palette + + def load_data_list(self) -> List[dict]: + """Load annotation from directory or annotation file. + + Returns: + list[dict]: All data info of dataset. + """ + data_list = [] + img_dir = self.data_prefix.get('img_path', None) + ann_dir = self.data_prefix.get('seg_map_path', None) + if not osp.isdir(self.ann_file) and self.ann_file: + assert osp.isfile(self.ann_file), \ + f'Failed to load `ann_file` {self.ann_file}' + lines = mmengine.list_from_file( + self.ann_file, backend_args=self.backend_args) + for line in lines: + img_name = line.strip() + data_info = dict( + img_path=osp.join(img_dir, img_name + self.img_suffix)) + if ann_dir is not None: + seg_map = img_name + self.seg_map_suffix + data_info['seg_map_path'] = osp.join(ann_dir, seg_map) + data_info['label_map'] = self.label_map + data_info['reduce_zero_label'] = self.reduce_zero_label + data_info['seg_fields'] = [] + data_list.append(data_info) + else: + _suffix_len = len(self.img_suffix) + for img in fileio.list_dir_or_file( + dir_path=img_dir, + list_dir=False, + suffix=self.img_suffix, + recursive=True, + backend_args=self.backend_args): + data_info = dict(img_path=osp.join(img_dir, img)) + if ann_dir is not None: + seg_map = img[:-_suffix_len] + self.seg_map_suffix + data_info['seg_map_path'] = osp.join(ann_dir, seg_map) + data_info['label_map'] = self.label_map + data_info['reduce_zero_label'] = self.reduce_zero_label + data_info['seg_fields'] = [] + data_list.append(data_info) + data_list = sorted(data_list, key=lambda x: x['img_path']) + return data_list + + +@DATASETS.register_module() +class BaseCDDataset(BaseDataset): + """Custom dataset for change detection. An example of file structure is as + followed. + + .. code-block:: none + + ├── data + │ ├── my_dataset + │ │ ├── img_dir + │ │ │ ├── train + │ │ │ │ ├── xxx{img_suffix} + │ │ │ │ ├── yyy{img_suffix} + │ │ │ │ ├── zzz{img_suffix} + │ │ │ ├── val + │ │ ├── img_dir2 + │ │ │ ├── train + │ │ │ │ ├── xxx{img_suffix} + │ │ │ │ ├── yyy{img_suffix} + │ │ │ │ ├── zzz{img_suffix} + │ │ │ ├── val + │ │ ├── ann_dir + │ │ │ ├── train + │ │ │ │ ├── xxx{seg_map_suffix} + │ │ │ │ ├── yyy{seg_map_suffix} + │ │ │ │ ├── zzz{seg_map_suffix} + │ │ │ ├── val + + The image names in img_dir and img_dir2 should be consistent. + The img/gt_semantic_seg pair of BaseSegDataset should be of the same + except suffix. A valid img/gt_semantic_seg filename pair should be like + ``xxx{img_suffix}`` and ``xxx{seg_map_suffix}`` (extension is also included + in the suffix). If split is given, then ``xxx`` is specified in txt file. + Otherwise, all files in ``img_dir/``and ``ann_dir`` will be loaded. + Please refer to ``docs/en/tutorials/new_dataset.md`` for more details. + + + Args: + ann_file (str): Annotation file path. Defaults to ''. + metainfo (dict, optional): Meta information for dataset, such as + specify classes to load. Defaults to None. + data_root (str, optional): The root directory for ``data_prefix`` and + ``ann_file``. Defaults to None. + data_prefix (dict, optional): Prefix for training data. Defaults to + dict(img_path=None, img_path2=None, seg_map_path=None). + img_suffix (str): Suffix of images. Default: '.jpg' + img_suffix2 (str): Suffix of images. Default: '.jpg' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + filter_cfg (dict, optional): Config for filter data. Defaults to None. + indices (int or Sequence[int], optional): Support using first few + data in annotation file to facilitate training/testing on a smaller + dataset. Defaults to None which means using all ``data_infos``. + serialize_data (bool, optional): Whether to hold memory using + serialized objects, when enabled, data loader workers can use + shared RAM from master process instead of making a copy. Defaults + to True. + pipeline (list, optional): Processing pipeline. Defaults to []. + test_mode (bool, optional): ``test_mode=True`` means in test phase. + Defaults to False. + lazy_init (bool, optional): Whether to load annotation during + instantiation. In some cases, such as visualization, only the meta + information of the dataset is needed, which is not necessary to + load annotation file. ``Basedataset`` can skip load annotations to + save time by set ``lazy_init=True``. Defaults to False. + max_refetch (int, optional): If ``Basedataset.prepare_data`` get a + None img. The maximum extra number of cycles to get a valid + image. Defaults to 1000. + ignore_index (int): The label index to be ignored. Default: 255 + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See https://mmengine.readthedocs.io/en/latest/api/fileio.htm + for details. Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + METAINFO: dict = dict() + + def __init__(self, + ann_file: str = '', + img_suffix='.jpg', + img_suffix2='.jpg', + seg_map_suffix='.png', + metainfo: Optional[dict] = None, + data_root: Optional[str] = None, + data_prefix: dict = dict( + img_path='', img_path2='', seg_map_path=''), + filter_cfg: Optional[dict] = None, + indices: Optional[Union[int, Sequence[int]]] = None, + serialize_data: bool = True, + pipeline: List[Union[dict, Callable]] = [], + test_mode: bool = False, + lazy_init: bool = False, + max_refetch: int = 1000, + ignore_index: int = 255, + reduce_zero_label: bool = False, + backend_args: Optional[dict] = None) -> None: + + self.img_suffix = img_suffix + self.img_suffix2 = img_suffix2 + self.seg_map_suffix = seg_map_suffix + self.ignore_index = ignore_index + self.reduce_zero_label = reduce_zero_label + self.backend_args = backend_args.copy() if backend_args else None + + self.data_root = data_root + self.data_prefix = copy.copy(data_prefix) + self.ann_file = ann_file + self.filter_cfg = copy.deepcopy(filter_cfg) + self._indices = indices + self.serialize_data = serialize_data + self.test_mode = test_mode + self.max_refetch = max_refetch + self.data_list: List[dict] = [] + self.data_bytes: np.ndarray + + # Set meta information. + self._metainfo = self._load_metainfo(copy.deepcopy(metainfo)) + + # Get label map for custom classes + new_classes = self._metainfo.get('classes', None) + self.label_map = self.get_label_map(new_classes) + self._metainfo.update( + dict( + label_map=self.label_map, + reduce_zero_label=self.reduce_zero_label)) + + # Update palette based on label map or generate palette + # if it is not defined + updated_palette = self._update_palette() + self._metainfo.update(dict(palette=updated_palette)) + + # Join paths. + if self.data_root is not None: + self._join_prefix() + + # Build pipeline. + self.pipeline = Compose(pipeline) + # Full initialize the dataset. + if not lazy_init: + self.full_init() + + if test_mode: + assert self._metainfo.get('classes') is not None, \ + 'dataset metainfo `classes` should be specified when testing' + + @classmethod + def get_label_map(cls, + new_classes: Optional[Sequence] = None + ) -> Union[Dict, None]: + """Require label mapping. + + The ``label_map`` is a dictionary, its keys are the old label ids and + its values are the new label ids, and is used for changing pixel + labels in load_annotations. If and only if old classes in cls.METAINFO + is not equal to new classes in self._metainfo and nether of them is not + None, `label_map` is not None. + + Args: + new_classes (list, tuple, optional): The new classes name from + metainfo. Default to None. + + + Returns: + dict, optional: The mapping from old classes in cls.METAINFO to + new classes in self._metainfo + """ + old_classes = cls.METAINFO.get('classes', None) + if (new_classes is not None and old_classes is not None + and list(new_classes) != list(old_classes)): + + label_map = {} + if not set(new_classes).issubset(cls.METAINFO['classes']): + raise ValueError( + f'new classes {new_classes} is not a ' + f'subset of classes {old_classes} in METAINFO.') + for i, c in enumerate(old_classes): + if c not in new_classes: + label_map[i] = 255 + else: + label_map[i] = new_classes.index(c) + return label_map + else: + return None + + def _update_palette(self) -> list: + """Update palette after loading metainfo. + + If length of palette is equal to classes, just return the palette. + If palette is not defined, it will randomly generate a palette. + If classes is updated by customer, it will return the subset of + palette. + + Returns: + Sequence: Palette for current dataset. + """ + palette = self._metainfo.get('palette', []) + classes = self._metainfo.get('classes', []) + # palette does match classes + if len(palette) == len(classes): + return palette + + if len(palette) == 0: + # Get random state before set seed, and restore + # random state later. + # It will prevent loss of randomness, as the palette + # may be different in each iteration if not specified. + # See: https://github.com/open-mmlab/mmdetection/issues/5844 + state = np.random.get_state() + np.random.seed(42) + # random palette + new_palette = np.random.randint( + 0, 255, size=(len(classes), 3)).tolist() + np.random.set_state(state) + elif len(palette) >= len(classes) and self.label_map is not None: + new_palette = [] + # return subset of palette + for old_id, new_id in sorted( + self.label_map.items(), key=lambda x: x[1]): + if new_id != 255: + new_palette.append(palette[old_id]) + new_palette = type(palette)(new_palette) + else: + raise ValueError('palette does not match classes ' + f'as metainfo is {self._metainfo}.') + return new_palette + + def load_data_list(self) -> List[dict]: + """Load annotation from directory or annotation file. + + Returns: + list[dict]: All data info of dataset. + """ + data_list = [] + img_dir = self.data_prefix.get('img_path', None) + img_dir2 = self.data_prefix.get('img_path2', None) + ann_dir = self.data_prefix.get('seg_map_path', None) + if osp.isfile(self.ann_file): + lines = mmengine.list_from_file( + self.ann_file, backend_args=self.backend_args) + for line in lines: + img_name = line.strip() + if '.' in osp.basename(img_name): + img_name, img_ext = osp.splitext(img_name) + self.img_suffix = img_ext + self.img_suffix2 = img_ext + data_info = dict( + img_path=osp.join(img_dir, img_name + self.img_suffix), + img_path2=osp.join(img_dir2, img_name + self.img_suffix2)) + + if ann_dir is not None: + seg_map = img_name + self.seg_map_suffix + data_info['seg_map_path'] = osp.join(ann_dir, seg_map) + data_info['label_map'] = self.label_map + data_info['reduce_zero_label'] = self.reduce_zero_label + data_info['seg_fields'] = [] + data_list.append(data_info) + else: + for img in fileio.list_dir_or_file( + dir_path=img_dir, + list_dir=False, + suffix=self.img_suffix, + recursive=True, + backend_args=self.backend_args): + if '.' in osp.basename(img): + img, img_ext = osp.splitext(img) + self.img_suffix = img_ext + self.img_suffix2 = img_ext + data_info = dict( + img_path=osp.join(img_dir, img + self.img_suffix), + img_path2=osp.join(img_dir2, img + self.img_suffix2)) + if ann_dir is not None: + seg_map = img + self.seg_map_suffix + data_info['seg_map_path'] = osp.join(ann_dir, seg_map) + data_info['label_map'] = self.label_map + data_info['reduce_zero_label'] = self.reduce_zero_label + data_info['seg_fields'] = [] + data_list.append(data_info) + data_list = sorted(data_list, key=lambda x: x['img_path']) + return data_list diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/bdd100k.py b/Seg_All_In_One_MMSeg/mmseg/datasets/bdd100k.py new file mode 100644 index 0000000..8ae70b5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/bdd100k.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +from mmseg.datasets.basesegdataset import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class BDD100KDataset(BaseSegDataset): + METAINFO = dict( + classes=('road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', + 'sky', 'person', 'rider', 'car', 'truck', 'bus', 'train', + 'motorcycle', 'bicycle'), + palette=[[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, + 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], + [220, 20, 60], [255, 0, 0], [0, 0, 142], [0, 0, 70], + [0, 60, 100], [0, 80, 100], [0, 0, 230], [119, 11, 32]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/chase_db1.py b/Seg_All_In_One_MMSeg/mmseg/datasets/chase_db1.py new file mode 100644 index 0000000..626ddf7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/chase_db1.py @@ -0,0 +1,32 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class ChaseDB1Dataset(BaseSegDataset): + """Chase_db1 dataset. + + In segmentation map annotation for Chase_db1, 0 stands for background, + which is included in 2 categories. ``reduce_zero_label`` is fixed to False. + The ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is fixed to + '_1stHO.png'. + """ + METAINFO = dict( + classes=('background', 'vessel'), + palette=[[120, 120, 120], [6, 230, 230]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='_1stHO.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + assert fileio.exists( + self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/cityscapes.py b/Seg_All_In_One_MMSeg/mmseg/datasets/cityscapes.py new file mode 100644 index 0000000..f494d62 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/cityscapes.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class CityscapesDataset(BaseSegDataset): + """Cityscapes dataset. + + The ``img_suffix`` is fixed to '_leftImg8bit.png' and ``seg_map_suffix`` is + fixed to '_gtFine_labelTrainIds.png' for Cityscapes dataset. + """ + METAINFO = dict( + classes=('road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', + 'sky', 'person', 'rider', 'car', 'truck', 'bus', 'train', + 'motorcycle', 'bicycle'), + palette=[[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, + 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], + [220, 20, 60], [255, 0, 0], [0, 0, 142], [0, 0, 70], + [0, 60, 100], [0, 80, 100], [0, 0, 230], [119, 11, 32]]) + + def __init__(self, + img_suffix='_leftImg8bit.png', + seg_map_suffix='_gtFine_labelTrainIds.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/coco_stuff.py b/Seg_All_In_One_MMSeg/mmseg/datasets/coco_stuff.py new file mode 100644 index 0000000..1e1574d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/coco_stuff.py @@ -0,0 +1,99 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class COCOStuffDataset(BaseSegDataset): + """COCO-Stuff dataset. + + In segmentation map annotation for COCO-Stuff, Train-IDs of the 10k version + are from 1 to 171, where 0 is the ignore index, and Train-ID of COCO Stuff + 164k is from 0 to 170, where 255 is the ignore index. So, they are all 171 + semantic categories. ``reduce_zero_label`` is set to True and False for the + 10k and 164k versions, respectively. The ``img_suffix`` is fixed to '.jpg', + and ``seg_map_suffix`` is fixed to '.png'. + """ + METAINFO = dict( + classes=( + 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', + 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', + 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', + 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', + 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', + 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', + 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', + 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', + 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', + 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', + 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', + 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', + 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush', 'banner', + 'blanket', 'branch', 'bridge', 'building-other', 'bush', 'cabinet', + 'cage', 'cardboard', 'carpet', 'ceiling-other', 'ceiling-tile', + 'cloth', 'clothes', 'clouds', 'counter', 'cupboard', 'curtain', + 'desk-stuff', 'dirt', 'door-stuff', 'fence', 'floor-marble', + 'floor-other', 'floor-stone', 'floor-tile', 'floor-wood', 'flower', + 'fog', 'food-other', 'fruit', 'furniture-other', 'grass', 'gravel', + 'ground-other', 'hill', 'house', 'leaves', 'light', 'mat', 'metal', + 'mirror-stuff', 'moss', 'mountain', 'mud', 'napkin', 'net', + 'paper', 'pavement', 'pillow', 'plant-other', 'plastic', + 'platform', 'playingfield', 'railing', 'railroad', 'river', 'road', + 'rock', 'roof', 'rug', 'salad', 'sand', 'sea', 'shelf', + 'sky-other', 'skyscraper', 'snow', 'solid-other', 'stairs', + 'stone', 'straw', 'structural-other', 'table', 'tent', + 'textile-other', 'towel', 'tree', 'vegetable', 'wall-brick', + 'wall-concrete', 'wall-other', 'wall-panel', 'wall-stone', + 'wall-tile', 'wall-wood', 'water-other', 'waterdrops', + 'window-blind', 'window-other', 'wood'), + palette=[[0, 192, 64], [0, 192, 64], [0, 64, 96], [128, 192, 192], + [0, 64, 64], [0, 192, 224], [0, 192, 192], [128, 192, 64], + [0, 192, 96], [128, 192, 64], [128, 32, 192], [0, 0, 224], + [0, 0, 64], [0, 160, 192], [128, 0, 96], [128, 0, 192], + [0, 32, 192], [128, 128, 224], [0, 0, 192], [128, 160, 192], + [128, 128, 0], [128, 0, 32], [128, 32, 0], [128, 0, 128], + [64, 128, 32], [0, 160, 0], [0, 0, 0], [192, 128, 160], + [0, 32, 0], [0, 128, 128], [64, 128, 160], [128, 160, 0], + [0, 128, 0], [192, 128, 32], [128, 96, 128], [0, 0, 128], + [64, 0, 32], [0, 224, 128], [128, 0, 0], [192, 0, 160], + [0, 96, 128], [128, 128, 128], [64, 0, 160], [128, 224, 128], + [128, 128, 64], [192, 0, 32], [128, 96, 0], [128, 0, 192], + [0, 128, 32], [64, 224, 0], [0, 0, 64], [128, 128, 160], + [64, 96, 0], [0, 128, 192], [0, 128, 160], [192, 224, 0], + [0, 128, 64], [128, 128, 32], [192, 32, 128], [0, 64, 192], + [0, 0, 32], [64, 160, 128], [128, 64, 64], [128, 0, 160], + [64, 32, 128], [128, 192, 192], [0, 0, 160], [192, 160, 128], + [128, 192, 0], [128, 0, 96], [192, 32, 0], [128, 64, 128], + [64, 128, 96], [64, 160, 0], [0, 64, 0], [192, 128, 224], + [64, 32, 0], [0, 192, 128], [64, 128, 224], [192, 160, 0], + [0, 192, 0], [192, 128, 96], [192, 96, 128], [0, 64, 128], + [64, 0, 96], [64, 224, 128], [128, 64, 0], [192, 0, 224], + [64, 96, 128], [128, 192, 128], [64, 0, 224], [192, 224, 128], + [128, 192, 64], [192, 0, 96], [192, 96, 0], [128, 64, 192], + [0, 128, 96], [0, 224, 0], [64, 64, 64], [128, 128, 224], + [0, 96, 0], [64, 192, 192], [0, 128, 224], [128, 224, 0], + [64, 192, 64], [128, 128, 96], [128, 32, 128], [64, 0, 192], + [0, 64, 96], [0, 160, 128], [192, 0, 64], [128, 64, 224], + [0, 32, 128], [192, 128, 192], [0, 64, 224], [128, 160, 128], + [192, 128, 0], [128, 64, 32], [128, 32, 64], [192, 0, 128], + [64, 192, 32], [0, 160, 64], [64, 0, 0], [192, 192, 160], + [0, 32, 64], [64, 128, 128], [64, 192, 160], [128, 160, 64], + [64, 128, 0], [192, 192, 32], [128, 96, 192], [64, 0, 128], + [64, 64, 32], [0, 224, 192], [192, 0, 0], [192, 64, 160], + [0, 96, 192], [192, 128, 128], [64, 64, 160], [128, 224, 192], + [192, 128, 64], [192, 64, 32], [128, 96, 64], [192, 0, 192], + [0, 192, 32], [64, 224, 64], [64, 0, 64], [128, 192, 160], + [64, 96, 64], [64, 128, 192], [0, 192, 160], [192, 224, 64], + [64, 128, 64], [128, 192, 32], [192, 32, 192], [64, 64, 192], + [0, 64, 32], [64, 160, 192], [192, 64, 64], [128, 64, 160], + [64, 32, 192], [192, 192, 192], [0, 64, 160], [192, 160, 192], + [192, 192, 0], [128, 64, 96], [192, 32, 64], [192, 64, 128], + [64, 192, 96], [64, 160, 64], [64, 64, 0]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='_labelTrainIds.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/dark_zurich.py b/Seg_All_In_One_MMSeg/mmseg/datasets/dark_zurich.py new file mode 100644 index 0000000..9b5393f --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/dark_zurich.py @@ -0,0 +1,15 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .cityscapes import CityscapesDataset + + +@DATASETS.register_module() +class DarkZurichDataset(CityscapesDataset): + """DarkZurichDataset dataset.""" + + def __init__(self, + img_suffix='_rgb_anon.png', + seg_map_suffix='_gt_labelTrainIds.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/dataset_wrappers.py b/Seg_All_In_One_MMSeg/mmseg/datasets/dataset_wrappers.py new file mode 100644 index 0000000..082c116 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/dataset_wrappers.py @@ -0,0 +1,136 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import collections +import copy +from typing import List, Optional, Sequence, Union + +from mmengine.dataset import ConcatDataset, force_full_init + +from mmseg.registry import DATASETS, TRANSFORMS + + +@DATASETS.register_module() +class MultiImageMixDataset: + """A wrapper of multiple images mixed dataset. + + Suitable for training on multiple images mixed data augmentation like + mosaic and mixup. + + Args: + dataset (ConcatDataset or dict): The dataset to be mixed. + pipeline (Sequence[dict]): Sequence of transform object or + config dict to be composed. + skip_type_keys (list[str], optional): Sequence of type string to + be skip pipeline. Default to None. + """ + + def __init__(self, + dataset: Union[ConcatDataset, dict], + pipeline: Sequence[dict], + skip_type_keys: Optional[List[str]] = None, + lazy_init: bool = False) -> None: + assert isinstance(pipeline, collections.abc.Sequence) + + if isinstance(dataset, dict): + self.dataset = DATASETS.build(dataset) + elif isinstance(dataset, ConcatDataset): + self.dataset = dataset + else: + raise TypeError( + 'elements in datasets sequence should be config or ' + f'`ConcatDataset` instance, but got {type(dataset)}') + + if skip_type_keys is not None: + assert all([ + isinstance(skip_type_key, str) + for skip_type_key in skip_type_keys + ]) + self._skip_type_keys = skip_type_keys + + self.pipeline = [] + self.pipeline_types = [] + for transform in pipeline: + if isinstance(transform, dict): + self.pipeline_types.append(transform['type']) + transform = TRANSFORMS.build(transform) + self.pipeline.append(transform) + else: + raise TypeError('pipeline must be a dict') + + self._metainfo = self.dataset.metainfo + self.num_samples = len(self.dataset) + + self._fully_initialized = False + if not lazy_init: + self.full_init() + + @property + def metainfo(self) -> dict: + """Get the meta information of the multi-image-mixed dataset. + + Returns: + dict: The meta information of multi-image-mixed dataset. + """ + return copy.deepcopy(self._metainfo) + + def full_init(self): + """Loop to ``full_init`` each dataset.""" + if self._fully_initialized: + return + + self.dataset.full_init() + self._ori_len = len(self.dataset) + self._fully_initialized = True + + @force_full_init + def get_data_info(self, idx: int) -> dict: + """Get annotation by index. + + Args: + idx (int): Global index of ``ConcatDataset``. + + Returns: + dict: The idx-th annotation of the datasets. + """ + return self.dataset.get_data_info(idx) + + @force_full_init + def __len__(self): + return self.num_samples + + def __getitem__(self, idx): + results = copy.deepcopy(self.dataset[idx]) + for (transform, transform_type) in zip(self.pipeline, + self.pipeline_types): + if self._skip_type_keys is not None and \ + transform_type in self._skip_type_keys: + continue + + if hasattr(transform, 'get_indices'): + indices = transform.get_indices(self.dataset) + if not isinstance(indices, collections.abc.Sequence): + indices = [indices] + mix_results = [ + copy.deepcopy(self.dataset[index]) for index in indices + ] + results['mix_results'] = mix_results + + results = transform(results) + + if 'mix_results' in results: + results.pop('mix_results') + + return results + + def update_skip_type_keys(self, skip_type_keys): + """Update skip_type_keys. + + It is called by an external hook. + + Args: + skip_type_keys (list[str], optional): Sequence of type + string to be skip pipeline. + """ + assert all([ + isinstance(skip_type_key, str) for skip_type_key in skip_type_keys + ]) + self._skip_type_keys = skip_type_keys diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/decathlon.py b/Seg_All_In_One_MMSeg/mmseg/datasets/decathlon.py new file mode 100644 index 0000000..26aa4ef --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/decathlon.py @@ -0,0 +1,96 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from typing import List + +from mmengine.fileio import load + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class DecathlonDataset(BaseSegDataset): + """Dataset for Dacathlon dataset. + + The dataset.json format is shown as follows + + .. code-block:: none + + { + "name": "BRATS", + "tensorImageSize": "4D", + "modality": + { + "0": "FLAIR", + "1": "T1w", + "2": "t1gd", + "3": "T2w" + }, + "labels": { + "0": "background", + "1": "edema", + "2": "non-enhancing tumor", + "3": "enhancing tumour" + }, + "numTraining": 484, + "numTest": 266, + "training": + [ + { + "image": "./imagesTr/BRATS_306.nii.gz" + "label": "./labelsTr/BRATS_306.nii.gz" + ... + } + ] + "test": + [ + "./imagesTs/BRATS_557.nii.gz" + ... + ] + } + """ + + def load_data_list(self) -> List[dict]: + """Load annotation from directory or annotation file. + + Returns: + list[dict]: All data info of dataset. + """ + # `self.ann_file` denotes the absolute annotation file path if + # `self.root=None` or relative path if `self.root=/path/to/data/`. + annotations = load(self.ann_file) + if not isinstance(annotations, dict): + raise TypeError(f'The annotations loaded from annotation file ' + f'should be a dict, but got {type(annotations)}!') + raw_data_list = annotations[ + 'training'] if not self.test_mode else annotations['test'] + data_list = [] + for raw_data_info in raw_data_list: + # `2:` works for removing './' in file path, which will break + # loading from cloud storage. + if isinstance(raw_data_info, dict): + data_info = dict( + img_path=osp.join(self.data_root, raw_data_info['image'] + [2:])) + data_info['seg_map_path'] = osp.join( + self.data_root, raw_data_info['label'][2:]) + else: + data_info = dict( + img_path=osp.join(self.data_root, raw_data_info)[2:]) + data_info['label_map'] = self.label_map + data_info['reduce_zero_label'] = self.reduce_zero_label + data_info['seg_fields'] = [] + data_list.append(data_info) + annotations.pop('training') + annotations.pop('test') + + metainfo = copy.deepcopy(annotations) + metainfo['classes'] = [*metainfo['labels'].values()] + # Meta information load from annotation file will not influence the + # existed meta information load from `BaseDataset.METAINFO` and + # `metainfo` arguments defined in constructor. + for k, v in metainfo.items(): + self._metainfo.setdefault(k, v) + + return data_list diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/drive.py b/Seg_All_In_One_MMSeg/mmseg/datasets/drive.py new file mode 100644 index 0000000..76c0160 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/drive.py @@ -0,0 +1,32 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class DRIVEDataset(BaseSegDataset): + """DRIVE dataset. + + In segmentation map annotation for DRIVE, 0 stands for background, which is + included in 2 categories. ``reduce_zero_label`` is fixed to False. The + ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is fixed to + '_manual1.png'. + """ + METAINFO = dict( + classes=('background', 'vessel'), + palette=[[120, 120, 120], [6, 230, 230]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='_manual1.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + assert fileio.exists( + self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/dsdl.py b/Seg_All_In_One_MMSeg/mmseg/datasets/dsdl.py new file mode 100644 index 0000000..bf7e4e6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/dsdl.py @@ -0,0 +1,116 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +from typing import Dict, List, Optional, Sequence, Union + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + +try: + from dsdl.dataset import DSDLDataset +except ImportError: + DSDLDataset = None + + +@DATASETS.register_module() +class DSDLSegDataset(BaseSegDataset): + """Dataset for dsdl segmentation. + + Args: + specific_key_path(dict): Path of specific key which can not + be loaded by it's field name. + pre_transform(dict): pre-transform functions before loading. + used_labels(sequence): list of actual used classes in train steps, + this must be subset of class domain. + """ + + METAINFO = {} + + def __init__(self, + specific_key_path: Dict = {}, + pre_transform: Dict = {}, + used_labels: Optional[Sequence] = None, + **kwargs) -> None: + + if DSDLDataset is None: + raise RuntimeError( + 'Package dsdl is not installed. Please run "pip install dsdl".' + ) + self.used_labels = used_labels + + loc_config = dict(type='LocalFileReader', working_dir='') + if kwargs.get('data_root'): + kwargs['ann_file'] = os.path.join(kwargs['data_root'], + kwargs['ann_file']) + required_fields = ['Image', 'LabelMap'] + + self.dsdldataset = DSDLDataset( + dsdl_yaml=kwargs['ann_file'], + location_config=loc_config, + required_fields=required_fields, + specific_key_path=specific_key_path, + transform=pre_transform, + ) + BaseSegDataset.__init__(self, **kwargs) + + def load_data_list(self) -> List[Dict]: + """Load data info from a dsdl yaml file named as ``self.ann_file`` + + Returns: + List[dict]: A list of data list. + """ + + if self.used_labels: + self._metainfo['classes'] = tuple(self.used_labels) + self.label_map = self.get_label_map(self.used_labels) + else: + self._metainfo['classes'] = tuple(['background'] + + self.dsdldataset.class_names) + data_list = [] + + for i, data in enumerate(self.dsdldataset): + datainfo = dict( + img_path=os.path.join(self.data_prefix['img_path'], + data['Image'][0].location), + seg_map_path=os.path.join(self.data_prefix['seg_map_path'], + data['LabelMap'][0].location), + label_map=self.label_map, + reduce_zero_label=self.reduce_zero_label, + seg_fields=[], + ) + data_list.append(datainfo) + + return data_list + + def get_label_map(self, + new_classes: Optional[Sequence] = None + ) -> Union[Dict, None]: + """Require label mapping. + + The ``label_map`` is a dictionary, its keys are the old label ids and + its values are the new label ids, and is used for changing pixel + labels in load_annotations. If and only if old classes in class_dom + is not equal to new classes in args and nether of them is not + None, `label_map` is not None. + Args: + new_classes (list, tuple, optional): The new classes name from + metainfo. Default to None. + Returns: + dict, optional: The mapping from old classes to new classes. + """ + old_classes = ['background'] + self.dsdldataset.class_names + if (new_classes is not None and old_classes is not None + and list(new_classes) != list(old_classes)): + + label_map = {} + if not set(new_classes).issubset(old_classes): + raise ValueError( + f'new classes {new_classes} is not a ' + f'subset of classes {old_classes} in class_dom.') + for i, c in enumerate(old_classes): + if c not in new_classes: + label_map[i] = 255 + else: + label_map[i] = new_classes.index(c) + return label_map + else: + return None diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/hrf.py b/Seg_All_In_One_MMSeg/mmseg/datasets/hrf.py new file mode 100644 index 0000000..fd669cc --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/hrf.py @@ -0,0 +1,32 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class HRFDataset(BaseSegDataset): + """HRF dataset. + + In segmentation map annotation for HRF, 0 stands for background, which is + included in 2 categories. ``reduce_zero_label`` is fixed to False. The + ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is fixed to + '.png'. + """ + METAINFO = dict( + classes=('background', 'vessel'), + palette=[[120, 120, 120], [6, 230, 230]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + assert fileio.exists( + self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/hsi_drive.py b/Seg_All_In_One_MMSeg/mmseg/datasets/hsi_drive.py new file mode 100644 index 0000000..3d46a86 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/hsi_drive.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + +classes_exp = ('unlabelled', 'road', 'road marks', 'vegetation', + 'painted metal', 'sky', 'concrete', 'pedestrian', 'water', + 'unpainted metal', 'glass') +palette_exp = [[0, 0, 0], [77, 77, 77], [255, 255, 255], [0, 255, 0], + [255, 0, 0], [0, 0, 255], [102, 51, 0], [255, 255, 0], + [0, 207, 250], [255, 166, 0], [0, 204, 204]] + + +@DATASETS.register_module() +class HSIDrive20Dataset(BaseSegDataset): + """HSI-Drive v2.0 (https://ieeexplore.ieee.org/document/10371793), the + updated version of HSI-Drive + (https://ieeexplore.ieee.org/document/9575298), is a structured dataset for + the research and development of automated driving systems (ADS) supported + by hyperspectral imaging (HSI). It contains per-pixel manually annotated + images selected from videos recorded in real driving conditions and has + been organized according to four parameters: season, daytime, road type, + and weather conditions. + + The video sequences have been captured with a small-size 25-band VNIR + (Visible-NearlnfraRed) snapshot hyperspectral camera mounted on a driving + automobile. As a consequence, you need to modify the in_channels parameter + of your model from 3 (RGB images) to 25 (HSI images) as it is done in + configs/unet/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py + + Apart from the abovementioned articles, additional information is provided + in the website (https://ipaccess.ehu.eus/HSI-Drive/) from where you can + download the dataset and also visualize some examples of segmented videos. + """ + + METAINFO = dict(classes=classes_exp, palette=palette_exp) + + def __init__(self, + img_suffix='.npy', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/isaid.py b/Seg_All_In_One_MMSeg/mmseg/datasets/isaid.py new file mode 100644 index 0000000..61942ec --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/isaid.py @@ -0,0 +1,39 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class iSAIDDataset(BaseSegDataset): + """ iSAID: A Large-scale Dataset for Instance Segmentation in Aerial Images + In segmentation map annotation for iSAID dataset, which is included + in 16 categories. ``reduce_zero_label`` is fixed to False. The + ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is fixed to + '_manual1.png'. + """ + + METAINFO = dict( + classes=('background', 'ship', 'store_tank', 'baseball_diamond', + 'tennis_court', 'basketball_court', 'Ground_Track_Field', + 'Bridge', 'Large_Vehicle', 'Small_Vehicle', 'Helicopter', + 'Swimming_pool', 'Roundabout', 'Soccer_ball_field', 'plane', + 'Harbor'), + palette=[[0, 0, 0], [0, 0, 63], [0, 63, 63], [0, 63, 0], [0, 63, 127], + [0, 63, 191], [0, 63, 255], [0, 127, 63], [0, 127, 127], + [0, 0, 127], [0, 0, 191], [0, 0, 255], [0, 191, 127], + [0, 127, 191], [0, 127, 255], [0, 100, 155]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='_instance_color_RGB.png', + ignore_index=255, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + ignore_index=ignore_index, + **kwargs) + assert fileio.exists( + self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/isprs.py b/Seg_All_In_One_MMSeg/mmseg/datasets/isprs.py new file mode 100644 index 0000000..30af53c --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/isprs.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class ISPRSDataset(BaseSegDataset): + """ISPRS dataset. + + In segmentation map annotation for ISPRS, 0 is the ignore index. + ``reduce_zero_label`` should be set to True. The ``img_suffix`` and + ``seg_map_suffix`` are both fixed to '.png'. + """ + METAINFO = dict( + classes=('impervious_surface', 'building', 'low_vegetation', 'tree', + 'car', 'clutter'), + palette=[[255, 255, 255], [0, 0, 255], [0, 255, 255], [0, 255, 0], + [255, 255, 0], [255, 0, 0]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=True, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/levir.py b/Seg_All_In_One_MMSeg/mmseg/datasets/levir.py new file mode 100644 index 0000000..f467481 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/levir.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +from mmseg.registry import DATASETS +from .basesegdataset import BaseCDDataset + + +@DATASETS.register_module() +class LEVIRCDDataset(BaseCDDataset): + """ISPRS dataset. + + In segmentation map annotation for ISPRS, 0 is to ignore index. + ``reduce_zero_label`` should be set to True. The ``img_suffix`` and + ``seg_map_suffix`` are both fixed to '.png'. + """ + + METAINFO = dict( + classes=('background', 'changed'), + palette=[[0, 0, 0], [255, 255, 255]]) + + def __init__(self, + img_suffix='.png', + img_suffix2='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + img_suffix2=img_suffix2, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/lip.py b/Seg_All_In_One_MMSeg/mmseg/datasets/lip.py new file mode 100644 index 0000000..3a32a19 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/lip.py @@ -0,0 +1,47 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class LIPDataset(BaseSegDataset): + """LIP dataset. + + The ``img_suffix`` is fixed to '.jpg' and ``seg_map_suffix`` is fixed to + '.png'. + """ + METAINFO = dict( + classes=('Background', 'Hat', 'Hair', 'Glove', 'Sunglasses', + 'UpperClothes', 'Dress', 'Coat', 'Socks', 'Pants', + 'Jumpsuits', 'Scarf', 'Skirt', 'Face', 'Left-arm', + 'Right-arm', 'Left-leg', 'Right-leg', 'Left-shoe', + 'Right-shoe'), + palette=( + [0, 0, 0], + [128, 0, 0], + [255, 0, 0], + [0, 85, 0], + [170, 0, 51], + [255, 85, 0], + [0, 0, 85], + [0, 119, 221], + [85, 85, 0], + [0, 85, 85], + [85, 51, 0], + [52, 86, 128], + [0, 128, 0], + [0, 0, 255], + [51, 170, 221], + [0, 255, 255], + [85, 255, 170], + [170, 255, 85], + [255, 255, 0], + [255, 170, 0], + )) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/loveda.py b/Seg_All_In_One_MMSeg/mmseg/datasets/loveda.py new file mode 100644 index 0000000..5c16db5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/loveda.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class LoveDADataset(BaseSegDataset): + """LoveDA dataset. + + In segmentation map annotation for LoveDA, 0 is the ignore index. + ``reduce_zero_label`` should be set to True. The ``img_suffix`` and + ``seg_map_suffix`` are both fixed to '.png'. + """ + METAINFO = dict( + classes=('background', 'building', 'road', 'water', 'barren', 'forest', + 'agricultural'), + palette=[[255, 255, 255], [255, 0, 0], [255, 255, 0], [0, 0, 255], + [159, 129, 183], [0, 255, 0], [255, 195, 128]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=True, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/mapillary.py b/Seg_All_In_One_MMSeg/mmseg/datasets/mapillary.py new file mode 100644 index 0000000..6c29473 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/mapillary.py @@ -0,0 +1,176 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class MapillaryDataset_v1(BaseSegDataset): + """Mapillary Vistas Dataset. + + Dataset paper link: + http://ieeexplore.ieee.org/document/8237796/ + + v1.2 contain 66 object classes. + (37 instance-specific) + + v2.0 contain 124 object classes. + (70 instance-specific, 46 stuff, 8 void or crowd). + + The ``img_suffix`` is fixed to '.jpg' and ``seg_map_suffix`` is + fixed to '.png' for Mapillary Vistas Dataset. + """ + METAINFO = dict( + classes=('Bird', 'Ground Animal', 'Curb', 'Fence', 'Guard Rail', + 'Barrier', 'Wall', 'Bike Lane', 'Crosswalk - Plain', + 'Curb Cut', 'Parking', 'Pedestrian Area', 'Rail Track', + 'Road', 'Service Lane', 'Sidewalk', 'Bridge', 'Building', + 'Tunnel', 'Person', 'Bicyclist', 'Motorcyclist', + 'Other Rider', 'Lane Marking - Crosswalk', + 'Lane Marking - General', 'Mountain', 'Sand', 'Sky', 'Snow', + 'Terrain', 'Vegetation', 'Water', 'Banner', 'Bench', + 'Bike Rack', 'Billboard', 'Catch Basin', 'CCTV Camera', + 'Fire Hydrant', 'Junction Box', 'Mailbox', 'Manhole', + 'Phone Booth', 'Pothole', 'Street Light', 'Pole', + 'Traffic Sign Frame', 'Utility Pole', 'Traffic Light', + 'Traffic Sign (Back)', 'Traffic Sign (Front)', 'Trash Can', + 'Bicycle', 'Boat', 'Bus', 'Car', 'Caravan', 'Motorcycle', + 'On Rails', 'Other Vehicle', 'Trailer', 'Truck', + 'Wheeled Slow', 'Car Mount', 'Ego Vehicle', 'Unlabeled'), + palette=[[165, 42, 42], [0, 192, 0], [196, 196, 196], [190, 153, 153], + [180, 165, 180], [90, 120, 150], [102, 102, 156], + [128, 64, 255], [140, 140, 200], [170, 170, 170], + [250, 170, 160], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], + [244, 35, 232], [150, 100, 100], [70, 70, 70], [150, 120, 90], + [220, 20, 60], [255, 0, 0], [255, 0, 100], [255, 0, 200], + [200, 128, 128], [255, 255, 255], [64, 170, + 64], [230, 160, 50], + [70, 130, 180], [190, 255, 255], [152, 251, 152], + [107, 142, 35], [0, 170, 30], [255, 255, 128], [250, 0, 30], + [100, 140, 180], [220, 220, 220], [220, 128, 128], + [222, 40, 40], [100, 170, 30], [40, 40, 40], [33, 33, 33], + [100, 128, 160], [142, 0, 0], [70, 100, 150], [210, 170, 100], + [153, 153, 153], [128, 128, 128], [0, 0, 80], [250, 170, 30], + [192, 192, 192], [220, 220, 0], [140, 140, 20], [119, 11, 32], + [150, 0, 255], [0, 60, 100], [0, 0, 142], [0, 0, 90], + [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 192], [32, 32, 32], [120, 10, + 10], [0, 0, 0]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) + + +@DATASETS.register_module() +class MapillaryDataset_v2(BaseSegDataset): + """Mapillary Vistas Dataset. + + Dataset paper link: + http://ieeexplore.ieee.org/document/8237796/ + + v1.2 contain 66 object classes. + (37 instance-specific) + + v2.0 contain 124 object classes. + (70 instance-specific, 46 stuff, 8 void or crowd). + + The ``img_suffix`` is fixed to '.jpg' and ``seg_map_suffix`` is + fixed to '.png' for Mapillary Vistas Dataset. + """ + METAINFO = dict( + classes=( + 'Bird', 'Ground Animal', 'Ambiguous Barrier', 'Concrete Block', + 'Curb', 'Fence', 'Guard Rail', 'Barrier', 'Road Median', + 'Road Side', 'Lane Separator', 'Temporary Barrier', 'Wall', + 'Bike Lane', 'Crosswalk - Plain', 'Curb Cut', 'Driveway', + 'Parking', 'Parking Aisle', 'Pedestrian Area', 'Rail Track', + 'Road', 'Road Shoulder', 'Service Lane', 'Sidewalk', + 'Traffic Island', 'Bridge', 'Building', 'Garage', 'Tunnel', + 'Person', 'Person Group', 'Bicyclist', 'Motorcyclist', + 'Other Rider', 'Lane Marking - Dashed Line', + 'Lane Marking - Straight Line', 'Lane Marking - Zigzag Line', + 'Lane Marking - Ambiguous', 'Lane Marking - Arrow (Left)', + 'Lane Marking - Arrow (Other)', 'Lane Marking - Arrow (Right)', + 'Lane Marking - Arrow (Split Left or Straight)', + 'Lane Marking - Arrow (Split Right or Straight)', + 'Lane Marking - Arrow (Straight)', 'Lane Marking - Crosswalk', + 'Lane Marking - Give Way (Row)', + 'Lane Marking - Give Way (Single)', + 'Lane Marking - Hatched (Chevron)', + 'Lane Marking - Hatched (Diagonal)', 'Lane Marking - Other', + 'Lane Marking - Stop Line', 'Lane Marking - Symbol (Bicycle)', + 'Lane Marking - Symbol (Other)', 'Lane Marking - Text', + 'Lane Marking (only) - Dashed Line', + 'Lane Marking (only) - Crosswalk', 'Lane Marking (only) - Other', + 'Lane Marking (only) - Test', 'Mountain', 'Sand', 'Sky', 'Snow', + 'Terrain', 'Vegetation', 'Water', 'Banner', 'Bench', 'Bike Rack', + 'Catch Basin', 'CCTV Camera', 'Fire Hydrant', 'Junction Box', + 'Mailbox', 'Manhole', 'Parking Meter', 'Phone Booth', 'Pothole', + 'Signage - Advertisement', 'Signage - Ambiguous', 'Signage - Back', + 'Signage - Information', 'Signage - Other', 'Signage - Store', + 'Street Light', 'Pole', 'Pole Group', 'Traffic Sign Frame', + 'Utility Pole', 'Traffic Cone', 'Traffic Light - General (Single)', + 'Traffic Light - Pedestrians', 'Traffic Light - General (Upright)', + 'Traffic Light - General (Horizontal)', 'Traffic Light - Cyclists', + 'Traffic Light - Other', 'Traffic Sign - Ambiguous', + 'Traffic Sign (Back)', 'Traffic Sign - Direction (Back)', + 'Traffic Sign - Direction (Front)', 'Traffic Sign (Front)', + 'Traffic Sign - Parking', 'Traffic Sign - Temporary (Back)', + 'Traffic Sign - Temporary (Front)', 'Trash Can', 'Bicycle', 'Boat', + 'Bus', 'Car', 'Caravan', 'Motorcycle', 'On Rails', 'Other Vehicle', + 'Trailer', 'Truck', 'Vehicle Group', 'Wheeled Slow', 'Water Valve', + 'Car Mount', 'Dynamic', 'Ego Vehicle', 'Ground', 'Static', + 'Unlabeled'), + palette=[[165, 42, 42], [0, 192, 0], [250, 170, 31], [250, 170, 32], + [196, 196, 196], [190, 153, 153], [180, 165, 180], + [90, 120, 150], [250, 170, 33], [250, 170, 34], + [128, 128, 128], [250, 170, 35], [102, 102, 156], + [128, 64, 255], [140, 140, 200], [170, 170, 170], + [250, 170, 36], [250, 170, 160], [250, 170, 37], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], + [110, 110, 110], [244, 35, 232], [128, 196, + 128], [150, 100, 100], + [70, 70, 70], [150, 150, 150], [150, 120, 90], [220, 20, 60], + [220, 20, 60], [255, 0, 0], [255, 0, 100], [255, 0, 200], + [255, 255, 255], [255, 255, 255], [250, 170, 29], + [250, 170, 28], [250, 170, 26], [250, 170, + 25], [250, 170, 24], + [250, 170, 22], [250, 170, 21], [250, 170, + 20], [255, 255, 255], + [250, 170, 19], [250, 170, 18], [250, 170, + 12], [250, 170, 11], + [255, 255, 255], [255, 255, 255], [250, 170, 16], + [250, 170, 15], [250, 170, 15], [255, 255, 255], + [255, 255, 255], [255, 255, 255], [255, 255, 255], + [64, 170, 64], [230, 160, 50], + [70, 130, 180], [190, 255, 255], [152, 251, 152], + [107, 142, 35], [0, 170, 30], [255, 255, 128], [250, 0, 30], + [100, 140, 180], [220, 128, 128], [222, 40, + 40], [100, 170, 30], + [40, 40, 40], [33, 33, 33], [100, 128, 160], [20, 20, 255], + [142, 0, 0], [70, 100, 150], [250, 171, 30], [250, 172, 30], + [250, 173, 30], [250, 174, 30], [250, 175, + 30], [250, 176, 30], + [210, 170, 100], [153, 153, 153], [153, 153, 153], + [128, 128, 128], [0, 0, 80], [210, 60, 60], [250, 170, 30], + [250, 170, 30], [250, 170, 30], [250, 170, + 30], [250, 170, 30], + [250, 170, 30], [192, 192, 192], [192, 192, 192], + [192, 192, 192], [220, 220, 0], [220, 220, 0], [0, 0, 196], + [192, 192, 192], [220, 220, 0], [140, 140, 20], [119, 11, 32], + [150, 0, 255], [0, 60, 100], [0, 0, 142], [0, 0, 90], + [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 142], [0, 0, 192], [170, 170, 170], + [32, 32, 32], [111, 74, 0], [120, 10, 10], [81, 0, 81], + [111, 111, 0], [0, 0, 0]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/my_dataset.py b/Seg_All_In_One_MMSeg/mmseg/datasets/my_dataset.py new file mode 100644 index 0000000..f03ac3c --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/my_dataset.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class MyDataset(BaseSegDataset): # 表示你定义的数据的名字,顺便取一个名字即可 + """MyDataset dataset. + """ + METAINFO = dict( + classes=['背景', '肝脏', '胆囊', '分离钳', '止血海绵', '肝总管', '胆总管', '吸引器', '剪刀', '止血纱布', '生物夹', '无损伤钳', '喷洒', '胆囊管', '胆囊动脉', '电凝', '标本袋', '引流管', '纱布', '金属钛夹', '术中超声', '吻合器', '乳胶管', '推结器', '肝带', '钳夹', '超声刀', '脂肪', '双极电凝', '棉球', '血管阻断夹', '肿瘤', '针', '线', '韧带', '胆囊静脉'], # 背景最好放到第一个 + palette=[[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233], [52, 184, 178], [90, 120, 41], [255, 0, 0], [177, 0, 0], [167, 24, 233], [112, 113, 150], [0, 255, 0], [255, 255, 255], [0, 255, 255], [138, 251, 213], [136, 162, 196], [197, 83, 181], [202, 202, 200], [113, 102, 140], [66, 115, 82], [240, 16, 116], [155, 132, 0], [155, 62, 0], [146, 175, 236], [255, 172, 159], [245, 161, 0], [134, 124, 118], [0, 157, 142], [181, 85, 105], [42, 8, 66]]) # TODO 标注类型和颜色 + + def __init__(self, + img_suffix='.png', # TODO mask图像类型 + seg_map_suffix='_gtFine_labelTrainIds.png', # TODO mask图像后缀 + reduce_zero_label=False, # TODO 在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + + # assert fileio.exists( + # self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/my_dataset_model.py b/Seg_All_In_One_MMSeg/mmseg/datasets/my_dataset_model.py new file mode 100644 index 0000000..c20a508 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/my_dataset_model.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class MyDataset_model(BaseSegDataset): # 表示你定义的数据的名字,顺便取一个名字即可 + """MyDataset_model dataset. + """ + METAINFO = dict( + classes=['背景', '肝脏', '胆囊', '分离钳', '止血海绵', '肝总管', '胆总管', '吸引器', '剪刀', '止血纱布', '生物夹', '无损伤钳', '喷洒', '胆囊管', '胆囊动脉', '电凝', '标本袋', '引流管', '纱布', '金属钛夹', '术中超声', '吻合器', '乳胶管', '推结器', '肝带', '钳夹', '超声刀', '脂肪', '双极电凝', '棉球', '血管阻断夹', '肿瘤', '针', '线', '韧带', '胆囊静脉'], # 背景最好放到第一个 + palette=[[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233], [52, 184, 178], [90, 120, 41], [255, 0, 0], [177, 0, 0], [167, 24, 233], [112, 113, 150], [0, 255, 0], [255, 255, 255], [0, 255, 255], [138, 251, 213], [136, 162, 196], [197, 83, 181], [202, 202, 200], [113, 102, 140], [66, 115, 82], [240, 16, 116], [155, 132, 0], [155, 62, 0], [146, 175, 236], [255, 172, 159], [245, 161, 0], [134, 124, 118], [0, 157, 142], [181, 85, 105], [42, 8, 66]]) # TODO 标注类型和颜色 + + def __init__(self, + img_suffix='.png', # TODO mask图像类型 + seg_map_suffix='_gtFine_labelTrainIds.png', # TODO mask图像后缀 + reduce_zero_label=False, # TODO 在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + + # assert fileio.exists( + # self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/night_driving.py b/Seg_All_In_One_MMSeg/mmseg/datasets/night_driving.py new file mode 100644 index 0000000..3ead91e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/night_driving.py @@ -0,0 +1,15 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .cityscapes import CityscapesDataset + + +@DATASETS.register_module() +class NightDrivingDataset(CityscapesDataset): + """NightDrivingDataset dataset.""" + + def __init__(self, + img_suffix='_leftImg8bit.png', + seg_map_suffix='_gtCoarse_labelTrainIds.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/nyu.py b/Seg_All_In_One_MMSeg/mmseg/datasets/nyu.py new file mode 100644 index 0000000..fcfda46 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/nyu.py @@ -0,0 +1,123 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from typing import List + +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class NYUDataset(BaseSegDataset): + """NYU depth estimation dataset. The file structure should be. + + .. code-block:: none + + ├── data + │ ├── nyu + │ │ ├── images + │ │ │ ├── train + │ │ │ │ ├── scene_xxx.jpg + │ │ │ │ ├── ... + │ │ │ ├── test + │ │ ├── annotations + │ │ │ ├── train + │ │ │ │ ├── scene_xxx.png + │ │ │ │ ├── ... + │ │ │ ├── test + + Args: + ann_file (str): Annotation file path. Defaults to ''. + metainfo (dict, optional): Meta information for dataset, such as + specify classes to load. Defaults to None. + data_root (str, optional): The root directory for ``data_prefix`` and + ``ann_file``. Defaults to None. + data_prefix (dict, optional): Prefix for training data. Defaults to + dict(img_path='images', depth_map_path='annotations'). + img_suffix (str): Suffix of images. Default: '.jpg' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + filter_cfg (dict, optional): Config for filter data. Defaults to None. + indices (int or Sequence[int], optional): Support using first few + data in annotation file to facilitate training/testing on a smaller + dataset. Defaults to None which means using all ``data_infos``. + serialize_data (bool, optional): Whether to hold memory using + serialized objects, when enabled, data loader workers can use + shared RAM from master process instead of making a copy. Defaults + to True. + pipeline (list, optional): Processing pipeline. Defaults to []. + test_mode (bool, optional): ``test_mode=True`` means in test phase. + Defaults to False. + lazy_init (bool, optional): Whether to load annotation during + instantiation. In some cases, such as visualization, only the meta + information of the dataset is needed, which is not necessary to + load annotation file. ``Basedataset`` can skip load annotations to + save time by set ``lazy_init=True``. Defaults to False. + max_refetch (int, optional): If ``Basedataset.prepare_data`` get a + None img. The maximum extra number of cycles to get a valid + image. Defaults to 1000. + ignore_index (int): The label index to be ignored. Default: 255 + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See https://mmengine.readthedocs.io/en/latest/api/fileio.htm + for details. Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + METAINFO = dict( + classes=('printer_room', 'bathroom', 'living_room', 'study', + 'conference_room', 'study_room', 'kitchen', 'home_office', + 'bedroom', 'dinette', 'playroom', 'indoor_balcony', + 'laundry_room', 'basement', 'excercise_room', 'foyer', + 'home_storage', 'cafe', 'furniture_store', 'office_kitchen', + 'student_lounge', 'dining_room', 'reception_room', + 'computer_lab', 'classroom', 'office', 'bookstore')) + + def __init__(self, + data_prefix=dict( + img_path='images', depth_map_path='annotations'), + img_suffix='.jpg', + depth_map_suffix='.png', + **kwargs) -> None: + super().__init__( + data_prefix=data_prefix, + img_suffix=img_suffix, + seg_map_suffix=depth_map_suffix, + **kwargs) + + def _get_category_id_from_filename(self, image_fname: str) -> int: + """Retrieve the category ID from the given image filename.""" + image_fname = osp.basename(image_fname) + position = image_fname.find(next(filter(str.isdigit, image_fname)), 0) + categoty_name = image_fname[:position - 1] + if categoty_name not in self._metainfo['classes']: + return -1 + else: + return self._metainfo['classes'].index(categoty_name) + + def load_data_list(self) -> List[dict]: + """Load annotation from directory or annotation file. + + Returns: + list[dict]: All data info of dataset. + """ + data_list = [] + img_dir = self.data_prefix.get('img_path', None) + ann_dir = self.data_prefix.get('depth_map_path', None) + + _suffix_len = len(self.img_suffix) + for img in fileio.list_dir_or_file( + dir_path=img_dir, + list_dir=False, + suffix=self.img_suffix, + recursive=True, + backend_args=self.backend_args): + data_info = dict(img_path=osp.join(img_dir, img)) + if ann_dir is not None: + depth_map = img[:-_suffix_len] + self.seg_map_suffix + data_info['depth_map_path'] = osp.join(ann_dir, depth_map) + data_info['seg_fields'] = [] + data_info['category_id'] = self._get_category_id_from_filename(img) + data_list.append(data_info) + data_list = sorted(data_list, key=lambda x: x['img_path']) + return data_list diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/pascal_context.py b/Seg_All_In_One_MMSeg/mmseg/datasets/pascal_context.py new file mode 100644 index 0000000..82d00a9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/pascal_context.py @@ -0,0 +1,116 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class PascalContextDataset(BaseSegDataset): + """PascalContext dataset. + + In segmentation map annotation for PascalContext, 0 stands for background, + which is included in 60 categories. ``reduce_zero_label`` is fixed to + False. The ``img_suffix`` is fixed to '.jpg' and ``seg_map_suffix`` is + fixed to '.png'. + + Args: + ann_file (str): Annotation file path. + """ + + METAINFO = dict( + classes=('background', 'aeroplane', 'bag', 'bed', 'bedclothes', + 'bench', 'bicycle', 'bird', 'boat', 'book', 'bottle', + 'building', 'bus', 'cabinet', 'car', 'cat', 'ceiling', + 'chair', 'cloth', 'computer', 'cow', 'cup', 'curtain', 'dog', + 'door', 'fence', 'floor', 'flower', 'food', 'grass', 'ground', + 'horse', 'keyboard', 'light', 'motorbike', 'mountain', + 'mouse', 'person', 'plate', 'platform', 'pottedplant', 'road', + 'rock', 'sheep', 'shelves', 'sidewalk', 'sign', 'sky', 'snow', + 'sofa', 'table', 'track', 'train', 'tree', 'truck', + 'tvmonitor', 'wall', 'water', 'window', 'wood'), + palette=[[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50], + [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255], + [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7], + [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82], + [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3], + [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255], + [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220], + [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224], + [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255], + [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7], + [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153], + [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255], + [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0], + [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255], + [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255]]) + + def __init__(self, + ann_file='', + img_suffix='.jpg', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + ann_file=ann_file, + reduce_zero_label=reduce_zero_label, + **kwargs) + assert fileio.exists(self.data_prefix['img_path'], self.backend_args) + + +@DATASETS.register_module() +class PascalContextDataset59(BaseSegDataset): + """PascalContext dataset. + + In segmentation map annotation for PascalContext, 0 stands for background, + which is included in 60 categories. ``reduce_zero_label`` is fixed to + True. The ``img_suffix`` is fixed to '.jpg' and ``seg_map_suffix`` is + fixed to '.png'. + Noted: If the background is 255 and the ids of categories are from 0 to 58, + ``reduce_zero_label`` needs to be set to False. + + Args: + ann_file (str): Annotation file path. + """ + METAINFO = dict( + classes=('aeroplane', 'bag', 'bed', 'bedclothes', 'bench', 'bicycle', + 'bird', 'boat', 'book', 'bottle', 'building', 'bus', + 'cabinet', 'car', 'cat', 'ceiling', 'chair', 'cloth', + 'computer', 'cow', 'cup', 'curtain', 'dog', 'door', 'fence', + 'floor', 'flower', 'food', 'grass', 'ground', 'horse', + 'keyboard', 'light', 'motorbike', 'mountain', 'mouse', + 'person', 'plate', 'platform', 'pottedplant', 'road', 'rock', + 'sheep', 'shelves', 'sidewalk', 'sign', 'sky', 'snow', 'sofa', + 'table', 'track', 'train', 'tree', 'truck', 'tvmonitor', + 'wall', 'water', 'window', 'wood'), + palette=[[180, 120, 120], [6, 230, 230], [80, 50, 50], [4, 200, 3], + [120, 120, 80], [140, 140, 140], [204, 5, 255], + [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7], + [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82], + [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3], + [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255], + [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220], + [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224], + [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255], + [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7], + [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153], + [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255], + [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0], + [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255], + [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255]]) + + def __init__(self, + ann_file='', + img_suffix='.jpg', + seg_map_suffix='.png', + reduce_zero_label=True, + **kwargs): + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + ann_file=ann_file, + reduce_zero_label=reduce_zero_label, + **kwargs) + assert fileio.exists(self.data_prefix['img_path'], self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/potsdam.py b/Seg_All_In_One_MMSeg/mmseg/datasets/potsdam.py new file mode 100644 index 0000000..6892de3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/potsdam.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class PotsdamDataset(BaseSegDataset): + """ISPRS Potsdam dataset. + + In segmentation map annotation for Potsdam dataset, 0 is the ignore index. + ``reduce_zero_label`` should be set to True. The ``img_suffix`` and + ``seg_map_suffix`` are both fixed to '.png'. + """ + METAINFO = dict( + classes=('impervious_surface', 'building', 'low_vegetation', 'tree', + 'car', 'clutter'), + palette=[[255, 255, 255], [0, 0, 255], [0, 255, 255], [0, 255, 0], + [255, 255, 0], [255, 0, 0]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=True, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_autolaparo.py b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_autolaparo.py new file mode 100644 index 0000000..c7f5c45 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_autolaparo.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class PublicDataSet_AutoLaparo(BaseSegDataset): # 表示你定义的数据的名字,顺便取一个名字即可 + """PublicDataSet_AutoLaparo dataset. + """ + METAINFO = dict( + classes=['背景', '1', '2', '3', '4', '5', '6', '7', '8', '9'], # 背景最好放到第一个 + palette=[[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95]]) # TODO 标注类型和颜色 + + def __init__(self, + img_suffix='.png', # TODO mask图像类型 + seg_map_suffix='.png', # TODO mask图像后缀 + reduce_zero_label=False, # TODO 在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + + # assert fileio.exists( + # self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_cholecseg8k.py b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_cholecseg8k.py new file mode 100644 index 0000000..6c5da02 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_cholecseg8k.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class PublicDataSet_CholecSeg8k(BaseSegDataset): # 表示你定义的数据的名字,顺便取一个名字即可 + """PublicDataSet_CholecSeg8k dataset. + """ + METAINFO = dict( + classes=['背景', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], # 背景最好放到第一个 + palette=[[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233], [52, 184, 178], [90, 120, 41]]) # TODO 标注类型和颜色 + + def __init__(self, + img_suffix='.png', # TODO mask图像类型 + seg_map_suffix='.png', # TODO mask图像后缀 + reduce_zero_label=False, # TODO 在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + + # assert fileio.exists( + # self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_dresden.py b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_dresden.py new file mode 100644 index 0000000..fdd8606 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_dresden.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class PublicDataSet_Dresden(BaseSegDataset): # 表示你定义的数据的名字,顺便取一个名字即可 + """PublicDataSet_Dresden dataset. + """ + METAINFO = dict( + classes=['背景', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], # 背景最好放到第一个 + palette=[[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233]]) # TODO 标注类型和颜色 + + def __init__(self, + img_suffix='.png', # TODO mask图像类型 + seg_map_suffix='.png', # TODO mask图像后缀 + reduce_zero_label=False, # TODO 在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + + # assert fileio.exists( + # self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_endovis_2017.py b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_endovis_2017.py new file mode 100644 index 0000000..bf305b2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_endovis_2017.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class PublicDataSet_Endovis_2017(BaseSegDataset): # 表示你定义的数据的名字,顺便取一个名字即可 + """PublicDataSet_Endovis_2017 dataset. + """ + METAINFO = dict( + classes=['背景', '1', '2', '3', '4', '5', '6', '7'], # 背景最好放到第一个 + palette=[[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255]]) # TODO 标注类型和颜色 + + def __init__(self, + img_suffix='.bmp', # TODO mask图像类型 + seg_map_suffix='.bmp', # TODO mask图像后缀 + reduce_zero_label=False, # TODO 在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + + # assert fileio.exists( + # self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_endovis_2018.py b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_endovis_2018.py new file mode 100644 index 0000000..e53d649 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/publicdataset_endovis_2018.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class PublicDataSet_Endovis_2018(BaseSegDataset): # 表示你定义的数据的名字,顺便取一个名字即可 + """PublicDataSet_Endovis_2018 dataset. + """ + METAINFO = dict( + classes=['背景', '1', '2', '3', '4', '5', '6', '7'], # 背景最好放到第一个 + palette=[[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255]]) # TODO 标注类型和颜色 + + def __init__(self, + img_suffix='.bmp', # TODO mask图像类型 + seg_map_suffix='.bmp', # TODO mask图像后缀 + reduce_zero_label=False, # TODO 在第 0 类为无意义黑边时,使用reduce_zero_label = True将其和待分类内容分开;在第 0 类为 background 类别的数据集上,如果您最终是需要将背景和您的其余类别分开时,是不需要使用reduce_zero_label的 【reduce_zero_label = False】 + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + + # assert fileio.exists( + # self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/refuge.py b/Seg_All_In_One_MMSeg/mmseg/datasets/refuge.py new file mode 100644 index 0000000..4016a82 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/refuge.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class REFUGEDataset(BaseSegDataset): + """REFUGE dataset. + + In segmentation map annotation for REFUGE, 0 stands for background, which + is not included in 2 categories. ``reduce_zero_label`` is fixed to True. + The ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is fixed to + '.png'. + """ + METAINFO = dict( + classes=('background', ' Optic Cup', 'Optic Disc'), + palette=[[120, 120, 120], [6, 230, 230], [56, 59, 120]]) + + def __init__(self, **kwargs) -> None: + super().__init__( + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) + assert fileio.exists( + self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/stare.py b/Seg_All_In_One_MMSeg/mmseg/datasets/stare.py new file mode 100644 index 0000000..1b997bb --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/stare.py @@ -0,0 +1,32 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class STAREDataset(BaseSegDataset): + """STARE dataset. + + In segmentation map annotation for STARE, 0 stands for background, which is + included in 2 categories. ``reduce_zero_label`` is fixed to False. The + ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is fixed to + '.ah.png'. + """ + METAINFO = dict( + classes=('background', 'vessel'), + palette=[[120, 120, 120], [6, 230, 230]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.ah.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) + assert fileio.exists( + self.data_prefix['img_path'], backend_args=self.backend_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/synapse.py b/Seg_All_In_One_MMSeg/mmseg/datasets/synapse.py new file mode 100644 index 0000000..6f83b64 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/synapse.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class SynapseDataset(BaseSegDataset): + """Synapse dataset. + + Before dataset preprocess of Synapse, there are total 13 categories of + foreground which does not include background. After preprocessing, 8 + foreground categories are kept while the other 5 foreground categories are + handled as background. The ``img_suffix`` is fixed to '.jpg' and + ``seg_map_suffix`` is fixed to '.png'. + """ + METAINFO = dict( + classes=('background', 'aorta', 'gallbladder', 'left_kidney', + 'right_kidney', 'liver', 'pancreas', 'spleen', 'stomach'), + palette=[[0, 0, 0], [0, 0, 255], [0, 255, 0], [255, 0, 0], + [0, 255, 255], [255, 0, 255], [255, 255, 0], [60, 255, 255], + [240, 240, 240]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/__init__.py b/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/__init__.py new file mode 100644 index 0000000..125f070 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/__init__.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .formatting import PackSegInputs +from .loading import (LoadAnnotations, LoadBiomedicalAnnotation, + LoadBiomedicalData, LoadBiomedicalImageFromFile, + LoadDepthAnnotation, LoadImageFromNDArray, + LoadMultipleRSImageFromFile, LoadSingleRSImageFromFile) +# yapf: disable +from .transforms import (CLAHE, AdjustGamma, Albu, BioMedical3DPad, + BioMedical3DRandomCrop, BioMedical3DRandomFlip, + BioMedicalGaussianBlur, BioMedicalGaussianNoise, + BioMedicalRandomGamma, ConcatCDInput, GenerateEdge, + PhotoMetricDistortion, RandomCrop, RandomCutOut, + RandomDepthMix, RandomFlip, RandomMosaic, + RandomRotate, RandomRotFlip, Rerange, Resize, + ResizeShortestEdge, ResizeToMultiple, RGB2Gray, + SegRescale) + +# yapf: enable +__all__ = [ + 'LoadAnnotations', 'RandomCrop', 'BioMedical3DRandomCrop', 'SegRescale', + 'PhotoMetricDistortion', 'RandomRotate', 'AdjustGamma', 'CLAHE', 'Rerange', + 'RGB2Gray', 'RandomCutOut', 'RandomMosaic', 'PackSegInputs', + 'ResizeToMultiple', 'LoadImageFromNDArray', 'LoadBiomedicalImageFromFile', + 'LoadBiomedicalAnnotation', 'LoadBiomedicalData', 'GenerateEdge', + 'ResizeShortestEdge', 'BioMedicalGaussianNoise', 'BioMedicalGaussianBlur', + 'BioMedical3DRandomFlip', 'BioMedicalRandomGamma', 'BioMedical3DPad', + 'RandomRotFlip', 'Albu', 'LoadSingleRSImageFromFile', 'ConcatCDInput', + 'LoadMultipleRSImageFromFile', 'LoadDepthAnnotation', 'RandomDepthMix', + 'RandomFlip', 'Resize' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/formatting.py b/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/formatting.py new file mode 100644 index 0000000..bd25055 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/formatting.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import numpy as np +from mmcv.transforms import to_tensor +from mmcv.transforms.base import BaseTransform +from mmengine.structures import PixelData + +from mmseg.registry import TRANSFORMS +from mmseg.structures import SegDataSample + + +@TRANSFORMS.register_module() +class PackSegInputs(BaseTransform): + """Pack the inputs data for the semantic segmentation. + + The ``img_meta`` item is always populated. The contents of the + ``img_meta`` dictionary depends on ``meta_keys``. By default this includes: + + - ``img_path``: filename of the image + + - ``ori_shape``: original shape of the image as a tuple (h, w, c) + + - ``img_shape``: shape of the image input to the network as a tuple \ + (h, w, c). Note that images may be zero padded on the \ + bottom/right if the batch tensor is larger than this shape. + + - ``pad_shape``: shape of padded images + + - ``scale_factor``: a float indicating the preprocessing scale + + - ``flip``: a boolean indicating if image flip transform was used + + - ``flip_direction``: the flipping direction + + Args: + meta_keys (Sequence[str], optional): Meta keys to be packed from + ``SegDataSample`` and collected in ``data[img_metas]``. + Default: ``('img_path', 'ori_shape', + 'img_shape', 'pad_shape', 'scale_factor', 'flip', + 'flip_direction')`` + """ + + def __init__(self, + meta_keys=('img_path', 'seg_map_path', 'ori_shape', + 'img_shape', 'pad_shape', 'scale_factor', 'flip', + 'flip_direction', 'reduce_zero_label')): + self.meta_keys = meta_keys + + def transform(self, results: dict) -> dict: + """Method to pack the input data. + + Args: + results (dict): Result dict from the data pipeline. + + Returns: + dict: + + - 'inputs' (obj:`torch.Tensor`): The forward data of models. + - 'data_sample' (obj:`SegDataSample`): The annotation info of the + sample. + """ + packed_results = dict() + if 'img' in results: + img = results['img'] + if len(img.shape) < 3: + img = np.expand_dims(img, -1) + if not img.flags.c_contiguous: + img = to_tensor(np.ascontiguousarray(img.transpose(2, 0, 1))) + else: + img = img.transpose(2, 0, 1) + img = to_tensor(img).contiguous() + packed_results['inputs'] = img + + data_sample = SegDataSample() + if 'gt_seg_map' in results: + if len(results['gt_seg_map'].shape) == 2: + data = to_tensor(results['gt_seg_map'][None, + ...].astype(np.int64)) + else: + warnings.warn('Please pay attention your ground truth ' + 'segmentation map, usually the segmentation ' + 'map is 2D, but got ' + f'{results["gt_seg_map"].shape}') + data = to_tensor(results['gt_seg_map'].astype(np.int64)) + gt_sem_seg_data = dict(data=data) + data_sample.gt_sem_seg = PixelData(**gt_sem_seg_data) + + if 'gt_edge_map' in results: + gt_edge_data = dict( + data=to_tensor(results['gt_edge_map'][None, + ...].astype(np.int64))) + data_sample.set_data(dict(gt_edge_map=PixelData(**gt_edge_data))) + + if 'gt_depth_map' in results: + gt_depth_data = dict( + data=to_tensor(results['gt_depth_map'][None, ...])) + data_sample.set_data(dict(gt_depth_map=PixelData(**gt_depth_data))) + + img_meta = {} + for key in self.meta_keys: + if key in results: + img_meta[key] = results[key] + data_sample.set_metainfo(img_meta) + packed_results['data_samples'] = data_sample + + return packed_results + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(meta_keys={self.meta_keys})' + return repr_str diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/loading.py b/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/loading.py new file mode 100644 index 0000000..c28937e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/loading.py @@ -0,0 +1,771 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from pathlib import Path +from typing import Dict, Optional, Union + +import mmcv +import mmengine.fileio as fileio +import numpy as np +from mmcv.transforms import BaseTransform +from mmcv.transforms import LoadAnnotations as MMCV_LoadAnnotations +from mmcv.transforms import LoadImageFromFile + +from mmseg.registry import TRANSFORMS +from mmseg.utils import datafrombytes + +try: + from osgeo import gdal +except ImportError: + gdal = None + + +@TRANSFORMS.register_module() +class LoadAnnotations(MMCV_LoadAnnotations): + """Load annotations for semantic segmentation provided by dataset. + + The annotation format is as the following: + + .. code-block:: python + + { + # Filename of semantic segmentation ground truth file. + 'seg_map_path': 'a/b/c' + } + + After this module, the annotation has been changed to the format below: + + .. code-block:: python + + { + # in str + 'seg_fields': List + # In uint8 type. + 'gt_seg_map': np.ndarray (H, W) + } + + Required Keys: + + - seg_map_path (str): Path of semantic segmentation ground truth file. + + Added Keys: + + - seg_fields (List) + - gt_seg_map (np.uint8) + + Args: + reduce_zero_label (bool, optional): Whether reduce all label value + by 1. Usually used for datasets where 0 is background label. + Defaults to None. + imdecode_backend (str): The image decoding backend type. The backend + argument for :func:``mmcv.imfrombytes``. + See :fun:``mmcv.imfrombytes`` for details. + Defaults to 'pillow'. + backend_args (dict): Arguments to instantiate a file backend. + See https://mmengine.readthedocs.io/en/latest/api/fileio.htm + for details. Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + + def __init__( + self, + reduce_zero_label=None, + backend_args=None, + imdecode_backend='pillow', + ) -> None: + super().__init__( + with_bbox=False, + with_label=False, + with_seg=True, + with_keypoints=False, + imdecode_backend=imdecode_backend, + backend_args=backend_args) + self.reduce_zero_label = reduce_zero_label + if self.reduce_zero_label is not None: + warnings.warn('`reduce_zero_label` will be deprecated, ' + 'if you would like to ignore the zero label, please ' + 'set `reduce_zero_label=True` when dataset ' + 'initialized') + self.imdecode_backend = imdecode_backend + + def _load_seg_map(self, results: dict) -> None: + """Private function to load semantic segmentation annotations. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded semantic segmentation annotations. + """ + + img_bytes = fileio.get( + results['seg_map_path'], backend_args=self.backend_args) + gt_semantic_seg = mmcv.imfrombytes( + img_bytes, flag='unchanged', + backend=self.imdecode_backend).squeeze().astype(np.uint8) + + # reduce zero_label + if self.reduce_zero_label is None: + self.reduce_zero_label = results['reduce_zero_label'] + assert self.reduce_zero_label == results['reduce_zero_label'], \ + 'Initialize dataset with `reduce_zero_label` as ' \ + f'{results["reduce_zero_label"]} but when load annotation ' \ + f'the `reduce_zero_label` is {self.reduce_zero_label}' + if self.reduce_zero_label: + # avoid using underflow conversion + gt_semantic_seg[gt_semantic_seg == 0] = 255 + gt_semantic_seg = gt_semantic_seg - 1 + gt_semantic_seg[gt_semantic_seg == 254] = 255 + # modify if custom classes + if results.get('label_map', None) is not None: + # Add deep copy to solve bug of repeatedly + # replace `gt_semantic_seg`, which is reported in + # https://github.com/open-mmlab/mmsegmentation/pull/1445/ + gt_semantic_seg_copy = gt_semantic_seg.copy() + for old_id, new_id in results['label_map'].items(): + gt_semantic_seg[gt_semantic_seg_copy == old_id] = new_id + results['gt_seg_map'] = gt_semantic_seg + results['seg_fields'].append('gt_seg_map') + + def __repr__(self) -> str: + repr_str = self.__class__.__name__ + repr_str += f'(reduce_zero_label={self.reduce_zero_label}, ' + repr_str += f"imdecode_backend='{self.imdecode_backend}', " + repr_str += f'backend_args={self.backend_args})' + return repr_str + + +@TRANSFORMS.register_module() +class LoadImageFromNDArray(LoadImageFromFile): + """Load an image from ``results['img']``. + + Similar with :obj:`LoadImageFromFile`, but the image has been loaded as + :obj:`np.ndarray` in ``results['img']``. Can be used when loading image + from webcam. + + Required Keys: + + - img + + Modified Keys: + + - img + - img_path + - img_shape + - ori_shape + + Args: + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is an uint8 array. + Defaults to False. + """ + + def transform(self, results: dict) -> dict: + """Transform function to add image meta information. + + Args: + results (dict): Result dict with Webcam read image in + ``results['img']``. + + Returns: + dict: The dict contains loaded image and meta information. + """ + + img = results['img'] + if self.to_float32: + img = img.astype(np.float32) + + results['img_path'] = None + results['img'] = img + results['img_shape'] = img.shape[:2] + results['ori_shape'] = img.shape[:2] + return results + + +@TRANSFORMS.register_module() +class LoadBiomedicalImageFromFile(BaseTransform): + """Load an biomedical mage from file. + + Required Keys: + + - img_path + + Added Keys: + + - img (np.ndarray): Biomedical image with shape (N, Z, Y, X) by default, + N is the number of modalities, and data type is float32 + if set to_float32 = True, or float64 if decode_backend is 'nifti' and + to_float32 is False. + - img_shape + - ori_shape + + Args: + decode_backend (str): The data decoding backend type. Options are + 'numpy'and 'nifti', and there is a convention that when backend is + 'nifti' the axis of data loaded is XYZ, and when backend is + 'numpy', the the axis is ZYX. The data will be transposed if the + backend is 'nifti'. Defaults to 'nifti'. + to_xyz (bool): Whether transpose data from Z, Y, X to X, Y, Z. + Defaults to False. + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is an float64 array. + Defaults to True. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See https://mmengine.readthedocs.io/en/latest/api/fileio.htm + for details. Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + + def __init__(self, + decode_backend: str = 'nifti', + to_xyz: bool = False, + to_float32: bool = True, + backend_args: Optional[dict] = None) -> None: + self.decode_backend = decode_backend + self.to_xyz = to_xyz + self.to_float32 = to_float32 + self.backend_args = backend_args.copy() if backend_args else None + + def transform(self, results: Dict) -> Dict: + """Functions to load image. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded image and meta information. + """ + + filename = results['img_path'] + + data_bytes = fileio.get(filename, self.backend_args) + img = datafrombytes(data_bytes, backend=self.decode_backend) + + if self.to_float32: + img = img.astype(np.float32) + + if len(img.shape) == 3: + img = img[None, ...] + + if self.decode_backend == 'nifti': + img = img.transpose(0, 3, 2, 1) + + if self.to_xyz: + img = img.transpose(0, 3, 2, 1) + + results['img'] = img + results['img_shape'] = img.shape[1:] + results['ori_shape'] = img.shape[1:] + return results + + def __repr__(self): + repr_str = (f'{self.__class__.__name__}(' + f"decode_backend='{self.decode_backend}', " + f'to_xyz={self.to_xyz}, ' + f'to_float32={self.to_float32}, ' + f'backend_args={self.backend_args})') + return repr_str + + +@TRANSFORMS.register_module() +class LoadBiomedicalAnnotation(BaseTransform): + """Load ``seg_map`` annotation provided by biomedical dataset. + + The annotation format is as the following: + + .. code-block:: python + + { + 'gt_seg_map': np.ndarray (X, Y, Z) or (Z, Y, X) + } + + Required Keys: + + - seg_map_path + + Added Keys: + + - gt_seg_map (np.ndarray): Biomedical seg map with shape (Z, Y, X) by + default, and data type is float32 if set to_float32 = True, or + float64 if decode_backend is 'nifti' and to_float32 is False. + + Args: + decode_backend (str): The data decoding backend type. Options are + 'numpy'and 'nifti', and there is a convention that when backend is + 'nifti' the axis of data loaded is XYZ, and when backend is + 'numpy', the the axis is ZYX. The data will be transposed if the + backend is 'nifti'. Defaults to 'nifti'. + to_xyz (bool): Whether transpose data from Z, Y, X to X, Y, Z. + Defaults to False. + to_float32 (bool): Whether to convert the loaded seg map to a float32 + numpy array. If set to False, the loaded image is an float64 array. + Defaults to True. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See :class:`mmengine.fileio` for details. + Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + + def __init__(self, + decode_backend: str = 'nifti', + to_xyz: bool = False, + to_float32: bool = True, + backend_args: Optional[dict] = None) -> None: + super().__init__() + self.decode_backend = decode_backend + self.to_xyz = to_xyz + self.to_float32 = to_float32 + self.backend_args = backend_args.copy() if backend_args else None + + def transform(self, results: Dict) -> Dict: + """Functions to load image. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded image and meta information. + """ + data_bytes = fileio.get(results['seg_map_path'], self.backend_args) + gt_seg_map = datafrombytes(data_bytes, backend=self.decode_backend) + + if self.to_float32: + gt_seg_map = gt_seg_map.astype(np.float32) + + if self.decode_backend == 'nifti': + gt_seg_map = gt_seg_map.transpose(2, 1, 0) + + if self.to_xyz: + gt_seg_map = gt_seg_map.transpose(2, 1, 0) + + results['gt_seg_map'] = gt_seg_map + return results + + def __repr__(self): + repr_str = (f'{self.__class__.__name__}(' + f"decode_backend='{self.decode_backend}', " + f'to_xyz={self.to_xyz}, ' + f'to_float32={self.to_float32}, ' + f'backend_args={self.backend_args})') + return repr_str + + +@TRANSFORMS.register_module() +class LoadBiomedicalData(BaseTransform): + """Load an biomedical image and annotation from file. + + The loading data format is as the following: + + .. code-block:: python + + { + 'img': np.ndarray data[:-1, X, Y, Z] + 'seg_map': np.ndarray data[-1, X, Y, Z] + } + + + Required Keys: + + - img_path + + Added Keys: + + - img (np.ndarray): Biomedical image with shape (N, Z, Y, X) by default, + N is the number of modalities. + - gt_seg_map (np.ndarray, optional): Biomedical seg map with shape + (Z, Y, X) by default. + - img_shape + - ori_shape + + Args: + with_seg (bool): Whether to parse and load the semantic segmentation + annotation. Defaults to False. + decode_backend (str): The data decoding backend type. Options are + 'numpy'and 'nifti', and there is a convention that when backend is + 'nifti' the axis of data loaded is XYZ, and when backend is + 'numpy', the the axis is ZYX. The data will be transposed if the + backend is 'nifti'. Defaults to 'nifti'. + to_xyz (bool): Whether transpose data from Z, Y, X to X, Y, Z. + Defaults to False. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See https://mmengine.readthedocs.io/en/latest/api/fileio.htm + for details. Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + + def __init__(self, + with_seg=False, + decode_backend: str = 'numpy', + to_xyz: bool = False, + backend_args: Optional[dict] = None) -> None: # noqa + self.with_seg = with_seg + self.decode_backend = decode_backend + self.to_xyz = to_xyz + self.backend_args = backend_args.copy() if backend_args else None + + def transform(self, results: Dict) -> Dict: + """Functions to load image. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded image and meta information. + """ + data_bytes = fileio.get(results['img_path'], self.backend_args) + data = datafrombytes(data_bytes, backend=self.decode_backend) + # img is 4D data (N, X, Y, Z), N is the number of protocol + img = data[:-1, :] + + if self.decode_backend == 'nifti': + img = img.transpose(0, 3, 2, 1) + + if self.to_xyz: + img = img.transpose(0, 3, 2, 1) + + results['img'] = img + results['img_shape'] = img.shape[1:] + results['ori_shape'] = img.shape[1:] + + if self.with_seg: + gt_seg_map = data[-1, :] + if self.decode_backend == 'nifti': + gt_seg_map = gt_seg_map.transpose(2, 1, 0) + + if self.to_xyz: + gt_seg_map = gt_seg_map.transpose(2, 1, 0) + results['gt_seg_map'] = gt_seg_map + return results + + def __repr__(self) -> str: + repr_str = (f'{self.__class__.__name__}(' + f'with_seg={self.with_seg}, ' + f"decode_backend='{self.decode_backend}', " + f'to_xyz={self.to_xyz}, ' + f'backend_args={self.backend_args})') + return repr_str + + +@TRANSFORMS.register_module() +class InferencerLoader(BaseTransform): + """Load an image from ``results['img']``. + + Similar with :obj:`LoadImageFromFile`, but the image has been loaded as + :obj:`np.ndarray` in ``results['img']``. Can be used when loading image + from webcam. + + Required Keys: + + - img + + Modified Keys: + + - img + - img_path + - img_shape + - ori_shape + + Args: + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is an uint8 array. + Defaults to False. + """ + + def __init__(self, **kwargs) -> None: + super().__init__() + self.from_file = TRANSFORMS.build( + dict(type='LoadImageFromFile', **kwargs)) + self.from_ndarray = TRANSFORMS.build( + dict(type='LoadImageFromNDArray', **kwargs)) + + def transform(self, single_input: Union[str, np.ndarray, dict]) -> dict: + """Transform function to add image meta information. + + Args: + results (dict): Result dict with Webcam read image in + ``results['img']``. + + Returns: + dict: The dict contains loaded image and meta information. + """ + if isinstance(single_input, str): + inputs = dict(img_path=single_input) + elif isinstance(single_input, np.ndarray): + inputs = dict(img=single_input) + elif isinstance(single_input, dict): + inputs = single_input + else: + raise NotImplementedError + + if 'img' in inputs: + return self.from_ndarray(inputs) + return self.from_file(inputs) + + +@TRANSFORMS.register_module() +class LoadSingleRSImageFromFile(BaseTransform): + """Load a Remote Sensing mage from file. + + Required Keys: + + - img_path + + Modified Keys: + + - img + - img_shape + - ori_shape + + Args: + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is a float64 array. + Defaults to True. + """ + + def __init__(self, to_float32: bool = True): + self.to_float32 = to_float32 + + if gdal is None: + raise RuntimeError('gdal is not installed') + + def transform(self, results: Dict) -> Dict: + """Functions to load image. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded image and meta information. + """ + + filename = results['img_path'] + ds = gdal.Open(filename) + if ds is None: + raise Exception(f'Unable to open file: {filename}') + img = np.einsum('ijk->jki', ds.ReadAsArray()) + + if self.to_float32: + img = img.astype(np.float32) + + results['img'] = img + results['img_shape'] = img.shape[:2] + results['ori_shape'] = img.shape[:2] + return results + + def __repr__(self): + repr_str = (f'{self.__class__.__name__}(' + f'to_float32={self.to_float32})') + return repr_str + + +@TRANSFORMS.register_module() +class LoadMultipleRSImageFromFile(BaseTransform): + """Load two Remote Sensing mage from file. + + Required Keys: + + - img_path + - img_path2 + + Modified Keys: + + - img + - img2 + - img_shape + - ori_shape + + Args: + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is a float64 array. + Defaults to True. + """ + + def __init__(self, to_float32: bool = True): + if gdal is None: + raise RuntimeError('gdal is not installed') + self.to_float32 = to_float32 + + def transform(self, results: Dict) -> Dict: + """Functions to load image. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded image and meta information. + """ + + filename = results['img_path'] + filename2 = results['img_path2'] + + ds = gdal.Open(filename) + ds2 = gdal.Open(filename2) + + if ds is None: + raise Exception(f'Unable to open file: {filename}') + if ds2 is None: + raise Exception(f'Unable to open file: {filename2}') + + img = np.einsum('ijk->jki', ds.ReadAsArray()) + img2 = np.einsum('ijk->jki', ds2.ReadAsArray()) + + if self.to_float32: + img = img.astype(np.float32) + img2 = img2.astype(np.float32) + + if img.shape != img2.shape: + raise Exception(f'Image shapes do not match:' + f' {img.shape} vs {img2.shape}') + + results['img'] = img + results['img2'] = img2 + results['img_shape'] = img.shape[:2] + results['ori_shape'] = img.shape[:2] + return results + + def __repr__(self): + repr_str = (f'{self.__class__.__name__}(' + f'to_float32={self.to_float32})') + return repr_str + + +@TRANSFORMS.register_module() +class LoadDepthAnnotation(BaseTransform): + """Load ``depth_map`` annotation provided by depth estimation dataset. + + The annotation format is as the following: + + .. code-block:: python + + { + 'gt_depth_map': np.ndarray [Y, X] + } + + Required Keys: + + - seg_depth_path + + Added Keys: + + - gt_depth_map (np.ndarray): Depth map with shape (Y, X) by + default, and data type is float32 if set to_float32 = True. + - depth_rescale_factor (float): The rescale factor of depth map, which + can be used to recover the original value of depth map. + + Args: + decode_backend (str): The data decoding backend type. Options are + 'numpy', 'nifti', and 'cv2'. Defaults to 'cv2'. + to_float32 (bool): Whether to convert the loaded depth map to a float32 + numpy array. If set to False, the loaded image is an uint16 array. + Defaults to True. + depth_rescale_factor (float): Factor to rescale the depth value to + limit the range. Defaults to 1.0. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See :class:`mmengine.fileio` for details. + Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + + def __init__(self, + decode_backend: str = 'cv2', + to_float32: bool = True, + depth_rescale_factor: float = 1.0, + backend_args: Optional[dict] = None) -> None: + super().__init__() + self.decode_backend = decode_backend + self.to_float32 = to_float32 + self.depth_rescale_factor = depth_rescale_factor + self.backend_args = backend_args.copy() if backend_args else None + + def transform(self, results: Dict) -> Dict: + """Functions to load depth map. + + Args: + results (dict): Result dict from :obj:``mmcv.BaseDataset``. + + Returns: + dict: The dict contains loaded depth map. + """ + data_bytes = fileio.get(results['depth_map_path'], self.backend_args) + gt_depth_map = datafrombytes(data_bytes, backend=self.decode_backend) + + if self.to_float32: + gt_depth_map = gt_depth_map.astype(np.float32) + + gt_depth_map *= self.depth_rescale_factor + results['gt_depth_map'] = gt_depth_map + results['seg_fields'].append('gt_depth_map') + results['depth_rescale_factor'] = self.depth_rescale_factor + return results + + def __repr__(self): + repr_str = (f'{self.__class__.__name__}(' + f"decode_backend='{self.decode_backend}', " + f'to_float32={self.to_float32}, ' + f'backend_args={self.backend_args})') + return repr_str + + +@TRANSFORMS.register_module() +class LoadImageFromNpyFile(LoadImageFromFile): + """Load an image from ``results['img_path']``. + + Required Keys: + + - img_path + + Modified Keys: + + - img + - img_shape + - ori_shape + + Args: + to_float32 (bool): Whether to convert the loaded image to a float32 + numpy array. If set to False, the loaded image is an uint8 array. + Defaults to False. + """ + + def transform(self, results: dict) -> Optional[dict]: + """Functions to load image. + + Args: + results (dict): Result dict from + :class:`mmengine.dataset.BaseDataset`. + + Returns: + dict: The dict contains loaded image and meta information. + """ + + filename = results['img_path'] + + try: + if Path(filename).suffix in ['.npy', '.npz']: + img = np.load(filename) + else: + if self.file_client_args is not None: + file_client = fileio.FileClient.infer_client( + self.file_client_args, filename) + img_bytes = file_client.get(filename) + else: + img_bytes = fileio.get( + filename, backend_args=self.backend_args) + img = mmcv.imfrombytes( + img_bytes, + flag=self.color_type, + backend=self.imdecode_backend) + except Exception as e: + if self.ignore_empty: + return None + else: + raise e + + # in some cases, images are not read successfully, the img would be + # `None`, refer to https://github.com/open-mmlab/mmpretrain/issues/1427 + assert img is not None, f'failed to load image: {filename}' + if self.to_float32: + img = img.astype(np.float32) + + results['img'] = img + results['img_shape'] = img.shape[:2] + results['ori_shape'] = img.shape[:2] + return results diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/transforms.py b/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/transforms.py new file mode 100644 index 0000000..64e2323 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/transforms/transforms.py @@ -0,0 +1,2537 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import inspect +import warnings +from typing import Dict, List, Optional, Sequence, Tuple, Union + +import cv2 +import mmcv +import mmengine +import numpy as np +from mmcv.transforms import RandomFlip as MMCV_RandomFlip +from mmcv.transforms import Resize as MMCV_Resize +from mmcv.transforms.base import BaseTransform +from mmcv.transforms.utils import cache_randomness +from mmengine.utils import is_tuple_of +from numpy import random +from scipy.ndimage import gaussian_filter + +from mmseg.datasets.dataset_wrappers import MultiImageMixDataset +from mmseg.registry import TRANSFORMS + +try: + import albumentations + from albumentations import Compose + ALBU_INSTALLED = True +except ImportError: + albumentations = None + Compose = None + ALBU_INSTALLED = False + + +@TRANSFORMS.register_module() +class ResizeToMultiple(BaseTransform): + """Resize images & seg to multiple of divisor. + + Required Keys: + + - img + - gt_seg_map + + Modified Keys: + + - img + - img_shape + - pad_shape + + Args: + size_divisor (int): images and gt seg maps need to resize to multiple + of size_divisor. Default: 32. + interpolation (str, optional): The interpolation mode of image resize. + Default: None + """ + + def __init__(self, size_divisor=32, interpolation=None): + self.size_divisor = size_divisor + self.interpolation = interpolation + + def transform(self, results: dict) -> dict: + """Call function to resize images, semantic segmentation map to + multiple of size divisor. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Resized results, 'img_shape', 'pad_shape' keys are updated. + """ + # Align image to multiple of size divisor. + img = results['img'] + img = mmcv.imresize_to_multiple( + img, + self.size_divisor, + scale_factor=1, + interpolation=self.interpolation + if self.interpolation else 'bilinear') + + results['img'] = img + results['img_shape'] = img.shape[:2] + results['pad_shape'] = img.shape[:2] + + # Align segmentation map to multiple of size divisor. + for key in results.get('seg_fields', []): + gt_seg = results[key] + gt_seg = mmcv.imresize_to_multiple( + gt_seg, + self.size_divisor, + scale_factor=1, + interpolation='nearest') + results[key] = gt_seg + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += (f'(size_divisor={self.size_divisor}, ' + f'interpolation={self.interpolation})') + return repr_str + + +@TRANSFORMS.register_module() +class Rerange(BaseTransform): + """Rerange the image pixel value. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + min_value (float or int): Minimum value of the reranged image. + Default: 0. + max_value (float or int): Maximum value of the reranged image. + Default: 255. + """ + + def __init__(self, min_value=0, max_value=255): + assert isinstance(min_value, float) or isinstance(min_value, int) + assert isinstance(max_value, float) or isinstance(max_value, int) + assert min_value < max_value + self.min_value = min_value + self.max_value = max_value + + def transform(self, results: dict) -> dict: + """Call function to rerange images. + + Args: + results (dict): Result dict from loading pipeline. + Returns: + dict: Reranged results. + """ + + img = results['img'] + img_min_value = np.min(img) + img_max_value = np.max(img) + + assert img_min_value < img_max_value + # rerange to [0, 1] + img = (img - img_min_value) / (img_max_value - img_min_value) + # rerange to [min_value, max_value] + img = img * (self.max_value - self.min_value) + self.min_value + results['img'] = img + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(min_value={self.min_value}, max_value={self.max_value})' + return repr_str + + +@TRANSFORMS.register_module() +class CLAHE(BaseTransform): + """Use CLAHE method to process the image. + + See `ZUIDERVELD,K. Contrast Limited Adaptive Histogram Equalization[J]. + Graphics Gems, 1994:474-485.` for more information. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + clip_limit (float): Threshold for contrast limiting. Default: 40.0. + tile_grid_size (tuple[int]): Size of grid for histogram equalization. + Input image will be divided into equally sized rectangular tiles. + It defines the number of tiles in row and column. Default: (8, 8). + """ + + def __init__(self, clip_limit=40.0, tile_grid_size=(8, 8)): + assert isinstance(clip_limit, (float, int)) + self.clip_limit = clip_limit + assert is_tuple_of(tile_grid_size, int) + assert len(tile_grid_size) == 2 + self.tile_grid_size = tile_grid_size + + def transform(self, results: dict) -> dict: + """Call function to Use CLAHE method process images. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Processed results. + """ + + for i in range(results['img'].shape[2]): + results['img'][:, :, i] = mmcv.clahe( + np.array(results['img'][:, :, i], dtype=np.uint8), + self.clip_limit, self.tile_grid_size) + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(clip_limit={self.clip_limit}, ' \ + f'tile_grid_size={self.tile_grid_size})' + return repr_str + + +@TRANSFORMS.register_module() +class RandomCrop(BaseTransform): + """Random crop the image & seg. + + Required Keys: + + - img + - gt_seg_map + + Modified Keys: + + - img + - img_shape + - gt_seg_map + + + Args: + crop_size (Union[int, Tuple[int, int]]): Expected size after cropping + with the format of (h, w). If set to an integer, then cropping + width and height are equal to this integer. + cat_max_ratio (float): The maximum ratio that single category could + occupy. + ignore_index (int): The label index to be ignored. Default: 255 + """ + + def __init__(self, + crop_size: Union[int, Tuple[int, int]], + cat_max_ratio: float = 1., + ignore_index: int = 255): + super().__init__() + assert isinstance(crop_size, int) or ( + isinstance(crop_size, tuple) and len(crop_size) == 2 + ), 'The expected crop_size is an integer, or a tuple containing two ' + 'intergers' + + if isinstance(crop_size, int): + crop_size = (crop_size, crop_size) + assert crop_size[0] > 0 and crop_size[1] > 0 + self.crop_size = crop_size + self.cat_max_ratio = cat_max_ratio + self.ignore_index = ignore_index + + @cache_randomness + def crop_bbox(self, results: dict) -> tuple: + """get a crop bounding box. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + tuple: Coordinates of the cropped image. + """ + + def generate_crop_bbox(img: np.ndarray) -> tuple: + """Randomly get a crop bounding box. + + Args: + img (np.ndarray): Original input image. + + Returns: + tuple: Coordinates of the cropped image. + """ + + margin_h = max(img.shape[0] - self.crop_size[0], 0) + margin_w = max(img.shape[1] - self.crop_size[1], 0) + offset_h = np.random.randint(0, margin_h + 1) + offset_w = np.random.randint(0, margin_w + 1) + crop_y1, crop_y2 = offset_h, offset_h + self.crop_size[0] + crop_x1, crop_x2 = offset_w, offset_w + self.crop_size[1] + + return crop_y1, crop_y2, crop_x1, crop_x2 + + img = results['img'] + crop_bbox = generate_crop_bbox(img) + if self.cat_max_ratio < 1.: + # Repeat 10 times + for _ in range(10): + seg_temp = self.crop(results['gt_seg_map'], crop_bbox) + labels, cnt = np.unique(seg_temp, return_counts=True) + cnt = cnt[labels != self.ignore_index] + if len(cnt) > 1 and np.max(cnt) / np.sum( + cnt) < self.cat_max_ratio: + break + crop_bbox = generate_crop_bbox(img) + + return crop_bbox + + def crop(self, img: np.ndarray, crop_bbox: tuple) -> np.ndarray: + """Crop from ``img`` + + Args: + img (np.ndarray): Original input image. + crop_bbox (tuple): Coordinates of the cropped image. + + Returns: + np.ndarray: The cropped image. + """ + + crop_y1, crop_y2, crop_x1, crop_x2 = crop_bbox + img = img[crop_y1:crop_y2, crop_x1:crop_x2, ...] + return img + + def transform(self, results: dict) -> dict: + """Transform function to randomly crop images, semantic segmentation + maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Randomly cropped results, 'img_shape' key in result dict is + updated according to crop size. + """ + + img = results['img'] + crop_bbox = self.crop_bbox(results) + + # crop the image + img = self.crop(img, crop_bbox) + + # crop semantic seg + for key in results.get('seg_fields', []): + results[key] = self.crop(results[key], crop_bbox) + + results['img'] = img + results['img_shape'] = img.shape[:2] + return results + + def __repr__(self): + return self.__class__.__name__ + f'(crop_size={self.crop_size})' + + +@TRANSFORMS.register_module() +class RandomRotate(BaseTransform): + """Rotate the image & seg. + + Required Keys: + + - img + - gt_seg_map + + Modified Keys: + + - img + - gt_seg_map + + Args: + prob (float): The rotation probability. + degree (float, tuple[float]): Range of degrees to select from. If + degree is a number instead of tuple like (min, max), + the range of degree will be (``-degree``, ``+degree``) + pad_val (float, optional): Padding value of image. Default: 0. + seg_pad_val (float, optional): Padding value of segmentation map. + Default: 255. + center (tuple[float], optional): Center point (w, h) of the rotation in + the source image. If not specified, the center of the image will be + used. Default: None. + auto_bound (bool): Whether to adjust the image size to cover the whole + rotated image. Default: False + """ + + def __init__(self, + prob, + degree, + pad_val=0, + seg_pad_val=255, + center=None, + auto_bound=False): + self.prob = prob + assert prob >= 0 and prob <= 1 + if isinstance(degree, (float, int)): + assert degree > 0, f'degree {degree} should be positive' + self.degree = (-degree, degree) + else: + self.degree = degree + assert len(self.degree) == 2, f'degree {self.degree} should be a ' \ + f'tuple of (min, max)' + self.pal_val = pad_val + self.seg_pad_val = seg_pad_val + self.center = center + self.auto_bound = auto_bound + + @cache_randomness + def generate_degree(self): + return np.random.rand() < self.prob, np.random.uniform( + min(*self.degree), max(*self.degree)) + + def transform(self, results: dict) -> dict: + """Call function to rotate image, semantic segmentation maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Rotated results. + """ + + rotate, degree = self.generate_degree() + if rotate: + # rotate image + results['img'] = mmcv.imrotate( + results['img'], + angle=degree, + border_value=self.pal_val, + center=self.center, + auto_bound=self.auto_bound) + + # rotate segs + for key in results.get('seg_fields', []): + results[key] = mmcv.imrotate( + results[key], + angle=degree, + border_value=self.seg_pad_val, + center=self.center, + auto_bound=self.auto_bound, + interpolation='nearest') + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' \ + f'degree={self.degree}, ' \ + f'pad_val={self.pal_val}, ' \ + f'seg_pad_val={self.seg_pad_val}, ' \ + f'center={self.center}, ' \ + f'auto_bound={self.auto_bound})' + return repr_str + + +@TRANSFORMS.register_module() +class RGB2Gray(BaseTransform): + """Convert RGB image to grayscale image. + + Required Keys: + + - img + + Modified Keys: + + - img + - img_shape + + This transform calculate the weighted mean of input image channels with + ``weights`` and then expand the channels to ``out_channels``. When + ``out_channels`` is None, the number of output channels is the same as + input channels. + + Args: + out_channels (int): Expected number of output channels after + transforming. Default: None. + weights (tuple[float]): The weights to calculate the weighted mean. + Default: (0.299, 0.587, 0.114). + """ + + def __init__(self, out_channels=None, weights=(0.299, 0.587, 0.114)): + assert out_channels is None or out_channels > 0 + self.out_channels = out_channels + assert isinstance(weights, tuple) + for item in weights: + assert isinstance(item, (float, int)) + self.weights = weights + + def transform(self, results: dict) -> dict: + """Call function to convert RGB image to grayscale image. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with grayscale image. + """ + img = results['img'] + assert len(img.shape) == 3 + assert img.shape[2] == len(self.weights) + weights = np.array(self.weights).reshape((1, 1, -1)) + img = (img * weights).sum(2, keepdims=True) + if self.out_channels is None: + img = img.repeat(weights.shape[2], axis=2) + else: + img = img.repeat(self.out_channels, axis=2) + + results['img'] = img + results['img_shape'] = img.shape + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(out_channels={self.out_channels}, ' \ + f'weights={self.weights})' + return repr_str + + +@TRANSFORMS.register_module() +class AdjustGamma(BaseTransform): + """Using gamma correction to process the image. + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + gamma (float or int): Gamma value used in gamma correction. + Default: 1.0. + """ + + def __init__(self, gamma=1.0): + assert isinstance(gamma, float) or isinstance(gamma, int) + assert gamma > 0 + self.gamma = gamma + inv_gamma = 1.0 / gamma + self.table = np.array([(i / 255.0)**inv_gamma * 255 + for i in np.arange(256)]).astype('uint8') + + def transform(self, results: dict) -> dict: + """Call function to process the image with gamma correction. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Processed results. + """ + + results['img'] = mmcv.lut_transform( + np.array(results['img'], dtype=np.uint8), self.table) + + return results + + def __repr__(self): + return self.__class__.__name__ + f'(gamma={self.gamma})' + + +@TRANSFORMS.register_module() +class SegRescale(BaseTransform): + """Rescale semantic segmentation maps. + + Required Keys: + + - gt_seg_map + + Modified Keys: + + - gt_seg_map + + Args: + scale_factor (float): The scale factor of the final output. + """ + + def __init__(self, scale_factor=1): + self.scale_factor = scale_factor + + def transform(self, results: dict) -> dict: + """Call function to scale the semantic segmentation map. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with semantic segmentation map scaled. + """ + for key in results.get('seg_fields', []): + if self.scale_factor != 1: + results[key] = mmcv.imrescale( + results[key], self.scale_factor, interpolation='nearest') + return results + + def __repr__(self): + return self.__class__.__name__ + f'(scale_factor={self.scale_factor})' + + +@TRANSFORMS.register_module() +class PhotoMetricDistortion(BaseTransform): + """Apply photometric distortion to image sequentially, every transformation + is applied with a probability of 0.5. The position of random contrast is in + second or second to last. + + 1. random brightness + 2. random contrast (mode 0) + 3. convert color from BGR to HSV + 4. random saturation + 5. random hue + 6. convert color from HSV to BGR + 7. random contrast (mode 1) + + Required Keys: + + - img + + Modified Keys: + + - img + + Args: + brightness_delta (int): delta of brightness. + contrast_range (tuple): range of contrast. + saturation_range (tuple): range of saturation. + hue_delta (int): delta of hue. + """ + + def __init__(self, + brightness_delta: int = 32, + contrast_range: Sequence[float] = (0.5, 1.5), + saturation_range: Sequence[float] = (0.5, 1.5), + hue_delta: int = 18): + self.brightness_delta = brightness_delta + self.contrast_lower, self.contrast_upper = contrast_range + self.saturation_lower, self.saturation_upper = saturation_range + self.hue_delta = hue_delta + + def convert(self, + img: np.ndarray, + alpha: int = 1, + beta: int = 0) -> np.ndarray: + """Multiple with alpha and add beat with clip. + + Args: + img (np.ndarray): The input image. + alpha (int): Image weights, change the contrast/saturation + of the image. Default: 1 + beta (int): Image bias, change the brightness of the + image. Default: 0 + + Returns: + np.ndarray: The transformed image. + """ + + img = img.astype(np.float32) * alpha + beta + img = np.clip(img, 0, 255) + return img.astype(np.uint8) + + def brightness(self, img: np.ndarray) -> np.ndarray: + """Brightness distortion. + + Args: + img (np.ndarray): The input image. + Returns: + np.ndarray: Image after brightness change. + """ + + if random.randint(2): + return self.convert( + img, + beta=random.uniform(-self.brightness_delta, + self.brightness_delta)) + return img + + def contrast(self, img: np.ndarray) -> np.ndarray: + """Contrast distortion. + + Args: + img (np.ndarray): The input image. + Returns: + np.ndarray: Image after contrast change. + """ + + if random.randint(2): + return self.convert( + img, + alpha=random.uniform(self.contrast_lower, self.contrast_upper)) + return img + + def saturation(self, img: np.ndarray) -> np.ndarray: + """Saturation distortion. + + Args: + img (np.ndarray): The input image. + Returns: + np.ndarray: Image after saturation change. + """ + + if random.randint(2): + img = mmcv.bgr2hsv(img) + img[:, :, 1] = self.convert( + img[:, :, 1], + alpha=random.uniform(self.saturation_lower, + self.saturation_upper)) + img = mmcv.hsv2bgr(img) + return img + + def hue(self, img: np.ndarray) -> np.ndarray: + """Hue distortion. + + Args: + img (np.ndarray): The input image. + Returns: + np.ndarray: Image after hue change. + """ + + if random.randint(2): + img = mmcv.bgr2hsv(img) + img[:, :, + 0] = (img[:, :, 0].astype(int) + + random.randint(-self.hue_delta, self.hue_delta)) % 180 + img = mmcv.hsv2bgr(img) + return img + + def transform(self, results: dict) -> dict: + """Transform function to perform photometric distortion on images. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with images distorted. + """ + + img = results['img'] + # random brightness + img = self.brightness(img) + + # mode == 0 --> do random contrast first + # mode == 1 --> do random contrast last + mode = random.randint(2) + if mode == 1: + img = self.contrast(img) + + # random saturation + img = self.saturation(img) + + # random hue + img = self.hue(img) + + # random contrast + if mode == 0: + img = self.contrast(img) + + results['img'] = img + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += (f'(brightness_delta={self.brightness_delta}, ' + f'contrast_range=({self.contrast_lower}, ' + f'{self.contrast_upper}), ' + f'saturation_range=({self.saturation_lower}, ' + f'{self.saturation_upper}), ' + f'hue_delta={self.hue_delta})') + return repr_str + + +@TRANSFORMS.register_module() +class RandomCutOut(BaseTransform): + """CutOut operation. + + Randomly drop some regions of image used in + `Cutout `_. + + Required Keys: + + - img + - gt_seg_map + + Modified Keys: + + - img + - gt_seg_map + + Args: + prob (float): cutout probability. + n_holes (int | tuple[int, int]): Number of regions to be dropped. + If it is given as a list, number of holes will be randomly + selected from the closed interval [`n_holes[0]`, `n_holes[1]`]. + cutout_shape (tuple[int, int] | list[tuple[int, int]]): The candidate + shape of dropped regions. It can be `tuple[int, int]` to use a + fixed cutout shape, or `list[tuple[int, int]]` to randomly choose + shape from the list. + cutout_ratio (tuple[float, float] | list[tuple[float, float]]): The + candidate ratio of dropped regions. It can be `tuple[float, float]` + to use a fixed ratio or `list[tuple[float, float]]` to randomly + choose ratio from the list. Please note that `cutout_shape` + and `cutout_ratio` cannot be both given at the same time. + fill_in (tuple[float, float, float] | tuple[int, int, int]): The value + of pixel to fill in the dropped regions. Default: (0, 0, 0). + seg_fill_in (int): The labels of pixel to fill in the dropped regions. + If seg_fill_in is None, skip. Default: None. + """ + + def __init__(self, + prob, + n_holes, + cutout_shape=None, + cutout_ratio=None, + fill_in=(0, 0, 0), + seg_fill_in=None): + + assert 0 <= prob and prob <= 1 + assert (cutout_shape is None) ^ (cutout_ratio is None), \ + 'Either cutout_shape or cutout_ratio should be specified.' + assert (isinstance(cutout_shape, (list, tuple)) + or isinstance(cutout_ratio, (list, tuple))) + if isinstance(n_holes, tuple): + assert len(n_holes) == 2 and 0 <= n_holes[0] < n_holes[1] + else: + n_holes = (n_holes, n_holes) + if seg_fill_in is not None: + assert (isinstance(seg_fill_in, int) and 0 <= seg_fill_in + and seg_fill_in <= 255) + self.prob = prob + self.n_holes = n_holes + self.fill_in = fill_in + self.seg_fill_in = seg_fill_in + self.with_ratio = cutout_ratio is not None + self.candidates = cutout_ratio if self.with_ratio else cutout_shape + if not isinstance(self.candidates, list): + self.candidates = [self.candidates] + + @cache_randomness + def do_cutout(self): + return np.random.rand() < self.prob + + @cache_randomness + def generate_patches(self, results): + cutout = self.do_cutout() + + h, w, _ = results['img'].shape + if cutout: + n_holes = np.random.randint(self.n_holes[0], self.n_holes[1] + 1) + else: + n_holes = 0 + x1_lst = [] + y1_lst = [] + index_lst = [] + for _ in range(n_holes): + x1_lst.append(np.random.randint(0, w)) + y1_lst.append(np.random.randint(0, h)) + index_lst.append(np.random.randint(0, len(self.candidates))) + return cutout, n_holes, x1_lst, y1_lst, index_lst + + def transform(self, results: dict) -> dict: + """Call function to drop some regions of image.""" + cutout, n_holes, x1_lst, y1_lst, index_lst = self.generate_patches( + results) + if cutout: + h, w, c = results['img'].shape + for i in range(n_holes): + x1 = x1_lst[i] + y1 = y1_lst[i] + index = index_lst[i] + if not self.with_ratio: + cutout_w, cutout_h = self.candidates[index] + else: + cutout_w = int(self.candidates[index][0] * w) + cutout_h = int(self.candidates[index][1] * h) + + x2 = np.clip(x1 + cutout_w, 0, w) + y2 = np.clip(y1 + cutout_h, 0, h) + results['img'][y1:y2, x1:x2, :] = self.fill_in + + if self.seg_fill_in is not None: + for key in results.get('seg_fields', []): + results[key][y1:y2, x1:x2] = self.seg_fill_in + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' + repr_str += f'n_holes={self.n_holes}, ' + repr_str += (f'cutout_ratio={self.candidates}, ' if self.with_ratio + else f'cutout_shape={self.candidates}, ') + repr_str += f'fill_in={self.fill_in}, ' + repr_str += f'seg_fill_in={self.seg_fill_in})' + return repr_str + + +@TRANSFORMS.register_module() +class RandomRotFlip(BaseTransform): + """Rotate and flip the image & seg or just rotate the image & seg. + + Required Keys: + + - img + - gt_seg_map + + Modified Keys: + + - img + - gt_seg_map + + Args: + rotate_prob (float): The probability of rotate image. + flip_prob (float): The probability of rotate&flip image. + degree (float, tuple[float]): Range of degrees to select from. If + degree is a number instead of tuple like (min, max), + the range of degree will be (``-degree``, ``+degree``) + """ + + def __init__(self, rotate_prob=0.5, flip_prob=0.5, degree=(-20, 20)): + self.rotate_prob = rotate_prob + self.flip_prob = flip_prob + assert 0 <= rotate_prob <= 1 and 0 <= flip_prob <= 1 + if isinstance(degree, (float, int)): + assert degree > 0, f'degree {degree} should be positive' + self.degree = (-degree, degree) + else: + self.degree = degree + assert len(self.degree) == 2, f'degree {self.degree} should be a ' \ + f'tuple of (min, max)' + + def random_rot_flip(self, results: dict) -> dict: + k = np.random.randint(0, 4) + results['img'] = np.rot90(results['img'], k) + for key in results.get('seg_fields', []): + results[key] = np.rot90(results[key], k) + axis = np.random.randint(0, 2) + results['img'] = np.flip(results['img'], axis=axis).copy() + for key in results.get('seg_fields', []): + results[key] = np.flip(results[key], axis=axis).copy() + return results + + def random_rotate(self, results: dict) -> dict: + angle = np.random.uniform(min(*self.degree), max(*self.degree)) + results['img'] = mmcv.imrotate(results['img'], angle=angle) + for key in results.get('seg_fields', []): + results[key] = mmcv.imrotate(results[key], angle=angle) + return results + + def transform(self, results: dict) -> dict: + """Call function to rotate or rotate & flip image, semantic + segmentation maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Rotated or rotated & flipped results. + """ + rotate_flag = 0 + if random.random() < self.rotate_prob: + results = self.random_rotate(results) + rotate_flag = 1 + if random.random() < self.flip_prob and rotate_flag == 0: + results = self.random_rot_flip(results) + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(rotate_prob={self.rotate_prob}, ' \ + f'flip_prob={self.flip_prob}, ' \ + f'degree={self.degree})' + return repr_str + + +@TRANSFORMS.register_module() +class RandomFlip(MMCV_RandomFlip): + """Flip the image & bbox & segmentation map. Added or Updated + keys: flip, flip_direction, img, gt_bboxes, gt_seg_map, and gt_depth_map. + There are 3 flip modes: + + - ``prob`` is float, ``direction`` is string: the image will be + ``direction``ly flipped with probability of ``prob`` . + E.g., ``prob=0.5``, ``direction='horizontal'``, + then image will be horizontally flipped with probability of 0.5. + + - ``prob`` is float, ``direction`` is list of string: the image will + be ``direction[i]``ly flipped with probability of + ``prob/len(direction)``. + E.g., ``prob=0.5``, ``direction=['horizontal', 'vertical']``, + then image will be horizontally flipped with probability of 0.25, + vertically with probability of 0.25. + + - ``prob`` is list of float, ``direction`` is list of string: + given ``len(prob) == len(direction)``, the image will + be ``direction[i]``ly flipped with probability of ``prob[i]``. + E.g., ``prob=[0.3, 0.5]``, ``direction=['horizontal', + 'vertical']``, then image will be horizontally flipped with + probability of 0.3, vertically with probability of 0.5. + + Required Keys: + + - img + - gt_bboxes (optional) + - gt_seg_map (optional) + - gt_depth_map (optional) + + Modified Keys: + + - img + - gt_bboxes (optional) + - gt_seg_map (optional) + - gt_depth_map (optional) + + Added Keys: + + - flip + - flip_direction + - swap_seg_labels (optional) + + Args: + prob (float | list[float], optional): The flipping probability. + Defaults to None. + direction(str | list[str]): The flipping direction. Options + If input is a list, the length must equal ``prob``. Each + element in ``prob`` indicates the flip probability of + corresponding direction. Defaults to 'horizontal'. + swap_seg_labels (list, optional): The label pair need to be swapped + for ground truth, like 'left arm' and 'right arm' need to be + swapped after horizontal flipping. For example, ``[(1, 5)]``, + where 1/5 is the label of the left/right arm. Defaults to None. + """ + + def _flip(self, results: dict) -> None: + """Flip images, bounding boxes and semantic segmentation map.""" + # flip image + results['img'] = mmcv.imflip( + results['img'], direction=results['flip_direction']) + + img_shape = results['img'].shape[:2] + + # flip bboxes + if results.get('gt_bboxes', None) is not None: + results['gt_bboxes'] = self._flip_bbox(results['gt_bboxes'], + img_shape, + results['flip_direction']) + + # flip seg map + for key in results.get('seg_fields', []): + if results.get(key, None) is not None: + results[key] = self._flip_seg_map( + results[key], direction=results['flip_direction']).copy() + results['swap_seg_labels'] = self.swap_seg_labels + + +@TRANSFORMS.register_module() +class Resize(MMCV_Resize): + """Resize images & seg & depth map. + + This transform resizes the input image according to ``scale`` or + ``scale_factor``. Seg map, depth map and other relative annotations are + then resized with the same scale factor. + if ``scale`` and ``scale_factor`` are both set, it will use ``scale`` to + resize. + + Required Keys: + + - img + - gt_seg_map (optional) + - gt_depth_map (optional) + + Modified Keys: + + - img + - gt_seg_map + - gt_depth_map + + Added Keys: + + - scale + - scale_factor + - keep_ratio + + Args: + scale (int or tuple): Images scales for resizing. Defaults to None + scale_factor (float or tuple[float]): Scale factors for resizing. + Defaults to None. + keep_ratio (bool): Whether to keep the aspect ratio when resizing the + image. Defaults to False. + clip_object_border (bool): Whether to clip the objects + outside the border of the image. In some dataset like MOT17, the gt + bboxes are allowed to cross the border of images. Therefore, we + don't need to clip the gt bboxes in these cases. Defaults to True. + backend (str): Image resize backend, choices are 'cv2' and 'pillow'. + These two backends generates slightly different results. Defaults + to 'cv2'. + interpolation (str): Interpolation method, accepted values are + "nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2' + backend, "nearest", "bilinear" for 'pillow' backend. Defaults + to 'bilinear'. + """ + + def _resize_seg(self, results: dict) -> None: + """Resize semantic segmentation map with ``results['scale']``.""" + for seg_key in results.get('seg_fields', []): + if results.get(seg_key, None) is not None: + if self.keep_ratio: + gt_seg = mmcv.imrescale( + results[seg_key], + results['scale'], + interpolation='nearest', + backend=self.backend) + else: + gt_seg = mmcv.imresize( + results[seg_key], + results['scale'], + interpolation='nearest', + backend=self.backend) + results[seg_key] = gt_seg + + +@TRANSFORMS.register_module() +class RandomMosaic(BaseTransform): + """Mosaic augmentation. Given 4 images, mosaic transform combines them into + one output image. The output image is composed of the parts from each sub- + image. + + .. code:: text + + mosaic transform + center_x + +------------------------------+ + | pad | pad | + | +-----------+ | + | | | | + | | image1 |--------+ | + | | | | | + | | | image2 | | + center_y |----+-------------+-----------| + | | cropped | | + |pad | image3 | image4 | + | | | | + +----|-------------+-----------+ + | | + +-------------+ + + The mosaic transform steps are as follows: + 1. Choose the mosaic center as the intersections of 4 images + 2. Get the left top image according to the index, and randomly + sample another 3 images from the custom dataset. + 3. Sub image will be cropped if image is larger than mosaic patch + + Required Keys: + + - img + - gt_seg_map + - mix_results + + Modified Keys: + + - img + - img_shape + - ori_shape + - gt_seg_map + + Args: + prob (float): mosaic probability. + img_scale (Sequence[int]): Image size after mosaic pipeline of + a single image. The size of the output image is four times + that of a single image. The output image comprises 4 single images. + Default: (640, 640). + center_ratio_range (Sequence[float]): Center ratio range of mosaic + output. Default: (0.5, 1.5). + pad_val (int): Pad value. Default: 0. + seg_pad_val (int): Pad value of segmentation map. Default: 255. + """ + + def __init__(self, + prob, + img_scale=(640, 640), + center_ratio_range=(0.5, 1.5), + pad_val=0, + seg_pad_val=255): + assert 0 <= prob and prob <= 1 + assert isinstance(img_scale, tuple) + self.prob = prob + self.img_scale = img_scale + self.center_ratio_range = center_ratio_range + self.pad_val = pad_val + self.seg_pad_val = seg_pad_val + + @cache_randomness + def do_mosaic(self): + return np.random.rand() < self.prob + + def transform(self, results: dict) -> dict: + """Call function to make a mosaic of image. + + Args: + results (dict): Result dict. + + Returns: + dict: Result dict with mosaic transformed. + """ + mosaic = self.do_mosaic() + if mosaic: + results = self._mosaic_transform_img(results) + results = self._mosaic_transform_seg(results) + return results + + def get_indices(self, dataset: MultiImageMixDataset) -> list: + """Call function to collect indices. + + Args: + dataset (:obj:`MultiImageMixDataset`): The dataset. + + Returns: + list: indices. + """ + + indices = [random.randint(0, len(dataset)) for _ in range(3)] + return indices + + @cache_randomness + def generate_mosaic_center(self): + # mosaic center x, y + center_x = int( + random.uniform(*self.center_ratio_range) * self.img_scale[1]) + center_y = int( + random.uniform(*self.center_ratio_range) * self.img_scale[0]) + return center_x, center_y + + def _mosaic_transform_img(self, results: dict) -> dict: + """Mosaic transform function. + + Args: + results (dict): Result dict. + + Returns: + dict: Updated result dict. + """ + + assert 'mix_results' in results + if len(results['img'].shape) == 3: + c = results['img'].shape[2] + mosaic_img = np.full( + (int(self.img_scale[0] * 2), int(self.img_scale[1] * 2), c), + self.pad_val, + dtype=results['img'].dtype) + else: + mosaic_img = np.full( + (int(self.img_scale[0] * 2), int(self.img_scale[1] * 2)), + self.pad_val, + dtype=results['img'].dtype) + + # mosaic center x, y + self.center_x, self.center_y = self.generate_mosaic_center() + center_position = (self.center_x, self.center_y) + + loc_strs = ('top_left', 'top_right', 'bottom_left', 'bottom_right') + for i, loc in enumerate(loc_strs): + if loc == 'top_left': + result_patch = copy.deepcopy(results) + else: + result_patch = copy.deepcopy(results['mix_results'][i - 1]) + + img_i = result_patch['img'] + h_i, w_i = img_i.shape[:2] + # keep_ratio resize + scale_ratio_i = min(self.img_scale[0] / h_i, + self.img_scale[1] / w_i) + img_i = mmcv.imresize( + img_i, (int(w_i * scale_ratio_i), int(h_i * scale_ratio_i))) + + # compute the combine parameters + paste_coord, crop_coord = self._mosaic_combine( + loc, center_position, img_i.shape[:2][::-1]) + x1_p, y1_p, x2_p, y2_p = paste_coord + x1_c, y1_c, x2_c, y2_c = crop_coord + + # crop and paste image + mosaic_img[y1_p:y2_p, x1_p:x2_p] = img_i[y1_c:y2_c, x1_c:x2_c] + + results['img'] = mosaic_img + results['img_shape'] = mosaic_img.shape + results['ori_shape'] = mosaic_img.shape + + return results + + def _mosaic_transform_seg(self, results: dict) -> dict: + """Mosaic transform function for label annotations. + + Args: + results (dict): Result dict. + + Returns: + dict: Updated result dict. + """ + + assert 'mix_results' in results + for key in results.get('seg_fields', []): + mosaic_seg = np.full( + (int(self.img_scale[0] * 2), int(self.img_scale[1] * 2)), + self.seg_pad_val, + dtype=results[key].dtype) + + # mosaic center x, y + center_position = (self.center_x, self.center_y) + + loc_strs = ('top_left', 'top_right', 'bottom_left', 'bottom_right') + for i, loc in enumerate(loc_strs): + if loc == 'top_left': + result_patch = copy.deepcopy(results) + else: + result_patch = copy.deepcopy(results['mix_results'][i - 1]) + + gt_seg_i = result_patch[key] + h_i, w_i = gt_seg_i.shape[:2] + # keep_ratio resize + scale_ratio_i = min(self.img_scale[0] / h_i, + self.img_scale[1] / w_i) + gt_seg_i = mmcv.imresize( + gt_seg_i, + (int(w_i * scale_ratio_i), int(h_i * scale_ratio_i)), + interpolation='nearest') + + # compute the combine parameters + paste_coord, crop_coord = self._mosaic_combine( + loc, center_position, gt_seg_i.shape[:2][::-1]) + x1_p, y1_p, x2_p, y2_p = paste_coord + x1_c, y1_c, x2_c, y2_c = crop_coord + + # crop and paste image + mosaic_seg[y1_p:y2_p, x1_p:x2_p] = \ + gt_seg_i[y1_c:y2_c, x1_c:x2_c] + + results[key] = mosaic_seg + + return results + + def _mosaic_combine(self, loc: str, center_position_xy: Sequence[float], + img_shape_wh: Sequence[int]) -> tuple: + """Calculate global coordinate of mosaic image and local coordinate of + cropped sub-image. + + Args: + loc (str): Index for the sub-image, loc in ('top_left', + 'top_right', 'bottom_left', 'bottom_right'). + center_position_xy (Sequence[float]): Mixing center for 4 images, + (x, y). + img_shape_wh (Sequence[int]): Width and height of sub-image + + Returns: + tuple[tuple[float]]: Corresponding coordinate of pasting and + cropping + - paste_coord (tuple): paste corner coordinate in mosaic image. + - crop_coord (tuple): crop corner coordinate in mosaic image. + """ + + assert loc in ('top_left', 'top_right', 'bottom_left', 'bottom_right') + if loc == 'top_left': + # index0 to top left part of image + x1, y1, x2, y2 = max(center_position_xy[0] - img_shape_wh[0], 0), \ + max(center_position_xy[1] - img_shape_wh[1], 0), \ + center_position_xy[0], \ + center_position_xy[1] + crop_coord = img_shape_wh[0] - (x2 - x1), img_shape_wh[1] - ( + y2 - y1), img_shape_wh[0], img_shape_wh[1] + + elif loc == 'top_right': + # index1 to top right part of image + x1, y1, x2, y2 = center_position_xy[0], \ + max(center_position_xy[1] - img_shape_wh[1], 0), \ + min(center_position_xy[0] + img_shape_wh[0], + self.img_scale[1] * 2), \ + center_position_xy[1] + crop_coord = 0, img_shape_wh[1] - (y2 - y1), min( + img_shape_wh[0], x2 - x1), img_shape_wh[1] + + elif loc == 'bottom_left': + # index2 to bottom left part of image + x1, y1, x2, y2 = max(center_position_xy[0] - img_shape_wh[0], 0), \ + center_position_xy[1], \ + center_position_xy[0], \ + min(self.img_scale[0] * 2, center_position_xy[1] + + img_shape_wh[1]) + crop_coord = img_shape_wh[0] - (x2 - x1), 0, img_shape_wh[0], min( + y2 - y1, img_shape_wh[1]) + + else: + # index3 to bottom right part of image + x1, y1, x2, y2 = center_position_xy[0], \ + center_position_xy[1], \ + min(center_position_xy[0] + img_shape_wh[0], + self.img_scale[1] * 2), \ + min(self.img_scale[0] * 2, center_position_xy[1] + + img_shape_wh[1]) + crop_coord = 0, 0, min(img_shape_wh[0], + x2 - x1), min(y2 - y1, img_shape_wh[1]) + + paste_coord = x1, y1, x2, y2 + return paste_coord, crop_coord + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' + repr_str += f'img_scale={self.img_scale}, ' + repr_str += f'center_ratio_range={self.center_ratio_range}, ' + repr_str += f'pad_val={self.pad_val}, ' + repr_str += f'seg_pad_val={self.pad_val})' + return repr_str + + +@TRANSFORMS.register_module() +class GenerateEdge(BaseTransform): + """Generate Edge for CE2P approach. + + Edge will be used to calculate loss of + `CE2P `_. + + Modified from https://github.com/liutinglt/CE2P/blob/master/dataset/target_generation.py # noqa:E501 + + Required Keys: + + - img_shape + - gt_seg_map + + Added Keys: + - gt_edge_map (np.ndarray, uint8): The edge annotation generated from the + seg map by extracting border between different semantics. + + Args: + edge_width (int): The width of edge. Default to 3. + ignore_index (int): Index that will be ignored. Default to 255. + """ + + def __init__(self, edge_width: int = 3, ignore_index: int = 255) -> None: + super().__init__() + self.edge_width = edge_width + self.ignore_index = ignore_index + + def transform(self, results: Dict) -> Dict: + """Call function to generate edge from segmentation map. + + Args: + results (dict): Result dict. + + Returns: + dict: Result dict with edge mask. + """ + h, w = results['img_shape'] + edge = np.zeros((h, w), dtype=np.uint8) + seg_map = results['gt_seg_map'] + + # down + edge_down = edge[1:h, :] + edge_down[(seg_map[1:h, :] != seg_map[:h - 1, :]) + & (seg_map[1:h, :] != self.ignore_index) & + (seg_map[:h - 1, :] != self.ignore_index)] = 1 + # left + edge_left = edge[:, :w - 1] + edge_left[(seg_map[:, :w - 1] != seg_map[:, 1:w]) + & (seg_map[:, :w - 1] != self.ignore_index) & + (seg_map[:, 1:w] != self.ignore_index)] = 1 + # up_left + edge_upleft = edge[:h - 1, :w - 1] + edge_upleft[(seg_map[:h - 1, :w - 1] != seg_map[1:h, 1:w]) + & (seg_map[:h - 1, :w - 1] != self.ignore_index) & + (seg_map[1:h, 1:w] != self.ignore_index)] = 1 + # up_right + edge_upright = edge[:h - 1, 1:w] + edge_upright[(seg_map[:h - 1, 1:w] != seg_map[1:h, :w - 1]) + & (seg_map[:h - 1, 1:w] != self.ignore_index) & + (seg_map[1:h, :w - 1] != self.ignore_index)] = 1 + + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, + (self.edge_width, self.edge_width)) + edge = cv2.dilate(edge, kernel) + + results['gt_edge_map'] = edge + results['edge_width'] = self.edge_width + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'edge_width={self.edge_width}, ' + repr_str += f'ignore_index={self.ignore_index})' + return repr_str + + +@TRANSFORMS.register_module() +class ResizeShortestEdge(BaseTransform): + """Resize the image and mask while keeping the aspect ratio unchanged. + + Modified from https://github.com/facebookresearch/detectron2/blob/main/detectron2/data/transforms/augmentation_impl.py#L130 # noqa:E501 + Copyright (c) Facebook, Inc. and its affiliates. + Licensed under the Apache-2.0 License + + This transform attempts to scale the shorter edge to the given + `scale`, as long as the longer edge does not exceed `max_size`. + If `max_size` is reached, then downscale so that the longer + edge does not exceed `max_size`. + + Required Keys: + + - img + - gt_seg_map (optional) + + Modified Keys: + + - img + - img_shape + - gt_seg_map (optional)) + + Added Keys: + + - scale + - scale_factor + - keep_ratio + + + Args: + scale (Union[int, Tuple[int, int]]): The target short edge length. + If it's tuple, will select the min value as the short edge length. + max_size (int): The maximum allowed longest edge length. + """ + + def __init__(self, scale: Union[int, Tuple[int, int]], + max_size: int) -> None: + super().__init__() + self.scale = scale + self.max_size = max_size + + # Create a empty Resize object + self.resize = TRANSFORMS.build({ + 'type': 'Resize', + 'scale': 0, + 'keep_ratio': True + }) + + def _get_output_shape(self, img, short_edge_length) -> Tuple[int, int]: + """Compute the target image shape with the given `short_edge_length`. + + Args: + img (np.ndarray): The input image. + short_edge_length (Union[int, Tuple[int, int]]): The target short + edge length. If it's tuple, will select the min value as the + short edge length. + """ + h, w = img.shape[:2] + if isinstance(short_edge_length, int): + size = short_edge_length * 1.0 + elif isinstance(short_edge_length, tuple): + size = min(short_edge_length) * 1.0 + scale = size / min(h, w) + if h < w: + new_h, new_w = size, scale * w + else: + new_h, new_w = scale * h, size + + if max(new_h, new_w) > self.max_size: + scale = self.max_size * 1.0 / max(new_h, new_w) + new_h *= scale + new_w *= scale + + new_h = int(new_h + 0.5) + new_w = int(new_w + 0.5) + return (new_w, new_h) + + def transform(self, results: Dict) -> Dict: + self.resize.scale = self._get_output_shape(results['img'], self.scale) + return self.resize(results) + + +@TRANSFORMS.register_module() +class BioMedical3DRandomCrop(BaseTransform): + """Crop the input patch for medical image & segmentation mask. + + Required Keys: + + - img (np.ndarray): Biomedical image with shape (N, Z, Y, X), + N is the number of modalities, and data type is float32. + - gt_seg_map (np.ndarray, optional): Biomedical semantic segmentation mask + with shape (Z, Y, X). + + Modified Keys: + + - img + - img_shape + - gt_seg_map (optional) + + Args: + crop_shape (Union[int, Tuple[int, int, int]]): Expected size after + cropping with the format of (z, y, x). If set to an integer, + then cropping width and height are equal to this integer. + keep_foreground (bool): If keep_foreground is True, it will sample a + voxel of foreground classes randomly, and will take it as the + center of the crop bounding-box. Default to True. + """ + + def __init__(self, + crop_shape: Union[int, Tuple[int, int, int]], + keep_foreground: bool = True): + super().__init__() + assert isinstance(crop_shape, int) or ( + isinstance(crop_shape, tuple) and len(crop_shape) == 3 + ), 'The expected crop_shape is an integer, or a tuple containing ' + 'three integers' + + if isinstance(crop_shape, int): + crop_shape = (crop_shape, crop_shape, crop_shape) + assert crop_shape[0] > 0 and crop_shape[1] > 0 and crop_shape[2] > 0 + self.crop_shape = crop_shape + self.keep_foreground = keep_foreground + + def random_sample_location(self, seg_map: np.ndarray) -> dict: + """sample foreground voxel when keep_foreground is True. + + Args: + seg_map (np.ndarray): gt seg map. + + Returns: + dict: Coordinates of selected foreground voxel. + """ + num_samples = 10000 + # at least 1% of the class voxels need to be selected, + # otherwise it may be too sparse + min_percent_coverage = 0.01 + class_locs = {} + foreground_classes = [] + all_classes = np.unique(seg_map) + for c in all_classes: + if c == 0: + # to avoid the segmentation mask full of background 0 + # and the class_locs is just void dictionary {} when it return + # there add a void list for background 0. + class_locs[c] = [] + else: + all_locs = np.argwhere(seg_map == c) + target_num_samples = min(num_samples, len(all_locs)) + target_num_samples = max( + target_num_samples, + int(np.ceil(len(all_locs) * min_percent_coverage))) + + selected = all_locs[np.random.choice( + len(all_locs), target_num_samples, replace=False)] + class_locs[c] = selected + foreground_classes.append(c) + + selected_voxel = None + if len(foreground_classes) > 0: + selected_class = np.random.choice(foreground_classes) + voxels_of_that_class = class_locs[selected_class] + selected_voxel = voxels_of_that_class[np.random.choice( + len(voxels_of_that_class))] + + return selected_voxel + + def random_generate_crop_bbox(self, margin_z: int, margin_y: int, + margin_x: int) -> tuple: + """Randomly get a crop bounding box. + + Args: + seg_map (np.ndarray): Ground truth segmentation map. + + Returns: + tuple: Coordinates of the cropped image. + """ + offset_z = np.random.randint(0, margin_z + 1) + offset_y = np.random.randint(0, margin_y + 1) + offset_x = np.random.randint(0, margin_x + 1) + crop_z1, crop_z2 = offset_z, offset_z + self.crop_shape[0] + crop_y1, crop_y2 = offset_y, offset_y + self.crop_shape[1] + crop_x1, crop_x2 = offset_x, offset_x + self.crop_shape[2] + + return crop_z1, crop_z2, crop_y1, crop_y2, crop_x1, crop_x2 + + def generate_margin(self, results: dict) -> tuple: + """Generate margin of crop bounding-box. + + If keep_foreground is True, it will sample a voxel of foreground + classes randomly, and will take it as the center of the bounding-box, + and return the margin between of the bounding-box and image. + If keep_foreground is False, it will return the difference from crop + shape and image shape. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + tuple: The margin for 3 dimensions of crop bounding-box and image. + """ + + seg_map = results['gt_seg_map'] + if self.keep_foreground: + selected_voxel = self.random_sample_location(seg_map) + if selected_voxel is None: + # this only happens if some image does not contain + # foreground voxels at all + warnings.warn(f'case does not contain any foreground classes' + f': {results["img_path"]}') + margin_z = max(seg_map.shape[0] - self.crop_shape[0], 0) + margin_y = max(seg_map.shape[1] - self.crop_shape[1], 0) + margin_x = max(seg_map.shape[2] - self.crop_shape[2], 0) + else: + margin_z = max(0, selected_voxel[0] - self.crop_shape[0] // 2) + margin_y = max(0, selected_voxel[1] - self.crop_shape[1] // 2) + margin_x = max(0, selected_voxel[2] - self.crop_shape[2] // 2) + margin_z = max( + 0, min(seg_map.shape[0] - self.crop_shape[0], margin_z)) + margin_y = max( + 0, min(seg_map.shape[1] - self.crop_shape[1], margin_y)) + margin_x = max( + 0, min(seg_map.shape[2] - self.crop_shape[2], margin_x)) + else: + margin_z = max(seg_map.shape[0] - self.crop_shape[0], 0) + margin_y = max(seg_map.shape[1] - self.crop_shape[1], 0) + margin_x = max(seg_map.shape[2] - self.crop_shape[2], 0) + + return margin_z, margin_y, margin_x + + def crop(self, img: np.ndarray, crop_bbox: tuple) -> np.ndarray: + """Crop from ``img`` + + Args: + img (np.ndarray): Original input image. + crop_bbox (tuple): Coordinates of the cropped image. + + Returns: + np.ndarray: The cropped image. + """ + crop_z1, crop_z2, crop_y1, crop_y2, crop_x1, crop_x2 = crop_bbox + if len(img.shape) == 3: + # crop seg map + img = img[crop_z1:crop_z2, crop_y1:crop_y2, crop_x1:crop_x2] + else: + # crop image + assert len(img.shape) == 4 + img = img[:, crop_z1:crop_z2, crop_y1:crop_y2, crop_x1:crop_x2] + return img + + def transform(self, results: dict) -> dict: + """Transform function to randomly crop images, semantic segmentation + maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Randomly cropped results, 'img_shape' key in result dict is + updated according to crop size. + """ + margin = self.generate_margin(results) + crop_bbox = self.random_generate_crop_bbox(*margin) + + # crop the image + img = results['img'] + results['img'] = self.crop(img, crop_bbox) + results['img_shape'] = results['img'].shape[1:] + + # crop semantic seg + seg_map = results['gt_seg_map'] + results['gt_seg_map'] = self.crop(seg_map, crop_bbox) + + return results + + def __repr__(self): + return self.__class__.__name__ + f'(crop_shape={self.crop_shape})' + + +@TRANSFORMS.register_module() +class BioMedicalGaussianNoise(BaseTransform): + """Add random Gaussian noise to image. + + Modified from https://github.com/MIC-DKFZ/batchgenerators/blob/7651ece69faf55263dd582a9f5cbd149ed9c3ad0/batchgenerators/transforms/noise_transforms.py#L53 # noqa:E501 + + Copyright (c) German Cancer Research Center (DKFZ) + Licensed under the Apache License, Version 2.0 + + Required Keys: + + - img (np.ndarray): Biomedical image with shape (N, Z, Y, X), + N is the number of modalities, and data type is float32. + + Modified Keys: + + - img + + Args: + prob (float): Probability to add Gaussian noise for + each sample. Default to 0.1. + mean (float): Mean or “centre” of the distribution. Default to 0.0. + std (float): Standard deviation of distribution. Default to 0.1. + """ + + def __init__(self, + prob: float = 0.1, + mean: float = 0.0, + std: float = 0.1) -> None: + super().__init__() + assert 0.0 <= prob <= 1.0 and std >= 0.0 + self.prob = prob + self.mean = mean + self.std = std + + def transform(self, results: Dict) -> Dict: + """Call function to add random Gaussian noise to image. + + Args: + results (dict): Result dict. + + Returns: + dict: Result dict with random Gaussian noise. + """ + if np.random.rand() < self.prob: + rand_std = np.random.uniform(0, self.std) + noise = np.random.normal( + self.mean, rand_std, size=results['img'].shape) + # noise is float64 array, convert to the results['img'].dtype + noise = noise.astype(results['img'].dtype) + results['img'] = results['img'] + noise + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' + repr_str += f'mean={self.mean}, ' + repr_str += f'std={self.std})' + return repr_str + + +@TRANSFORMS.register_module() +class BioMedicalGaussianBlur(BaseTransform): + """Add Gaussian blur with random sigma to image. + + Modified from https://github.com/MIC-DKFZ/batchgenerators/blob/7651ece69faf55263dd582a9f5cbd149ed9c3ad0/batchgenerators/transforms/noise_transforms.py#L81 # noqa:E501 + + Copyright (c) German Cancer Research Center (DKFZ) + Licensed under the Apache License, Version 2.0 + + Required Keys: + + - img (np.ndarray): Biomedical image with shape (N, Z, Y, X), + N is the number of modalities, and data type is float32. + + Modified Keys: + + - img + + Args: + sigma_range (Tuple[float, float]|float): range to randomly + select sigma value. Default to (0.5, 1.0). + prob (float): Probability to apply Gaussian blur + for each sample. Default to 0.2. + prob_per_channel (float): Probability to apply Gaussian blur + for each channel (axis N of the image). Default to 0.5. + different_sigma_per_channel (bool): whether to use different + sigma for each channel (axis N of the image). Default to True. + different_sigma_per_axis (bool): whether to use different + sigma for axis Z, X and Y of the image. Default to True. + """ + + def __init__(self, + sigma_range: Tuple[float, float] = (0.5, 1.0), + prob: float = 0.2, + prob_per_channel: float = 0.5, + different_sigma_per_channel: bool = True, + different_sigma_per_axis: bool = True) -> None: + super().__init__() + assert 0.0 <= prob <= 1.0 + assert 0.0 <= prob_per_channel <= 1.0 + assert isinstance(sigma_range, Sequence) and len(sigma_range) == 2 + self.sigma_range = sigma_range + self.prob = prob + self.prob_per_channel = prob_per_channel + self.different_sigma_per_channel = different_sigma_per_channel + self.different_sigma_per_axis = different_sigma_per_axis + + def _get_valid_sigma(self, value_range) -> Tuple[float, ...]: + """Ensure the `value_range` to be either a single value or a sequence + of two values. If the `value_range` is a sequence, generate a random + value with `[value_range[0], value_range[1]]` based on uniform + sampling. + + Modified from https://github.com/MIC-DKFZ/batchgenerators/blob/7651ece69faf55263dd582a9f5cbd149ed9c3ad0/batchgenerators/augmentations/utils.py#L625 # noqa:E501 + + Args: + value_range (tuple|list|float|int): the input value range + """ + if (isinstance(value_range, (list, tuple))): + if (value_range[0] == value_range[1]): + value = value_range[0] + else: + orig_type = type(value_range[0]) + value = np.random.uniform(value_range[0], value_range[1]) + value = orig_type(value) + return value + + def _gaussian_blur(self, data_sample: np.ndarray) -> np.ndarray: + """Random generate sigma and apply Gaussian Blur to the data + Args: + data_sample (np.ndarray): data sample with multiple modalities, + the data shape is (N, Z, Y, X) + """ + sigma = None + for c in range(data_sample.shape[0]): + if np.random.rand() < self.prob_per_channel: + # if no `sigma` is generated, generate one + # if `self.different_sigma_per_channel` is True, + # re-generate random sigma for each channel + if (sigma is None or self.different_sigma_per_channel): + if (not self.different_sigma_per_axis): + sigma = self._get_valid_sigma(self.sigma_range) + else: + sigma = [ + self._get_valid_sigma(self.sigma_range) + for _ in data_sample.shape[1:] + ] + # apply gaussian filter with `sigma` + data_sample[c] = gaussian_filter( + data_sample[c], sigma, order=0) + return data_sample + + def transform(self, results: Dict) -> Dict: + """Call function to add random Gaussian blur to image. + + Args: + results (dict): Result dict. + + Returns: + dict: Result dict with random Gaussian noise. + """ + if np.random.rand() < self.prob: + results['img'] = self._gaussian_blur(results['img']) + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' + repr_str += f'prob_per_channel={self.prob_per_channel}, ' + repr_str += f'sigma_range={self.sigma_range}, ' + repr_str += 'different_sigma_per_channel=' \ + f'{self.different_sigma_per_channel}, ' + repr_str += 'different_sigma_per_axis=' \ + f'{self.different_sigma_per_axis})' + return repr_str + + +@TRANSFORMS.register_module() +class BioMedicalRandomGamma(BaseTransform): + """Using random gamma correction to process the biomedical image. + + Modified from + https://github.com/MIC-DKFZ/batchgenerators/blob/master/batchgenerators/transforms/color_transforms.py#L132 # noqa:E501 + With licence: Apache 2.0 + + Required Keys: + + - img (np.ndarray): Biomedical image with shape (N, Z, Y, X), + N is the number of modalities, and data type is float32. + + Modified Keys: + - img + + Args: + prob (float): The probability to perform this transform. Default: 0.5. + gamma_range (Tuple[float]): Range of gamma values. Default: (0.5, 2). + invert_image (bool): Whether invert the image before applying gamma + augmentation. Default: False. + per_channel (bool): Whether perform the transform each channel + individually. Default: False + retain_stats (bool): Gamma transformation will alter the mean and std + of the data in the patch. If retain_stats=True, the data will be + transformed to match the mean and standard deviation before gamma + augmentation. Default: False. + """ + + def __init__(self, + prob: float = 0.5, + gamma_range: Tuple[float] = (0.5, 2), + invert_image: bool = False, + per_channel: bool = False, + retain_stats: bool = False): + assert 0 <= prob and prob <= 1 + assert isinstance(gamma_range, tuple) and len(gamma_range) == 2 + assert isinstance(invert_image, bool) + assert isinstance(per_channel, bool) + assert isinstance(retain_stats, bool) + self.prob = prob + self.gamma_range = gamma_range + self.invert_image = invert_image + self.per_channel = per_channel + self.retain_stats = retain_stats + + @cache_randomness + def _do_gamma(self): + """Whether do adjust gamma for image.""" + return np.random.rand() < self.prob + + def _adjust_gamma(self, img: np.array): + """Gamma adjustment for image. + + Args: + img (np.array): Input image before gamma adjust. + + Returns: + np.arrays: Image after gamma adjust. + """ + + if self.invert_image: + img = -img + + def _do_adjust(img): + if retain_stats_here: + img_mean = img.mean() + img_std = img.std() + if np.random.random() < 0.5 and self.gamma_range[0] < 1: + gamma = np.random.uniform(self.gamma_range[0], 1) + else: + gamma = np.random.uniform( + max(self.gamma_range[0], 1), self.gamma_range[1]) + img_min = img.min() + img_range = img.max() - img_min # range + img = np.power(((img - img_min) / float(img_range + 1e-7)), + gamma) * img_range + img_min + if retain_stats_here: + img = img - img.mean() + img = img / (img.std() + 1e-8) * img_std + img = img + img_mean + return img + + if not self.per_channel: + retain_stats_here = self.retain_stats + img = _do_adjust(img) + else: + for c in range(img.shape[0]): + img[c] = _do_adjust(img[c]) + if self.invert_image: + img = -img + return img + + def transform(self, results: dict) -> dict: + """Call function to perform random gamma correction + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Result dict with random gamma correction performed. + """ + do_gamma = self._do_gamma() + + if do_gamma: + results['img'] = self._adjust_gamma(results['img']) + else: + pass + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, ' + repr_str += f'gamma_range={self.gamma_range},' + repr_str += f'invert_image={self.invert_image},' + repr_str += f'per_channel={self.per_channel},' + repr_str += f'retain_stats={self.retain_stats}' + return repr_str + + +@TRANSFORMS.register_module() +class BioMedical3DPad(BaseTransform): + """Pad the biomedical 3d image & biomedical 3d semantic segmentation maps. + + Required Keys: + + - img (np.ndarry): Biomedical image with shape (N, Z, Y, X) by default, + N is the number of modalities. + - gt_seg_map (np.ndarray, optional): Biomedical seg map with shape + (Z, Y, X) by default. + + Modified Keys: + + - img (np.ndarry): Biomedical image with shape (N, Z, Y, X) by default, + N is the number of modalities. + - gt_seg_map (np.ndarray, optional): Biomedical seg map with shape + (Z, Y, X) by default. + + Added Keys: + + - pad_shape (Tuple[int, int, int]): The padded shape. + + Args: + pad_shape (Tuple[int, int, int]): Fixed padding size. + Expected padding shape (Z, Y, X). + pad_val (float): Padding value for biomedical image. + The padding mode is set to "constant". The value + to be filled in padding area. Default: 0. + seg_pad_val (int): Padding value for biomedical 3d semantic + segmentation maps. The padding mode is set to "constant". + The value to be filled in padding area. Default: 0. + """ + + def __init__(self, + pad_shape: Tuple[int, int, int], + pad_val: float = 0., + seg_pad_val: int = 0) -> None: + + # check pad_shape + assert pad_shape is not None + if not isinstance(pad_shape, tuple): + assert len(pad_shape) == 3 + + self.pad_shape = pad_shape + self.pad_val = pad_val + self.seg_pad_val = seg_pad_val + + def _pad_img(self, results: dict) -> None: + """Pad images according to ``self.pad_shape`` + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: The dict contains the padded image and shape + information. + """ + padded_img = self._to_pad( + results['img'], pad_shape=self.pad_shape, pad_val=self.pad_val) + + results['img'] = padded_img + results['pad_shape'] = padded_img.shape[1:] + + def _pad_seg(self, results: dict) -> None: + """Pad semantic segmentation map according to ``self.pad_shape`` if + ``gt_seg_map`` is not None in results dict. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Update the padded gt seg map in dict. + """ + if results.get('gt_seg_map', None) is not None: + pad_gt_seg = self._to_pad( + results['gt_seg_map'][None, ...], + pad_shape=results['pad_shape'], + pad_val=self.seg_pad_val) + results['gt_seg_map'] = pad_gt_seg[1:] + + @staticmethod + def _to_pad(img: np.ndarray, + pad_shape: Tuple[int, int, int], + pad_val: Union[int, float] = 0) -> np.ndarray: + """Pad the given 3d image to a certain shape with specified padding + value. + + Args: + img (ndarray): Biomedical image with shape (N, Z, Y, X) + to be padded. N is the number of modalities. + pad_shape (Tuple[int,int,int]): Expected padding shape (Z, Y, X). + pad_val (float, int): Values to be filled in padding areas + and the padding_mode is set to 'constant'. Default: 0. + + Returns: + ndarray: The padded image. + """ + # compute pad width + d = max(pad_shape[0] - img.shape[1], 0) + pad_d = (d // 2, d - d // 2) + h = max(pad_shape[1] - img.shape[2], 0) + pad_h = (h // 2, h - h // 2) + w = max(pad_shape[2] - img.shape[2], 0) + pad_w = (w // 2, w - w // 2) + + pad_list = [(0, 0), pad_d, pad_h, pad_w] + + img = np.pad(img, pad_list, mode='constant', constant_values=pad_val) + return img + + def transform(self, results: dict) -> dict: + """Call function to pad images, semantic segmentation maps. + + Args: + results (dict): Result dict from loading pipeline. + + Returns: + dict: Updated result dict. + """ + self._pad_img(results) + self._pad_seg(results) + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'pad_shape={self.pad_shape}, ' + repr_str += f'pad_val={self.pad_val}), ' + repr_str += f'seg_pad_val={self.seg_pad_val})' + return repr_str + + +@TRANSFORMS.register_module() +class BioMedical3DRandomFlip(BaseTransform): + """Flip biomedical 3D images and segmentations. + + Modified from https://github.com/MIC-DKFZ/batchgenerators/blob/master/batchgenerators/transforms/spatial_transforms.py # noqa:E501 + + Copyright 2021 Division of + Medical Image Computing, German Cancer Research Center (DKFZ) and Applied + Computer Vision Lab, Helmholtz Imaging Platform. + Licensed under the Apache-2.0 License. + + Required Keys: + + - img (np.ndarry): Biomedical image with shape (N, Z, Y, X) by default, + N is the number of modalities. + - gt_seg_map (np.ndarray, optional): Biomedical seg map with shape + (Z, Y, X) by default. + + Modified Keys: + + - img (np.ndarry): Biomedical image with shape (N, Z, Y, X) by default, + N is the number of modalities. + - gt_seg_map (np.ndarray, optional): Biomedical seg map with shape + (Z, Y, X) by default. + + Added Keys: + + - do_flip + - flip_axes + + Args: + prob (float): Flipping probability. + axes (Tuple[int, ...]): Flipping axes with order 'ZXY'. + swap_label_pairs (Optional[List[Tuple[int, int]]]): + The segmentation label pairs that are swapped when flipping. + """ + + def __init__(self, + prob: float, + axes: Tuple[int, ...], + swap_label_pairs: Optional[List[Tuple[int, int]]] = None): + self.prob = prob + self.axes = axes + self.swap_label_pairs = swap_label_pairs + assert prob >= 0 and prob <= 1 + if axes is not None: + assert max(axes) <= 2 + + @staticmethod + def _flip(img, direction: Tuple[bool, bool, bool]) -> np.ndarray: + if direction[0]: + img[:, :] = img[:, ::-1] + if direction[1]: + img[:, :, :] = img[:, :, ::-1] + if direction[2]: + img[:, :, :, :] = img[:, :, :, ::-1] + return img + + def _do_flip(self, img: np.ndarray) -> Tuple[bool, bool, bool]: + """Call function to determine which axis to flip. + + Args: + img (np.ndarry): Image or segmentation map array. + Returns: + tuple: Flip action, whether to flip on the z, x, and y axes. + """ + flip_c, flip_x, flip_y = False, False, False + if self.axes is not None: + flip_c = 0 in self.axes and np.random.rand() < self.prob + flip_x = 1 in self.axes and np.random.rand() < self.prob + if len(img.shape) == 4: + flip_y = 2 in self.axes and np.random.rand() < self.prob + return flip_c, flip_x, flip_y + + def _swap_label(self, seg: np.ndarray) -> np.ndarray: + out = seg.copy() + for first, second in self.swap_label_pairs: + first_area = (seg == first) + second_area = (seg == second) + out[first_area] = second + out[second_area] = first + return out + + def transform(self, results: Dict) -> Dict: + """Call function to flip and swap pair labels. + + Args: + results (dict): Result dict. + Returns: + dict: Flipped results, 'do_flip', 'flip_axes' keys are added into + result dict. + """ + # get actual flipped axis + if 'do_flip' not in results: + results['do_flip'] = self._do_flip(results['img']) + if 'flip_axes' not in results: + results['flip_axes'] = self.axes + # flip image + results['img'] = self._flip( + results['img'], direction=results['do_flip']) + # flip seg + if results['gt_seg_map'] is not None: + if results['gt_seg_map'].shape != results['img'].shape: + results['gt_seg_map'] = results['gt_seg_map'][None, :] + results['gt_seg_map'] = self._flip( + results['gt_seg_map'], direction=results['do_flip']) + results['gt_seg_map'] = results['gt_seg_map'].squeeze() + # swap label pairs + if self.swap_label_pairs is not None: + results['gt_seg_map'] = self._swap_label(results['gt_seg_map']) + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(prob={self.prob}, axes={self.axes}, ' \ + f'swap_label_pairs={self.swap_label_pairs})' + return repr_str + + +@TRANSFORMS.register_module() +class Albu(BaseTransform): + """Albumentation augmentation. Adds custom transformations from + Albumentations library. Please, visit + `https://albumentations.readthedocs.io` to get more information. An example + of ``transforms`` is as followed: + + .. code-block:: + [ + dict( + type='ShiftScaleRotate', + shift_limit=0.0625, + scale_limit=0.0, + rotate_limit=0, + interpolation=1, + p=0.5), + dict( + type='RandomBrightnessContrast', + brightness_limit=[0.1, 0.3], + contrast_limit=[0.1, 0.3], + p=0.2), + dict(type='ChannelShuffle', p=0.1), + dict( + type='OneOf', + transforms=[ + dict(type='Blur', blur_limit=3, p=1.0), + dict(type='MedianBlur', blur_limit=3, p=1.0) + ], + p=0.1), + ] + Args: + transforms (list[dict]): A list of albu transformations + keymap (dict): Contains {'input key':'albumentation-style key'} + additional_targets(dict): Allows applying same augmentations to \ + multiple objects of same type. + update_pad_shape (bool): Whether to update padding shape according to \ + the output shape of the last transform + bgr_to_rgb (bool): Whether to convert the band order to RGB + """ + + def __init__(self, + transforms: List[dict], + keymap: Optional[dict] = None, + additional_targets: Optional[dict] = None, + update_pad_shape: bool = False, + bgr_to_rgb: bool = True): + if not ALBU_INSTALLED: + raise ImportError( + 'albumentations is not installed, ' + 'we suggest install albumentation by ' + '"pip install albumentations>=0.3.2 --no-binary qudida,albumentations"' # noqa + ) + + # Args will be modified later, copying it will be safer + transforms = copy.deepcopy(transforms) + + self.transforms = transforms + self.keymap = keymap + self.additional_targets = additional_targets + self.update_pad_shape = update_pad_shape + self.bgr_to_rgb = bgr_to_rgb + + self.aug = Compose([self.albu_builder(t) for t in self.transforms], + additional_targets=self.additional_targets) + + if not keymap: + self.keymap_to_albu = {'img': 'image', 'gt_seg_map': 'mask'} + else: + self.keymap_to_albu = copy.deepcopy(keymap) + self.keymap_back = {v: k for k, v in self.keymap_to_albu.items()} + + def albu_builder(self, cfg: dict) -> object: + """Build a callable object from a dict containing albu arguments. + + Args: + cfg (dict): Config dict. It should at least contain the key "type". + + Returns: + Callable: A callable object. + """ + + assert isinstance(cfg, dict) and 'type' in cfg + args = cfg.copy() + + obj_type = args.pop('type') + if mmengine.is_str(obj_type): + if not ALBU_INSTALLED: + raise ImportError( + 'albumentations is not installed, ' + 'we suggest install albumentation by ' + '"pip install albumentations>=0.3.2 --no-binary qudida,albumentations"' # noqa + ) + obj_cls = getattr(albumentations, obj_type) + elif inspect.isclass(obj_type): + obj_cls = obj_type + else: + raise TypeError( + f'type must be a valid type or str, but got {type(obj_type)}') + + if 'transforms' in args: + args['transforms'] = [ + self.albu_builder(t) for t in args['transforms'] + ] + + return obj_cls(**args) + + @staticmethod + def mapper(d: dict, keymap: dict): + """Dictionary mapper. + + Renames keys according to keymap provided. + Args: + d (dict): old dict + keymap (dict): {'old_key':'new_key'} + Returns: + dict: new dict. + """ + + updated_dict = {} + for k, _ in zip(d.keys(), d.values()): + new_k = keymap.get(k, k) + updated_dict[new_k] = d[k] + return updated_dict + + def transform(self, results): + # dict to albumentations format + results = self.mapper(results, self.keymap_to_albu) + + # Convert to RGB since Albumentations works with RGB images + if self.bgr_to_rgb: + results['image'] = cv2.cvtColor(results['image'], + cv2.COLOR_BGR2RGB) + if self.additional_targets: + for key, value in self.additional_targets.items(): + if value == 'image': + results[key] = cv2.cvtColor(results[key], + cv2.COLOR_BGR2RGB) + + # Apply Transform + results = self.aug(**results) + + # Convert back to BGR + if self.bgr_to_rgb: + results['image'] = cv2.cvtColor(results['image'], + cv2.COLOR_RGB2BGR) + if self.additional_targets: + for key, value in self.additional_targets.items(): + if value == 'image': + results[key] = cv2.cvtColor(results['image2'], + cv2.COLOR_RGB2BGR) + + # back to the original format + results = self.mapper(results, self.keymap_back) + + # update final shape + if self.update_pad_shape: + results['pad_shape'] = results['img'].shape + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + f'(transforms={self.transforms})' + return repr_str + + +@TRANSFORMS.register_module() +class ConcatCDInput(BaseTransform): + """Concat images for change detection. + + Required Keys: + + - img + - img2 + + Args: + input_keys (tuple): Input image keys for change detection. + Default: ('img', 'img2'). + """ + + def __init__(self, input_keys=('img', 'img2')): + self.input_keys = input_keys + + def transform(self, results: dict) -> dict: + img = [] + for input_key in self.input_keys: + img.append(results.pop(input_key)) + results['img'] = np.concatenate(img, axis=2) + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(input_keys={self.input_keys}, ' + return repr_str + + +@TRANSFORMS.register_module() +class RandomDepthMix(BaseTransform): + """This class implements the RandomDepthMix transform. + + Args: + prob (float): Probability of applying the transformation. + Defaults to 0.25. + mix_scale_ratio (float): Ratio to scale the mix width. + Defaults to 0.75. + """ + + def __init__( + self, + prob: float = 0.25, + mix_scale_ratio: float = 0.75, + ): + super().__init__() + + self.prob = prob + self.mix_scale_ratio = mix_scale_ratio + + def transform(self, results: dict) -> dict: + if random.random() > self.prob: + return results + + h, w = results['img_shape'][:2] + left = int(w * random.random()) + width_ratio = self.mix_scale_ratio * random.random() + width = int(max(1, (w - left) * width_ratio)) + + img = results['img'] + depth_rescale_factor = results.get('depth_rescale_factor', 1) + depth_map = results['gt_depth_map'] / depth_rescale_factor + + if img.ndim == 3: + for c in range(img.shape[-1]): + img[:, left:left + width, c] = depth_map[:, left:left + width] + elif img.ndim == 2: + img[:, left:left + width] = depth_map[:, left:left + width] + else: + raise ValueError(f'Invalid image shape ({img.shape})') + + results['img'] = img + return results diff --git a/Seg_All_In_One_MMSeg/mmseg/datasets/voc.py b/Seg_All_In_One_MMSeg/mmseg/datasets/voc.py new file mode 100644 index 0000000..5e5d602 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/datasets/voc.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp + +import mmengine.fileio as fileio + +from mmseg.registry import DATASETS +from .basesegdataset import BaseSegDataset + + +@DATASETS.register_module() +class PascalVOCDataset(BaseSegDataset): + """Pascal VOC dataset. + + Args: + split (str): Split txt file for Pascal VOC. + """ + METAINFO = dict( + classes=('background', 'aeroplane', 'bicycle', 'bird', 'boat', + 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', + 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', + 'sofa', 'train', 'tvmonitor'), + palette=[[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0], + [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128], + [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0], + [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128], + [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0], + [0, 64, 128]]) + + def __init__(self, + ann_file, + img_suffix='.jpg', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + ann_file=ann_file, + **kwargs) + assert fileio.exists(self.data_prefix['img_path'], + self.backend_args) and osp.isfile(self.ann_file) diff --git a/Seg_All_In_One_MMSeg/mmseg/engine/__init__.py b/Seg_All_In_One_MMSeg/mmseg/engine/__init__.py new file mode 100644 index 0000000..98139a0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/engine/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .hooks import SegVisualizationHook +from .optimizers import (ForceDefaultOptimWrapperConstructor, + LayerDecayOptimizerConstructor, + LearningRateDecayOptimizerConstructor) +from .schedulers import PolyLRRatio + +__all__ = [ + 'LearningRateDecayOptimizerConstructor', 'LayerDecayOptimizerConstructor', + 'SegVisualizationHook', 'PolyLRRatio', + 'ForceDefaultOptimWrapperConstructor' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/engine/hooks/__init__.py b/Seg_All_In_One_MMSeg/mmseg/engine/hooks/__init__.py new file mode 100644 index 0000000..c604808 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/engine/hooks/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .visualization_hook import SegVisualizationHook + +__all__ = ['SegVisualizationHook'] diff --git a/Seg_All_In_One_MMSeg/mmseg/engine/hooks/visualization_hook.py b/Seg_All_In_One_MMSeg/mmseg/engine/hooks/visualization_hook.py new file mode 100644 index 0000000..21cddde --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/engine/hooks/visualization_hook.py @@ -0,0 +1,129 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import warnings +from typing import Optional, Sequence + +import mmcv +from mmengine.fileio import get +from mmengine.hooks import Hook +from mmengine.runner import Runner +from mmengine.visualization import Visualizer + +from mmseg.registry import HOOKS +from mmseg.structures import SegDataSample + + +@HOOKS.register_module() +class SegVisualizationHook(Hook): + """Segmentation Visualization Hook. Used to visualize validation and + testing process prediction results. + + In the testing phase: + + 1. If ``show`` is True, it means that only the prediction results are + visualized without storing data, so ``vis_backends`` needs to + be excluded. + + Args: + draw (bool): whether to draw prediction results. If it is False, + it means that no drawing will be done. Defaults to False. + interval (int): The interval of visualization. Defaults to 50. + show (bool): Whether to display the drawn image. Default to False. + wait_time (float): The interval of show (s). Defaults to 0. + backend_args (dict, Optional): Arguments to instantiate a file backend. + See https://mmengine.readthedocs.io/en/latest/api/fileio.htm + for details. Defaults to None. + Notes: mmcv>=2.0.0rc4, mmengine>=0.2.0 required. + """ + + def __init__(self, + draw: bool = False, + interval: int = 50, + show: bool = False, + wait_time: float = 0., + backend_args: Optional[dict] = None): + self._visualizer: Visualizer = Visualizer.get_current_instance() + self.interval = interval + self.show = show + if self.show: + # No need to think about vis backends. + self._visualizer._vis_backends = {} + warnings.warn('The show is True, it means that only ' + 'the prediction results are visualized ' + 'without storing data, so vis_backends ' + 'needs to be excluded.') + + self.wait_time = wait_time + self.backend_args = backend_args.copy() if backend_args else None + self.draw = draw + if not self.draw: + warnings.warn('The draw is False, it means that the ' + 'hook for visualization will not take ' + 'effect. The results will NOT be ' + 'visualized or stored.') + self._test_index = 0 + + def after_val_iter(self, runner: Runner, batch_idx: int, data_batch: dict, + outputs: Sequence[SegDataSample]) -> None: + """Run after every ``self.interval`` validation iterations. + + Args: + runner (:obj:`Runner`): The runner of the validation process. + batch_idx (int): The index of the current batch in the val loop. + data_batch (dict): Data from dataloader. + outputs (Sequence[:obj:`SegDataSample`]]): A batch of data samples + that contain annotations and predictions. + """ + if self.draw is False: + return + + # There is no guarantee that the same batch of images + # is visualized for each evaluation. + total_curr_iter = runner.iter + batch_idx + + # Visualize only the first data + img_path = outputs[0].img_path + img_bytes = get(img_path, backend_args=self.backend_args) + img = mmcv.imfrombytes(img_bytes, channel_order='rgb') + window_name = f'val_{osp.basename(img_path)}' + + if total_curr_iter % self.interval == 0: + self._visualizer.add_datasample( + window_name, + img, + data_sample=outputs[0], + show=self.show, + wait_time=self.wait_time, + step=total_curr_iter) + + def after_test_iter(self, runner: Runner, batch_idx: int, data_batch: dict, + outputs: Sequence[SegDataSample]) -> None: + """Run after every testing iterations. + + Args: + runner (:obj:`Runner`): The runner of the testing process. + batch_idx (int): The index of the current batch in the val loop. + data_batch (dict): Data from dataloader. + outputs (Sequence[:obj:`SegDataSample`]): A batch of data samples + that contain annotations and predictions. + """ + if self.draw is False: + return + + for data_sample in outputs: + self._test_index += 1 + + img_path = data_sample.img_path + window_name = f'test_{osp.basename(img_path)}' + + img_path = data_sample.img_path + img_bytes = get(img_path, backend_args=self.backend_args) + img = mmcv.imfrombytes(img_bytes, channel_order='rgb') + + self._visualizer.add_datasample( + window_name, + img, + data_sample=data_sample, + show=self.show, + wait_time=self.wait_time, + step=self._test_index) diff --git a/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/__init__.py b/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/__init__.py new file mode 100644 index 0000000..e4cf587 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/__init__.py @@ -0,0 +1,9 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .force_default_constructor import ForceDefaultOptimWrapperConstructor +from .layer_decay_optimizer_constructor import ( + LayerDecayOptimizerConstructor, LearningRateDecayOptimizerConstructor) + +__all__ = [ + 'LearningRateDecayOptimizerConstructor', 'LayerDecayOptimizerConstructor', + 'ForceDefaultOptimWrapperConstructor' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/force_default_constructor.py b/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/force_default_constructor.py new file mode 100644 index 0000000..12c642a --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/force_default_constructor.py @@ -0,0 +1,255 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging +from typing import List, Optional, Union + +import torch +import torch.nn as nn +from mmengine.logging import print_log +from mmengine.optim import DefaultOptimWrapperConstructor +from mmengine.utils.dl_utils import mmcv_full_available +from mmengine.utils.dl_utils.parrots_wrapper import _BatchNorm, _InstanceNorm +from torch.nn import GroupNorm, LayerNorm + +from mmseg.registry import OPTIM_WRAPPER_CONSTRUCTORS + + +@OPTIM_WRAPPER_CONSTRUCTORS.register_module() +class ForceDefaultOptimWrapperConstructor(DefaultOptimWrapperConstructor): + """Default constructor with forced optimizer settings. + + This constructor extends the default constructor to add an option for + forcing default optimizer settings. This is useful for ensuring that + certain parameters or layers strictly adhere to pre-defined default + settings, regardless of any custom settings specified. + + By default, each parameter share the same optimizer settings, and we + provide an argument ``paramwise_cfg`` to specify parameter-wise settings. + It is a dict and may contain various fields like 'custom_keys', + 'bias_lr_mult', etc., as well as the additional field + `force_default_settings` which allows for enforcing default settings on + optimizer parameters. + + - ``custom_keys`` (dict): Specified parameters-wise settings by keys. If + one of the keys in ``custom_keys`` is a substring of the name of one + parameter, then the setting of the parameter will be specified by + ``custom_keys[key]`` and other setting like ``bias_lr_mult`` etc. will + be ignored. It should be noted that the aforementioned ``key`` is the + longest key that is a substring of the name of the parameter. If there + are multiple matched keys with the same length, then the key with lower + alphabet order will be chosen. + ``custom_keys[key]`` should be a dict and may contain fields ``lr_mult`` + and ``decay_mult``. See Example 2 below. + - ``bias_lr_mult`` (float): It will be multiplied to the learning + rate for all bias parameters (except for those in normalization + layers and offset layers of DCN). + - ``bias_decay_mult`` (float): It will be multiplied to the weight + decay for all bias parameters (except for those in + normalization layers, depthwise conv layers, offset layers of DCN). + - ``norm_decay_mult`` (float): It will be multiplied to the weight + decay for all weight and bias parameters of normalization + layers. + - ``flat_decay_mult`` (float): It will be multiplied to the weight + decay for all one-dimensional parameters + - ``dwconv_decay_mult`` (float): It will be multiplied to the weight + decay for all weight and bias parameters of depthwise conv + layers. + - ``dcn_offset_lr_mult`` (float): It will be multiplied to the learning + rate for parameters of offset layer in the deformable convs + of a model. + - ``bypass_duplicate`` (bool): If true, the duplicate parameters + would not be added into optimizer. Defaults to False. + - ``force_default_settings`` (bool): If true, this will override any + custom settings defined by ``custom_keys`` and enforce the use of + default settings for optimizer parameters like ``bias_lr_mult``. + This is particularly useful when you want to ensure that certain layers + or parameters adhere strictly to the pre-defined default settings. + + Note: + + 1. If the option ``dcn_offset_lr_mult`` is used, the constructor will + override the effect of ``bias_lr_mult`` in the bias of offset layer. + So be careful when using both ``bias_lr_mult`` and + ``dcn_offset_lr_mult``. If you wish to apply both of them to the offset + layer in deformable convs, set ``dcn_offset_lr_mult`` to the original + ``dcn_offset_lr_mult`` * ``bias_lr_mult``. + + 2. If the option ``dcn_offset_lr_mult`` is used, the constructor will + apply it to all the DCN layers in the model. So be careful when the + model contains multiple DCN layers in places other than backbone. + + 3. When the option ``force_default_settings`` is true, it will override + any custom settings provided in ``custom_keys``. This ensures that the + default settings for the optimizer parameters are used. + + Args: + optim_wrapper_cfg (dict): The config dict of the optimizer wrapper. + + Required fields of ``optim_wrapper_cfg`` are + + - ``type``: class name of the OptimizerWrapper + - ``optimizer``: The configuration of optimizer. + + Optional fields of ``optim_wrapper_cfg`` are + + - any arguments of the corresponding optimizer wrapper type, + e.g., accumulative_counts, clip_grad, etc. + + Required fields of ``optimizer`` are + + - `type`: class name of the optimizer. + + Optional fields of ``optimizer`` are + + - any arguments of the corresponding optimizer type, e.g., + lr, weight_decay, momentum, etc. + + paramwise_cfg (dict, optional): Parameter-wise options. + + Example 1: + >>> model = torch.nn.modules.Conv1d(1, 1, 1) + >>> optim_wrapper_cfg = dict( + >>> dict(type='OptimWrapper', optimizer=dict(type='SGD', lr=0.01, + >>> momentum=0.9, weight_decay=0.0001)) + >>> paramwise_cfg = dict(norm_decay_mult=0.) + >>> optim_wrapper_builder = DefaultOptimWrapperConstructor( + >>> optim_wrapper_cfg, paramwise_cfg) + >>> optim_wrapper = optim_wrapper_builder(model) + + Example 2: + >>> # assume model have attribute model.backbone and model.cls_head + >>> optim_wrapper_cfg = dict(type='OptimWrapper', optimizer=dict( + >>> type='SGD', lr=0.01, weight_decay=0.95)) + >>> paramwise_cfg = dict(custom_keys={ + >>> 'backbone': dict(lr_mult=0.1, decay_mult=0.9)}) + >>> optim_wrapper_builder = DefaultOptimWrapperConstructor( + >>> optim_wrapper_cfg, paramwise_cfg) + >>> optim_wrapper = optim_wrapper_builder(model) + >>> # Then the `lr` and `weight_decay` for model.backbone is + >>> # (0.01 * 0.1, 0.95 * 0.9). `lr` and `weight_decay` for + >>> # model.cls_head is (0.01, 0.95). + """ + + def add_params(self, + params: List[dict], + module: nn.Module, + prefix: str = '', + is_dcn_module: Optional[Union[int, float]] = None) -> None: + """Add all parameters of module to the params list. + + The parameters of the given module will be added to the list of param + groups, with specific rules defined by paramwise_cfg. + + Args: + params (list[dict]): A list of param groups, it will be modified + in place. + module (nn.Module): The module to be added. + prefix (str): The prefix of the module + is_dcn_module (int|float|None): If the current module is a + submodule of DCN, `is_dcn_module` will be passed to + control conv_offset layer's learning rate. Defaults to None. + """ + # get param-wise options + custom_keys = self.paramwise_cfg.get('custom_keys', {}) + # first sort with alphabet order and then sort with reversed len of str + sorted_keys = sorted(sorted(custom_keys.keys()), key=len, reverse=True) + + bias_lr_mult = self.paramwise_cfg.get('bias_lr_mult', None) + bias_decay_mult = self.paramwise_cfg.get('bias_decay_mult', None) + norm_decay_mult = self.paramwise_cfg.get('norm_decay_mult', None) + dwconv_decay_mult = self.paramwise_cfg.get('dwconv_decay_mult', None) + flat_decay_mult = self.paramwise_cfg.get('flat_decay_mult', None) + bypass_duplicate = self.paramwise_cfg.get('bypass_duplicate', False) + dcn_offset_lr_mult = self.paramwise_cfg.get('dcn_offset_lr_mult', None) + force_default_settings = self.paramwise_cfg.get( + 'force_default_settings', False) + + # special rules for norm layers and depth-wise conv layers + is_norm = isinstance(module, + (_BatchNorm, _InstanceNorm, GroupNorm, LayerNorm)) + is_dwconv = ( + isinstance(module, torch.nn.Conv2d) + and module.in_channels == module.groups) + + for name, param in module.named_parameters(recurse=False): + param_group = {'params': [param]} + if bypass_duplicate and self._is_in(param_group, params): + print_log( + f'{prefix} is duplicate. It is skipped since ' + f'bypass_duplicate={bypass_duplicate}', + logger='current', + level=logging.WARNING) + continue + if not param.requires_grad: + params.append(param_group) + continue + + # if the parameter match one of the custom keys, ignore other rules + is_custom = False + for key in sorted_keys: + if key in f'{prefix}.{name}': + is_custom = True + lr_mult = custom_keys[key].get('lr_mult', 1.) + param_group['lr'] = self.base_lr * lr_mult + if self.base_wd is not None: + decay_mult = custom_keys[key].get('decay_mult', 1.) + param_group['weight_decay'] = self.base_wd * decay_mult + # add custom settings to param_group + for k, v in custom_keys[key].items(): + param_group[k] = v + break + + if not is_custom or force_default_settings: + # bias_lr_mult affects all bias parameters + # except for norm.bias dcn.conv_offset.bias + if name == 'bias' and not ( + is_norm or is_dcn_module) and bias_lr_mult is not None: + param_group['lr'] = self.base_lr * bias_lr_mult + + if (prefix.find('conv_offset') != -1 and is_dcn_module + and dcn_offset_lr_mult is not None + and isinstance(module, torch.nn.Conv2d)): + # deal with both dcn_offset's bias & weight + param_group['lr'] = self.base_lr * dcn_offset_lr_mult + + # apply weight decay policies + if self.base_wd is not None: + # norm decay + if is_norm and norm_decay_mult is not None: + param_group[ + 'weight_decay'] = self.base_wd * norm_decay_mult + # bias lr and decay + elif (name == 'bias' and not is_dcn_module + and bias_decay_mult is not None): + param_group[ + 'weight_decay'] = self.base_wd * bias_decay_mult + # depth-wise conv + elif is_dwconv and dwconv_decay_mult is not None: + param_group[ + 'weight_decay'] = self.base_wd * dwconv_decay_mult + # flatten parameters except dcn offset + elif (param.ndim == 1 and not is_dcn_module + and flat_decay_mult is not None): + param_group[ + 'weight_decay'] = self.base_wd * flat_decay_mult + params.append(param_group) + for key, value in param_group.items(): + if key == 'params': + continue + full_name = f'{prefix}.{name}' if prefix else name + print_log( + f'paramwise_options -- {full_name}:{key}={value}', + logger='current') + + if mmcv_full_available(): + from mmcv.ops import DeformConv2d, ModulatedDeformConv2d + is_dcn_module = isinstance(module, + (DeformConv2d, ModulatedDeformConv2d)) + else: + is_dcn_module = False + for child_name, child_mod in module.named_children(): + child_prefix = f'{prefix}.{child_name}' if prefix else child_name + self.add_params( + params, + child_mod, + prefix=child_prefix, + is_dcn_module=is_dcn_module) diff --git a/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/layer_decay_optimizer_constructor.py b/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/layer_decay_optimizer_constructor.py new file mode 100644 index 0000000..fdae3ca --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/engine/optimizers/layer_decay_optimizer_constructor.py @@ -0,0 +1,207 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +import warnings + +from mmengine.dist import get_dist_info +from mmengine.logging import print_log +from mmengine.optim import DefaultOptimWrapperConstructor + +from mmseg.registry import OPTIM_WRAPPER_CONSTRUCTORS + + +def get_layer_id_for_convnext(var_name, max_layer_id): + """Get the layer id to set the different learning rates in ``layer_wise`` + decay_type. + + Args: + var_name (str): The key of the model. + max_layer_id (int): Maximum number of backbone layers. + + Returns: + int: The id number corresponding to different learning rate in + ``LearningRateDecayOptimizerConstructor``. + """ + + if var_name in ('backbone.cls_token', 'backbone.mask_token', + 'backbone.pos_embed'): + return 0 + elif var_name.startswith('backbone.downsample_layers'): + stage_id = int(var_name.split('.')[2]) + if stage_id == 0: + layer_id = 0 + elif stage_id == 1: + layer_id = 2 + elif stage_id == 2: + layer_id = 3 + elif stage_id == 3: + layer_id = max_layer_id + return layer_id + elif var_name.startswith('backbone.stages'): + stage_id = int(var_name.split('.')[2]) + block_id = int(var_name.split('.')[3]) + if stage_id == 0: + layer_id = 1 + elif stage_id == 1: + layer_id = 2 + elif stage_id == 2: + layer_id = 3 + block_id // 3 + elif stage_id == 3: + layer_id = max_layer_id + return layer_id + else: + return max_layer_id + 1 + + +def get_stage_id_for_convnext(var_name, max_stage_id): + """Get the stage id to set the different learning rates in ``stage_wise`` + decay_type. + + Args: + var_name (str): The key of the model. + max_stage_id (int): Maximum number of backbone layers. + + Returns: + int: The id number corresponding to different learning rate in + ``LearningRateDecayOptimizerConstructor``. + """ + + if var_name in ('backbone.cls_token', 'backbone.mask_token', + 'backbone.pos_embed'): + return 0 + elif var_name.startswith('backbone.downsample_layers'): + return 0 + elif var_name.startswith('backbone.stages'): + stage_id = int(var_name.split('.')[2]) + return stage_id + 1 + else: + return max_stage_id - 1 + + +def get_layer_id_for_vit(var_name, max_layer_id): + """Get the layer id to set the different learning rates. + + Args: + var_name (str): The key of the model. + num_max_layer (int): Maximum number of backbone layers. + + Returns: + int: Returns the layer id of the key. + """ + + if var_name in ('backbone.cls_token', 'backbone.mask_token', + 'backbone.pos_embed'): + return 0 + elif var_name.startswith('backbone.patch_embed'): + return 0 + elif var_name.startswith('backbone.layers'): + layer_id = int(var_name.split('.')[2]) + return layer_id + 1 + else: + return max_layer_id - 1 + + +@OPTIM_WRAPPER_CONSTRUCTORS.register_module() +class LearningRateDecayOptimizerConstructor(DefaultOptimWrapperConstructor): + """Different learning rates are set for different layers of backbone. + + Note: Currently, this optimizer constructor is built for ConvNeXt, + BEiT and MAE. + """ + + def add_params(self, params, module, **kwargs): + """Add all parameters of module to the params list. + + The parameters of the given module will be added to the list of param + groups, with specific rules defined by paramwise_cfg. + + Args: + params (list[dict]): A list of param groups, it will be modified + in place. + module (nn.Module): The module to be added. + """ + + parameter_groups = {} + print_log(f'self.paramwise_cfg is {self.paramwise_cfg}') + num_layers = self.paramwise_cfg.get('num_layers') + 2 + decay_rate = self.paramwise_cfg.get('decay_rate') + decay_type = self.paramwise_cfg.get('decay_type', 'layer_wise') + print_log('Build LearningRateDecayOptimizerConstructor ' + f'{decay_type} {decay_rate} - {num_layers}') + weight_decay = self.base_wd + for name, param in module.named_parameters(): + if not param.requires_grad: + continue # frozen weights + if len(param.shape) == 1 or name.endswith('.bias') or name in ( + 'pos_embed', 'cls_token'): + group_name = 'no_decay' + this_weight_decay = 0. + else: + group_name = 'decay' + this_weight_decay = weight_decay + if 'layer_wise' in decay_type: + if 'ConvNeXt' in module.backbone.__class__.__name__: + layer_id = get_layer_id_for_convnext( + name, self.paramwise_cfg.get('num_layers')) + print_log(f'set param {name} as id {layer_id}') + elif 'BEiT' in module.backbone.__class__.__name__ or \ + 'MAE' in module.backbone.__class__.__name__: + layer_id = get_layer_id_for_vit(name, num_layers) + print_log(f'set param {name} as id {layer_id}') + else: + raise NotImplementedError() + elif decay_type == 'stage_wise': + if 'ConvNeXt' in module.backbone.__class__.__name__: + layer_id = get_stage_id_for_convnext(name, num_layers) + print_log(f'set param {name} as id {layer_id}') + else: + raise NotImplementedError() + group_name = f'layer_{layer_id}_{group_name}' + + if group_name not in parameter_groups: + scale = decay_rate**(num_layers - layer_id - 1) + + parameter_groups[group_name] = { + 'weight_decay': this_weight_decay, + 'params': [], + 'param_names': [], + 'lr_scale': scale, + 'group_name': group_name, + 'lr': scale * self.base_lr, + } + + parameter_groups[group_name]['params'].append(param) + parameter_groups[group_name]['param_names'].append(name) + rank, _ = get_dist_info() + if rank == 0: + to_display = {} + for key in parameter_groups: + to_display[key] = { + 'param_names': parameter_groups[key]['param_names'], + 'lr_scale': parameter_groups[key]['lr_scale'], + 'lr': parameter_groups[key]['lr'], + 'weight_decay': parameter_groups[key]['weight_decay'], + } + print_log(f'Param groups = {json.dumps(to_display, indent=2)}') + params.extend(parameter_groups.values()) + + +@OPTIM_WRAPPER_CONSTRUCTORS.register_module() +class LayerDecayOptimizerConstructor(LearningRateDecayOptimizerConstructor): + """Different learning rates are set for different layers of backbone. + + Note: Currently, this optimizer constructor is built for BEiT, + and it will be deprecated. + Please use ``LearningRateDecayOptimizerConstructor`` instead. + """ + + def __init__(self, optim_wrapper_cfg, paramwise_cfg): + warnings.warn('DeprecationWarning: Original ' + 'LayerDecayOptimizerConstructor of BEiT ' + 'will be deprecated. Please use ' + 'LearningRateDecayOptimizerConstructor instead, ' + 'and set decay_type = layer_wise_vit in paramwise_cfg.') + paramwise_cfg.update({'decay_type': 'layer_wise_vit'}) + warnings.warn('DeprecationWarning: Layer_decay_rate will ' + 'be deleted, please use decay_rate instead.') + paramwise_cfg['decay_rate'] = paramwise_cfg.pop('layer_decay_rate') + super().__init__(optim_wrapper_cfg, paramwise_cfg) diff --git a/Seg_All_In_One_MMSeg/mmseg/engine/schedulers/__init__.py b/Seg_All_In_One_MMSeg/mmseg/engine/schedulers/__init__.py new file mode 100644 index 0000000..3cd3f62 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/engine/schedulers/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .poly_ratio_scheduler import PolyLRRatio + +__all__ = ['PolyLRRatio'] diff --git a/Seg_All_In_One_MMSeg/mmseg/engine/schedulers/poly_ratio_scheduler.py b/Seg_All_In_One_MMSeg/mmseg/engine/schedulers/poly_ratio_scheduler.py new file mode 100644 index 0000000..057203a --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/engine/schedulers/poly_ratio_scheduler.py @@ -0,0 +1,62 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +from mmengine.optim.scheduler import PolyLR + +from mmseg.registry import PARAM_SCHEDULERS + + +@PARAM_SCHEDULERS.register_module() +class PolyLRRatio(PolyLR): + """Implements polynomial learning rate decay with ratio. + + This scheduler adjusts the learning rate of each parameter group + following a polynomial decay equation. The decay can occur in + conjunction with external parameter adjustments made outside this + scheduler. + + Args: + optimizer (Optimizer or OptimWrapper): Wrapped optimizer. + eta_min (float): Minimum learning rate at the end of scheduling. + Defaults to 0. + eta_min_ratio (float, optional): The ratio of the minimum parameter + value to the base parameter value. Either `eta_min` or + `eta_min_ratio` should be specified. Defaults to None. + power (float): The power of the polynomial. Defaults to 1.0. + begin (int): Step at which to start updating the parameters. + Defaults to 0. + end (int): Step at which to stop updating the parameters. + Defaults to INF. + last_step (int): The index of last step. Used for resume without + state dict. Defaults to -1. + by_epoch (bool): Whether the scheduled parameters are updated by + epochs. Defaults to True. + verbose (bool): Whether to print the value for each update. + Defaults to False. + """ + + def __init__(self, eta_min_ratio: Optional[int] = None, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.eta_min_ratio = eta_min_ratio + + def _get_value(self): + """Compute value using chainable form of the scheduler.""" + + if self.last_step == 0: + return [ + group[self.param_name] for group in self.optimizer.param_groups + ] + + param_groups_value = [] + for base_value, param_group in zip(self.base_values, + self.optimizer.param_groups): + eta_min = self.eta_min if self.eta_min_ratio is None else \ + base_value * self.eta_min_ratio + step_ratio = (1 - 1 / + (self.total_iters - self.last_step + 1))**self.power + step_value = (param_group[self.param_name] - + eta_min) * step_ratio + eta_min + param_groups_value.append(step_value) + + return param_groups_value diff --git a/Seg_All_In_One_MMSeg/mmseg/evaluation/__init__.py b/Seg_All_In_One_MMSeg/mmseg/evaluation/__init__.py new file mode 100644 index 0000000..82b3a8d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/evaluation/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .metrics import CityscapesMetric, DepthMetric, IoUMetric + +__all__ = ['IoUMetric', 'CityscapesMetric', 'DepthMetric'] diff --git a/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/__init__.py b/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/__init__.py new file mode 100644 index 0000000..848d471 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .citys_metric import CityscapesMetric +from .depth_metric import DepthMetric +from .iou_metric import IoUMetric + +__all__ = ['IoUMetric', 'CityscapesMetric', 'DepthMetric'] diff --git a/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/citys_metric.py b/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/citys_metric.py new file mode 100644 index 0000000..3298465 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/citys_metric.py @@ -0,0 +1,158 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import shutil +from collections import OrderedDict +from typing import Dict, Optional, Sequence + +try: + + import cityscapesscripts.evaluation.evalPixelLevelSemanticLabeling as CSEval # noqa + import cityscapesscripts.helpers.labels as CSLabels +except ImportError: + CSLabels = None + CSEval = None + +import numpy as np +from mmengine.dist import is_main_process, master_only +from mmengine.evaluator import BaseMetric +from mmengine.logging import MMLogger, print_log +from mmengine.utils import mkdir_or_exist +from PIL import Image + +from mmseg.registry import METRICS + + +@METRICS.register_module() +class CityscapesMetric(BaseMetric): + """Cityscapes evaluation metric. + + Args: + output_dir (str): The directory for output prediction + ignore_index (int): Index that will be ignored in evaluation. + Default: 255. + format_only (bool): Only format result for results commit without + perform evaluation. It is useful when you want to format the result + to a specific format and submit it to the test server. + Defaults to False. + keep_results (bool): Whether to keep the results. When ``format_only`` + is True, ``keep_results`` must be True. Defaults to False. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + + def __init__(self, + output_dir: str, + ignore_index: int = 255, + format_only: bool = False, + keep_results: bool = False, + collect_device: str = 'cpu', + prefix: Optional[str] = None, + **kwargs) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + if CSEval is None: + raise ImportError('Please run "pip install cityscapesscripts" to ' + 'install cityscapesscripts first.') + self.output_dir = output_dir + self.ignore_index = ignore_index + + self.format_only = format_only + if format_only: + assert keep_results, ( + 'When format_only is True, the results must be keep, please ' + f'set keep_results as True, but got {keep_results}') + self.keep_results = keep_results + self.prefix = prefix + if is_main_process(): + mkdir_or_exist(self.output_dir) + + @master_only + def __del__(self) -> None: + """Clean up.""" + if not self.keep_results: + shutil.rmtree(self.output_dir) + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data and data_samples. + + The processed results should be stored in ``self.results``, which will + be used to computed the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of outputs from the model. + """ + mkdir_or_exist(self.output_dir) + + for data_sample in data_samples: + pred_label = data_sample['pred_sem_seg']['data'][0].cpu().numpy() + # when evaluating with official cityscapesscripts, + # labelIds should be used + pred_label = self._convert_to_label_id(pred_label) + basename = osp.splitext(osp.basename(data_sample['img_path']))[0] + png_filename = osp.abspath( + osp.join(self.output_dir, f'{basename}.png')) + output = Image.fromarray(pred_label.astype(np.uint8)).convert('P') + output.save(png_filename) + if self.format_only: + # format_only always for test dataset without ground truth + gt_filename = '' + else: + # when evaluating with official cityscapesscripts, + # **_gtFine_labelIds.png is used + gt_filename = data_sample['seg_map_path'].replace( + 'labelTrainIds.png', 'labelIds.png') + self.results.append((png_filename, gt_filename)) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): Testing results of the dataset. + + Returns: + dict[str: float]: Cityscapes evaluation results. + """ + logger: MMLogger = MMLogger.get_current_instance() + if self.format_only: + logger.info(f'results are saved to {osp.dirname(self.output_dir)}') + return OrderedDict() + + msg = 'Evaluating in Cityscapes style' + if logger is None: + msg = '\n' + msg + print_log(msg, logger=logger) + + eval_results = dict() + print_log( + f'Evaluating results under {self.output_dir} ...', logger=logger) + + CSEval.args.evalInstLevelScore = True + CSEval.args.predictionPath = osp.abspath(self.output_dir) + CSEval.args.evalPixelAccuracy = True + CSEval.args.JSONOutput = False + + pred_list, gt_list = zip(*results) + metric = dict() + eval_results.update( + CSEval.evaluateImgLists(pred_list, gt_list, CSEval.args)) + metric['averageScoreCategories'] = eval_results[ + 'averageScoreCategories'] + metric['averageScoreInstCategories'] = eval_results[ + 'averageScoreInstCategories'] + return metric + + @staticmethod + def _convert_to_label_id(result): + """Convert trainId to id for cityscapes.""" + if isinstance(result, str): + result = np.load(result) + result_copy = result.copy() + for trainId, label in CSLabels.trainId2label.items(): + result_copy[result == trainId] = label.id + + return result_copy diff --git a/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/depth_metric.py b/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/depth_metric.py new file mode 100644 index 0000000..621d4a3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/depth_metric.py @@ -0,0 +1,212 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from collections import OrderedDict, defaultdict +from typing import Dict, List, Optional, Sequence + +import cv2 +import numpy as np +import torch +from mmengine.dist import is_main_process +from mmengine.evaluator import BaseMetric +from mmengine.logging import MMLogger, print_log +from mmengine.utils import mkdir_or_exist +from prettytable import PrettyTable +from torch import Tensor + +from mmseg.registry import METRICS + + +@METRICS.register_module() +class DepthMetric(BaseMetric): + """Depth estimation evaluation metric. + + Args: + depth_metrics (List[str], optional): List of metrics to compute. If + not specified, defaults to all metrics in self.METRICS. + min_depth_eval (float): Minimum depth value for evaluation. + Defaults to 0.0. + max_depth_eval (float): Maximum depth value for evaluation. + Defaults to infinity. + crop_type (str, optional): Specifies the type of cropping to be used + during evaluation. This option can affect how the evaluation mask + is generated. Currently, 'nyu_crop' is supported, but other + types can be added in future. Defaults to None if no cropping + should be applied. + depth_scale_factor (float): Factor to scale the depth values. + Defaults to 1.0. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + output_dir (str): The directory for output prediction. Defaults to + None. + format_only (bool): Only format result for results commit without + perform evaluation. It is useful when you want to save the result + to a specific format and submit it to the test server. + Defaults to False. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + METRICS = ('d1', 'd2', 'd3', 'abs_rel', 'sq_rel', 'rmse', 'rmse_log', + 'log10', 'silog') + + def __init__(self, + depth_metrics: Optional[List[str]] = None, + min_depth_eval: float = 0.0, + max_depth_eval: float = float('inf'), + crop_type: Optional[str] = None, + depth_scale_factor: float = 1.0, + collect_device: str = 'cpu', + output_dir: Optional[str] = None, + format_only: bool = False, + prefix: Optional[str] = None, + **kwargs) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + + if depth_metrics is None: + self.metrics = self.METRICS + elif isinstance(depth_metrics, [tuple, list]): + for metric in depth_metrics: + assert metric in self.METRICS, f'the metric {metric} is not ' \ + f'supported. Please use metrics in {self.METRICS}' + self.metrics = depth_metrics + + # Validate crop_type, if provided + assert crop_type in [ + None, 'nyu_crop' + ], (f'Invalid value for crop_type: {crop_type}. Supported values are ' + 'None or \'nyu_crop\'.') + self.crop_type = crop_type + self.min_depth_eval = min_depth_eval + self.max_depth_eval = max_depth_eval + self.output_dir = output_dir + if self.output_dir and is_main_process(): + mkdir_or_exist(self.output_dir) + self.format_only = format_only + self.depth_scale_factor = depth_scale_factor + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data and data_samples. + + The processed results should be stored in ``self.results``, which will + be used to compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of outputs from the model. + """ + for data_sample in data_samples: + pred_label = data_sample['pred_depth_map']['data'].squeeze() + # format_only always for test dataset without ground truth + if not self.format_only: + gt_depth = data_sample['gt_depth_map']['data'].squeeze().to( + pred_label) + + eval_mask = self._get_eval_mask(gt_depth) + self.results.append( + (gt_depth[eval_mask], pred_label[eval_mask])) + # format_result + if self.output_dir is not None: + basename = osp.splitext(osp.basename( + data_sample['img_path']))[0] + png_filename = osp.abspath( + osp.join(self.output_dir, f'{basename}.png')) + output_mask = pred_label.cpu().numpy( + ) * self.depth_scale_factor + + cv2.imwrite(png_filename, output_mask.astype(np.uint16), + [cv2.IMWRITE_PNG_COMPRESSION, 0]) + + def _get_eval_mask(self, gt_depth: Tensor): + """Generates an evaluation mask based on ground truth depth and + cropping. + + Args: + gt_depth (Tensor): Ground truth depth map. + + Returns: + Tensor: Boolean mask where evaluation should be performed. + """ + valid_mask = torch.logical_and(gt_depth > self.min_depth_eval, + gt_depth < self.max_depth_eval) + + if self.crop_type == 'nyu_crop': + # this implementation is adapted from + # https://github.com/zhyever/Monocular-Depth-Estimation-Toolbox/blob/main/depth/datasets/nyu.py # noqa + crop_mask = torch.zeros_like(valid_mask) + crop_mask[45:471, 41:601] = 1 + else: + crop_mask = torch.ones_like(valid_mask) + + eval_mask = torch.logical_and(valid_mask, crop_mask) + return eval_mask + + @staticmethod + def _calc_all_metrics(gt_depth, pred_depth): + """Computes final evaluation metrics based on accumulated results.""" + assert gt_depth.shape == pred_depth.shape + + thresh = torch.max((gt_depth / pred_depth), (pred_depth / gt_depth)) + diff = pred_depth - gt_depth + diff_log = torch.log(pred_depth) - torch.log(gt_depth) + + d1 = torch.sum(thresh < 1.25).float() / len(thresh) + d2 = torch.sum(thresh < 1.25**2).float() / len(thresh) + d3 = torch.sum(thresh < 1.25**3).float() / len(thresh) + + abs_rel = torch.mean(torch.abs(diff) / gt_depth) + sq_rel = torch.mean(torch.pow(diff, 2) / gt_depth) + + rmse = torch.sqrt(torch.mean(torch.pow(diff, 2))) + rmse_log = torch.sqrt(torch.mean(torch.pow(diff_log, 2))) + + log10 = torch.mean( + torch.abs(torch.log10(pred_depth) - torch.log10(gt_depth))) + silog = torch.sqrt( + torch.pow(diff_log, 2).mean() - + 0.5 * torch.pow(diff_log.mean(), 2)) + + return { + 'd1': d1.item(), + 'd2': d2.item(), + 'd3': d3.item(), + 'abs_rel': abs_rel.item(), + 'sq_rel': sq_rel.item(), + 'rmse': rmse.item(), + 'rmse_log': rmse_log.item(), + 'log10': log10.item(), + 'silog': silog.item() + } + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. The keys + are identical with self.metrics. + """ + logger: MMLogger = MMLogger.get_current_instance() + if self.format_only: + logger.info(f'results are saved to {osp.dirname(self.output_dir)}') + return OrderedDict() + + metrics = defaultdict(list) + for gt_depth, pred_depth in results: + for key, value in self._calc_all_metrics(gt_depth, + pred_depth).items(): + metrics[key].append(value) + metrics = {k: sum(metrics[k]) / len(metrics[k]) for k in self.metrics} + + table_data = PrettyTable() + for key, val in metrics.items(): + table_data.add_column(key, [round(val, 5)]) + + print_log('results:', logger) + print_log('\n' + table_data.get_string(), logger=logger) + + return metrics diff --git a/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/iou_metric.py b/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/iou_metric.py new file mode 100644 index 0000000..16014c7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/evaluation/metrics/iou_metric.py @@ -0,0 +1,286 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from collections import OrderedDict +from typing import Dict, List, Optional, Sequence + +import numpy as np +import torch +from mmengine.dist import is_main_process +from mmengine.evaluator import BaseMetric +from mmengine.logging import MMLogger, print_log +from mmengine.utils import mkdir_or_exist +from PIL import Image +from prettytable import PrettyTable + +from mmseg.registry import METRICS + + +@METRICS.register_module() +class IoUMetric(BaseMetric): + """IoU evaluation metric. + + Args: + ignore_index (int): Index that will be ignored in evaluation. + Default: 255. + iou_metrics (list[str] | str): Metrics to be calculated, the options + includes 'mIoU', 'mDice' and 'mFscore'. + nan_to_num (int, optional): If specified, NaN values will be replaced + by the numbers defined by the user. Default: None. + beta (int): Determines the weight of recall in the combined score. + Default: 1. + collect_device (str): Device name used for collecting results from + different ranks during distributed training. Must be 'cpu' or + 'gpu'. Defaults to 'cpu'. + output_dir (str): The directory for output prediction. Defaults to + None. + format_only (bool): Only format result for results commit without + perform evaluation. It is useful when you want to save the result + to a specific format and submit it to the test server. + Defaults to False. + prefix (str, optional): The prefix that will be added in the metric + names to disambiguate homonymous metrics of different evaluators. + If prefix is not provided in the argument, self.default_prefix + will be used instead. Defaults to None. + """ + + def __init__(self, + ignore_index: int = 255, + iou_metrics: List[str] = ['mIoU'], + nan_to_num: Optional[int] = None, + beta: int = 1, + collect_device: str = 'cpu', + output_dir: Optional[str] = None, + format_only: bool = False, + prefix: Optional[str] = None, + **kwargs) -> None: + super().__init__(collect_device=collect_device, prefix=prefix) + + self.ignore_index = ignore_index + self.metrics = iou_metrics + self.nan_to_num = nan_to_num + self.beta = beta + self.output_dir = output_dir + if self.output_dir and is_main_process(): + mkdir_or_exist(self.output_dir) + self.format_only = format_only + + def process(self, data_batch: dict, data_samples: Sequence[dict]) -> None: + """Process one batch of data and data_samples. + + The processed results should be stored in ``self.results``, which will + be used to compute the metrics when all batches have been processed. + + Args: + data_batch (dict): A batch of data from the dataloader. + data_samples (Sequence[dict]): A batch of outputs from the model. + """ + num_classes = len(self.dataset_meta['classes']) + for data_sample in data_samples: + pred_label = data_sample['pred_sem_seg']['data'].squeeze() + # format_only always for test dataset without ground truth + if not self.format_only: + label = data_sample['gt_sem_seg']['data'].squeeze().to( + pred_label) + self.results.append( + self.intersect_and_union(pred_label, label, num_classes, + self.ignore_index)) + # format_result + if self.output_dir is not None: + basename = osp.splitext(osp.basename( + data_sample['img_path']))[0] + png_filename = osp.abspath( + osp.join(self.output_dir, f'{basename}.png')) + output_mask = pred_label.cpu().numpy() + # The index range of official ADE20k dataset is from 0 to 150. + # But the index range of output is from 0 to 149. + # That is because we set reduce_zero_label=True. + if data_sample.get('reduce_zero_label', False): + output_mask = output_mask + 1 + output = Image.fromarray(output_mask.astype(np.uint8)) + output.save(png_filename) + + def compute_metrics(self, results: list) -> Dict[str, float]: + """Compute the metrics from processed results. + + Args: + results (list): The processed results of each batch. + + Returns: + Dict[str, float]: The computed metrics. The keys are the names of + the metrics, and the values are corresponding results. The key + mainly includes aAcc, mIoU, mAcc, mDice, mFscore, mPrecision, + mRecall. + """ + logger: MMLogger = MMLogger.get_current_instance() + if self.format_only: + logger.info(f'results are saved to {osp.dirname(self.output_dir)}') + return OrderedDict() + # convert list of tuples to tuple of lists, e.g. + # [(A_1, B_1, C_1, D_1), ..., (A_n, B_n, C_n, D_n)] to + # ([A_1, ..., A_n], ..., [D_1, ..., D_n]) + results = tuple(zip(*results)) + assert len(results) == 4 + + total_area_intersect = sum(results[0]) + total_area_union = sum(results[1]) + total_area_pred_label = sum(results[2]) + total_area_label = sum(results[3]) + ret_metrics = self.total_area_to_metrics( + total_area_intersect, total_area_union, total_area_pred_label, + total_area_label, self.metrics, self.nan_to_num, self.beta) + + class_names = self.dataset_meta['classes'] + + # summary table + ret_metrics_summary = OrderedDict({ + ret_metric: np.round(np.nanmean(ret_metric_value) * 100, 2) + for ret_metric, ret_metric_value in ret_metrics.items() + }) + metrics = dict() + for key, val in ret_metrics_summary.items(): + if key == 'aAcc': + metrics[key] = val + else: + metrics['m' + key] = val + + # each class table + ret_metrics.pop('aAcc', None) + ret_metrics_class = OrderedDict({ + ret_metric: np.round(ret_metric_value * 100, 2) + for ret_metric, ret_metric_value in ret_metrics.items() + }) + ret_metrics_class.update({'Class': class_names}) + ret_metrics_class.move_to_end('Class', last=False) + class_table_data = PrettyTable() + for key, val in ret_metrics_class.items(): + class_table_data.add_column(key, val) + + print_log('per class results:', logger) + print_log('\n' + class_table_data.get_string(), logger=logger) + + return metrics + + @staticmethod + def intersect_and_union(pred_label: torch.tensor, label: torch.tensor, + num_classes: int, ignore_index: int): + """Calculate Intersection and Union. + + Args: + pred_label (torch.tensor): Prediction segmentation map + or predict result filename. The shape is (H, W). + label (torch.tensor): Ground truth segmentation map + or label filename. The shape is (H, W). + num_classes (int): Number of categories. + ignore_index (int): Index that will be ignored in evaluation. + + Returns: + torch.Tensor: The intersection of prediction and ground truth + histogram on all classes. + torch.Tensor: The union of prediction and ground truth histogram on + all classes. + torch.Tensor: The prediction histogram on all classes. + torch.Tensor: The ground truth histogram on all classes. + """ + + mask = (label != ignore_index) + pred_label = pred_label[mask] + label = label[mask] + + intersect = pred_label[pred_label == label] + area_intersect = torch.histc( + intersect.float(), bins=(num_classes), min=0, + max=num_classes - 1).cpu() + area_pred_label = torch.histc( + pred_label.float(), bins=(num_classes), min=0, + max=num_classes - 1).cpu() + area_label = torch.histc( + label.float(), bins=(num_classes), min=0, + max=num_classes - 1).cpu() + area_union = area_pred_label + area_label - area_intersect + return area_intersect, area_union, area_pred_label, area_label + + @staticmethod + def total_area_to_metrics(total_area_intersect: np.ndarray, + total_area_union: np.ndarray, + total_area_pred_label: np.ndarray, + total_area_label: np.ndarray, + metrics: List[str] = ['mIoU'], + nan_to_num: Optional[int] = None, + beta: int = 1): + """Calculate evaluation metrics + Args: + total_area_intersect (np.ndarray): The intersection of prediction + and ground truth histogram on all classes. + total_area_union (np.ndarray): The union of prediction and ground + truth histogram on all classes. + total_area_pred_label (np.ndarray): The prediction histogram on + all classes. + total_area_label (np.ndarray): The ground truth histogram on + all classes. + metrics (List[str] | str): Metrics to be evaluated, 'mIoU' and + 'mDice'. + nan_to_num (int, optional): If specified, NaN values will be + replaced by the numbers defined by the user. Default: None. + beta (int): Determines the weight of recall in the combined score. + Default: 1. + Returns: + Dict[str, np.ndarray]: per category evaluation metrics, + shape (num_classes, ). + """ + + def f_score(precision, recall, beta=1): + """calculate the f-score value. + + Args: + precision (float | torch.Tensor): The precision value. + recall (float | torch.Tensor): The recall value. + beta (int): Determines the weight of recall in the combined + score. Default: 1. + + Returns: + [torch.tensor]: The f-score value. + """ + score = (1 + beta**2) * (precision * recall) / ( + (beta**2 * precision) + recall) + return score + + if isinstance(metrics, str): + metrics = [metrics] + allowed_metrics = ['mIoU', 'mDice', 'mFscore'] + if not set(metrics).issubset(set(allowed_metrics)): + raise KeyError(f'metrics {metrics} is not supported') + + all_acc = total_area_intersect.sum() / total_area_label.sum() + ret_metrics = OrderedDict({'aAcc': all_acc}) + for metric in metrics: + if metric == 'mIoU': + iou = total_area_intersect / total_area_union + acc = total_area_intersect / total_area_label + ret_metrics['IoU'] = iou + ret_metrics['Acc'] = acc + elif metric == 'mDice': + dice = 2 * total_area_intersect / ( + total_area_pred_label + total_area_label) + acc = total_area_intersect / total_area_label + ret_metrics['Dice'] = dice + ret_metrics['Acc'] = acc + elif metric == 'mFscore': + precision = total_area_intersect / total_area_pred_label + recall = total_area_intersect / total_area_label + f_value = torch.tensor([ + f_score(x[0], x[1], beta) for x in zip(precision, recall) + ]) + ret_metrics['Fscore'] = f_value + ret_metrics['Precision'] = precision + ret_metrics['Recall'] = recall + + ret_metrics = { + metric: value.numpy() + for metric, value in ret_metrics.items() + } + if nan_to_num is not None: + ret_metrics = OrderedDict({ + metric: np.nan_to_num(metric_value, nan=nan_to_num) + for metric, metric_value in ret_metrics.items() + }) + return ret_metrics diff --git a/Seg_All_In_One_MMSeg/mmseg/models/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/__init__.py new file mode 100644 index 0000000..a989512 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .assigners import * # noqa: F401,F403 +from .backbones import * # noqa: F401,F403 +from .builder import (BACKBONES, HEADS, LOSSES, SEGMENTORS, build_backbone, + build_head, build_loss, build_segmentor) +from .data_preprocessor import SegDataPreProcessor +from .decode_heads import * # noqa: F401,F403 +from .losses import * # noqa: F401,F403 +from .necks import * # noqa: F401,F403 +from .segmentors import * # noqa: F401,F403 +from .text_encoder import * # noqa: F401,F403 + +__all__ = [ + 'BACKBONES', 'HEADS', 'LOSSES', 'SEGMENTORS', 'build_backbone', + 'build_head', 'build_loss', 'build_segmentor', 'SegDataPreProcessor' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/assigners/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/assigners/__init__.py new file mode 100644 index 0000000..d49b1b1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/assigners/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_assigner import BaseAssigner +from .hungarian_assigner import HungarianAssigner +from .match_cost import ClassificationCost, CrossEntropyLossCost, DiceCost + +__all__ = [ + 'BaseAssigner', + 'HungarianAssigner', + 'ClassificationCost', + 'CrossEntropyLossCost', + 'DiceCost', +] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/assigners/base_assigner.py b/Seg_All_In_One_MMSeg/mmseg/models/assigners/base_assigner.py new file mode 100644 index 0000000..97895cd --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/assigners/base_assigner.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import Optional + +from mmengine.structures import InstanceData + + +class BaseAssigner(metaclass=ABCMeta): + """Base assigner that assigns masks to ground truth class labels.""" + + @abstractmethod + def assign(self, + pred_instances: InstanceData, + gt_instances: InstanceData, + gt_instances_ignore: Optional[InstanceData] = None, + **kwargs): + """Assign masks to either a ground truth class label or a negative + label.""" diff --git a/Seg_All_In_One_MMSeg/mmseg/models/assigners/hungarian_assigner.py b/Seg_All_In_One_MMSeg/mmseg/models/assigners/hungarian_assigner.py new file mode 100644 index 0000000..28868f0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/assigners/hungarian_assigner.py @@ -0,0 +1,86 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Union + +import torch +from mmengine import ConfigDict +from mmengine.structures import InstanceData +from scipy.optimize import linear_sum_assignment +from torch.cuda.amp import autocast + +from mmseg.registry import TASK_UTILS +from .base_assigner import BaseAssigner + + +@TASK_UTILS.register_module() +class HungarianAssigner(BaseAssigner): + """Computes one-to-one matching between prediction masks and ground truth. + + This class uses bipartite matching-based assignment to computes an + assignment between the prediction masks and the ground truth. The + assignment result is based on the weighted sum of match costs. The + Hungarian algorithm is used to calculate the best matching with the + minimum cost. The prediction masks that are not matched are classified + as background. + + Args: + match_costs (ConfigDict|List[ConfigDict]): Match cost configs. + """ + + def __init__( + self, match_costs: Union[List[Union[dict, ConfigDict]], dict, + ConfigDict] + ) -> None: + + if isinstance(match_costs, dict): + match_costs = [match_costs] + elif isinstance(match_costs, list): + assert len(match_costs) > 0, \ + 'match_costs must not be a empty list.' + + self.match_costs = [ + TASK_UTILS.build(match_cost) for match_cost in match_costs + ] + + def assign(self, pred_instances: InstanceData, gt_instances: InstanceData, + **kwargs): + """Computes one-to-one matching based on the weighted costs. + + This method assign each query prediction to a ground truth or + background. The assignment first calculates the cost for each + category assigned to each query mask, and then uses the + Hungarian algorithm to calculate the minimum cost as the best + match. + + Args: + pred_instances (InstanceData): Instances of model + predictions. It includes "masks", with shape + (n, h, w) or (n, l), and "cls", with shape (n, num_classes+1) + gt_instances (InstanceData): Ground truth of instance + annotations. It includes "labels", with shape (k, ), + and "masks", with shape (k, h, w) or (k, l). + + Returns: + matched_quiery_inds (Tensor): The indexes of matched quieres. + matched_label_inds (Tensor): The indexes of matched labels. + """ + # compute weighted cost + cost_list = [] + with autocast(enabled=False): + for match_cost in self.match_costs: + cost = match_cost( + pred_instances=pred_instances, gt_instances=gt_instances) + cost_list.append(cost) + cost = torch.stack(cost_list).sum(dim=0) + + device = cost.device + # do Hungarian matching on CPU using linear_sum_assignment + cost = cost.detach().cpu() + if linear_sum_assignment is None: + raise ImportError('Please run "pip install scipy" ' + 'to install scipy first.') + + matched_quiery_inds, matched_label_inds = linear_sum_assignment(cost) + matched_quiery_inds = torch.from_numpy(matched_quiery_inds).to(device) + matched_label_inds = torch.from_numpy(matched_label_inds).to(device) + + return matched_quiery_inds, matched_label_inds diff --git a/Seg_All_In_One_MMSeg/mmseg/models/assigners/match_cost.py b/Seg_All_In_One_MMSeg/mmseg/models/assigners/match_cost.py new file mode 100644 index 0000000..560df85 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/assigners/match_cost.py @@ -0,0 +1,231 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import abstractmethod +from typing import Union + +import torch +import torch.nn.functional as F +from mmengine.structures import InstanceData +from torch import Tensor + +from mmseg.registry import TASK_UTILS + + +class BaseMatchCost: + """Base match cost class. + + Args: + weight (Union[float, int]): Cost weight. Defaults to 1. + """ + + def __init__(self, weight: Union[float, int] = 1.) -> None: + self.weight = weight + + @abstractmethod + def __call__(self, pred_instances: InstanceData, + gt_instances: InstanceData, **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (InstanceData): Instances of model predictions. + It often includes "labels" and "scores". + gt_instances (InstanceData): Ground truth of instance + annotations. It usually includes "labels". + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + pass + + +@TASK_UTILS.register_module() +class ClassificationCost(BaseMatchCost): + """ClsSoftmaxCost. + + Args: + weight (Union[float, int]): Cost weight. Defaults to 1. + + Examples: + >>> from mmseg.models.assigners import ClassificationCost + >>> import torch + >>> self = ClassificationCost() + >>> cls_pred = torch.rand(4, 3) + >>> gt_labels = torch.tensor([0, 1, 2]) + >>> factor = torch.tensor([10, 8, 10, 8]) + >>> self(cls_pred, gt_labels) + tensor([[-0.3430, -0.3525, -0.3045], + [-0.3077, -0.2931, -0.3992], + [-0.3664, -0.3455, -0.2881], + [-0.3343, -0.2701, -0.3956]]) + """ + + def __init__(self, weight: Union[float, int] = 1) -> None: + super().__init__(weight=weight) + + def __call__(self, pred_instances: InstanceData, + gt_instances: InstanceData, **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (InstanceData): "scores" inside is + predicted classification logits, of shape + (num_queries, num_class). + gt_instances (InstanceData): "labels" inside should have + shape (num_gt, ). + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + assert hasattr(pred_instances, 'scores'), \ + "pred_instances must contain 'scores'" + assert hasattr(gt_instances, 'labels'), \ + "gt_instances must contain 'labels'" + pred_scores = pred_instances.scores + gt_labels = gt_instances.labels + + pred_scores = pred_scores.softmax(-1) + cls_cost = -pred_scores[:, gt_labels] + + return cls_cost * self.weight + + +@TASK_UTILS.register_module() +class DiceCost(BaseMatchCost): + """Cost of mask assignments based on dice losses. + + Args: + pred_act (bool): Whether to apply sigmoid to mask_pred. + Defaults to False. + eps (float): Defaults to 1e-3. + naive_dice (bool): If True, use the naive dice loss + in which the power of the number in the denominator is + the first power. If False, use the second power that + is adopted by K-Net and SOLO. Defaults to True. + weight (Union[float, int]): Cost weight. Defaults to 1. + """ + + def __init__(self, + pred_act: bool = False, + eps: float = 1e-3, + naive_dice: bool = True, + weight: Union[float, int] = 1.) -> None: + super().__init__(weight=weight) + self.pred_act = pred_act + self.eps = eps + self.naive_dice = naive_dice + + def _binary_mask_dice_loss(self, mask_preds: Tensor, + gt_masks: Tensor) -> Tensor: + """ + Args: + mask_preds (Tensor): Mask prediction in shape (num_queries, *). + gt_masks (Tensor): Ground truth in shape (num_gt, *) + store 0 or 1, 0 for negative class and 1 for + positive class. + + Returns: + Tensor: Dice cost matrix in shape (num_queries, num_gt). + """ + mask_preds = mask_preds.flatten(1) + gt_masks = gt_masks.flatten(1).float() + numerator = 2 * torch.einsum('nc,mc->nm', mask_preds, gt_masks) + if self.naive_dice: + denominator = mask_preds.sum(-1)[:, None] + \ + gt_masks.sum(-1)[None, :] + else: + denominator = mask_preds.pow(2).sum(1)[:, None] + \ + gt_masks.pow(2).sum(1)[None, :] + loss = 1 - (numerator + self.eps) / (denominator + self.eps) + return loss + + def __call__(self, pred_instances: InstanceData, + gt_instances: InstanceData, **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (InstanceData): Predicted instances which + must contain "masks". + gt_instances (InstanceData): Ground truth which must contain + "mask". + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + assert hasattr(pred_instances, 'masks'), \ + "pred_instances must contain 'masks'" + assert hasattr(gt_instances, 'masks'), \ + "gt_instances must contain 'masks'" + pred_masks = pred_instances.masks + gt_masks = gt_instances.masks + + if self.pred_act: + pred_masks = pred_masks.sigmoid() + dice_cost = self._binary_mask_dice_loss(pred_masks, gt_masks) + return dice_cost * self.weight + + +@TASK_UTILS.register_module() +class CrossEntropyLossCost(BaseMatchCost): + """CrossEntropyLossCost. + + Args: + use_sigmoid (bool): Whether the prediction uses sigmoid + of softmax. Defaults to True. + weight (Union[float, int]): Cost weight. Defaults to 1. + """ + + def __init__(self, + use_sigmoid: bool = True, + weight: Union[float, int] = 1.) -> None: + super().__init__(weight=weight) + self.use_sigmoid = use_sigmoid + + def _binary_cross_entropy(self, cls_pred: Tensor, + gt_labels: Tensor) -> Tensor: + """ + Args: + cls_pred (Tensor): The prediction with shape (num_queries, 1, *) or + (num_queries, *). + gt_labels (Tensor): The learning label of prediction with + shape (num_gt, *). + + Returns: + Tensor: Cross entropy cost matrix in shape (num_queries, num_gt). + """ + cls_pred = cls_pred.flatten(1).float() + gt_labels = gt_labels.flatten(1).float() + n = cls_pred.shape[1] + pos = F.binary_cross_entropy_with_logits( + cls_pred, torch.ones_like(cls_pred), reduction='none') + neg = F.binary_cross_entropy_with_logits( + cls_pred, torch.zeros_like(cls_pred), reduction='none') + cls_cost = torch.einsum('nc,mc->nm', pos, gt_labels) + \ + torch.einsum('nc,mc->nm', neg, 1 - gt_labels) + cls_cost = cls_cost / n + + return cls_cost + + def __call__(self, pred_instances: InstanceData, + gt_instances: InstanceData, **kwargs) -> Tensor: + """Compute match cost. + + Args: + pred_instances (:obj:`InstanceData`): Predicted instances which + must contain ``masks``. + gt_instances (:obj:`InstanceData`): Ground truth which must contain + ``masks``. + + Returns: + Tensor: Match Cost matrix of shape (num_preds, num_gts). + """ + assert hasattr(pred_instances, 'masks'), \ + "pred_instances must contain 'masks'" + assert hasattr(gt_instances, 'masks'), \ + "gt_instances must contain 'masks'" + pred_masks = pred_instances.masks + gt_masks = gt_instances.masks + if self.use_sigmoid: + cls_cost = self._binary_cross_entropy(pred_masks, gt_masks) + else: + raise NotImplementedError + + return cls_cost * self.weight diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/__init__.py new file mode 100644 index 0000000..63239f4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/__init__.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .beit import BEiT +from .bisenetv1 import BiSeNetV1 +from .bisenetv2 import BiSeNetV2 +from .en_bisenetv2 import EnBiSeNetV2 # TODO +from .my_bisnetv2_A1 import My_BiSeNetV2_A1 # TODO +from .my_bisnetv2_A2 import My_BiSeNetV2_A2 # TODO +from .my_bisnetv2_A1_add_A2 import My_BiSeNetV2_A1_add_A2 # TODO +from .cgnet import CGNet +from .ddrnet import DDRNet +from .erfnet import ERFNet +from .fast_scnn import FastSCNN +from .hrnet import HRNet +from .icnet import ICNet +from .mae import MAE +from .mit import MixVisionTransformer +from .mobilenet_v2 import MobileNetV2 +from .mobilenet_v3 import MobileNetV3 +from .mscan import MSCAN +from .pidnet import PIDNet +from .resnest import ResNeSt +from .resnet import ResNet, ResNetV1c, ResNetV1d +from .resnext import ResNeXt +from .stdc import STDCContextPathNet, STDCNet +from .swin import SwinTransformer +from .timm_backbone import TIMMBackbone +from .twins import PCPVT, SVT +from .unet import UNet +from .vit import VisionTransformer +from .vpd import VPD + +__all__ = [ + 'EnBiSeNetV2', 'My_BiSeNetV2_A1', 'My_BiSeNetV2_A2', 'My_BiSeNetV2_A1_add_A2', # TODO + 'ResNet', 'ResNetV1c', 'ResNetV1d', 'ResNeXt', 'HRNet', 'FastSCNN', + 'ResNeSt', 'MobileNetV2', 'UNet', 'CGNet', 'MobileNetV3', + 'VisionTransformer', 'SwinTransformer', 'MixVisionTransformer', + 'BiSeNetV1', 'BiSeNetV2', 'ICNet', 'TIMMBackbone', 'ERFNet', 'PCPVT', + 'SVT', 'STDCNet', 'STDCContextPathNet', 'BEiT', 'MAE', 'PIDNet', 'MSCAN', + 'DDRNet', 'VPD' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/beit.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/beit.py new file mode 100644 index 0000000..e5da71e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/beit.py @@ -0,0 +1,554 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.drop import build_dropout +from mmengine.model import BaseModule, ModuleList +from mmengine.model.weight_init import (constant_init, kaiming_init, + trunc_normal_) +from mmengine.runner.checkpoint import _load_checkpoint +from scipy import interpolate +from torch.nn.modules.batchnorm import _BatchNorm +from torch.nn.modules.utils import _pair as to_2tuple + +from mmseg.registry import MODELS +from ..utils import PatchEmbed +from .vit import TransformerEncoderLayer as VisionTransformerEncoderLayer + + +class BEiTAttention(BaseModule): + """Window based multi-head self-attention (W-MSA) module with relative + position bias. + + Args: + embed_dims (int): Number of input channels. + num_heads (int): Number of attention heads. + window_size (tuple[int]): The height and width of the window. + bias (bool): The option to add leanable bias for q, k, v. If bias is + True, it will add leanable bias. If bias is 'qv_bias', it will only + add leanable bias for q, v. If bias is False, it will not add bias + for q, k, v. Default to 'qv_bias'. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + attn_drop_rate (float): Dropout ratio of attention weight. + Default: 0.0 + proj_drop_rate (float): Dropout ratio of output. Default: 0. + init_cfg (dict | None, optional): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + window_size, + bias='qv_bias', + qk_scale=None, + attn_drop_rate=0., + proj_drop_rate=0., + init_cfg=None, + **kwargs): + super().__init__(init_cfg=init_cfg) + self.embed_dims = embed_dims + self.num_heads = num_heads + head_embed_dims = embed_dims // num_heads + self.bias = bias + self.scale = qk_scale or head_embed_dims**-0.5 + + qkv_bias = bias + if bias == 'qv_bias': + self._init_qv_bias() + qkv_bias = False + + self.window_size = window_size + self._init_rel_pos_embedding() + + self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop_rate) + self.proj = nn.Linear(embed_dims, embed_dims) + self.proj_drop = nn.Dropout(proj_drop_rate) + + def _init_qv_bias(self): + self.q_bias = nn.Parameter(torch.zeros(self.embed_dims)) + self.v_bias = nn.Parameter(torch.zeros(self.embed_dims)) + + def _init_rel_pos_embedding(self): + Wh, Ww = self.window_size + # cls to token & token 2 cls & cls to cls + self.num_relative_distance = (2 * Wh - 1) * (2 * Ww - 1) + 3 + # relative_position_bias_table shape is (2*Wh-1 * 2*Ww-1 + 3, nH) + self.relative_position_bias_table = nn.Parameter( + torch.zeros(self.num_relative_distance, self.num_heads)) + + # get pair-wise relative position index for + # each token inside the window + coords_h = torch.arange(Wh) + coords_w = torch.arange(Ww) + # coords shape is (2, Wh, Ww) + coords = torch.stack(torch.meshgrid([coords_h, coords_w])) + # coords_flatten shape is (2, Wh*Ww) + coords_flatten = torch.flatten(coords, 1) + relative_coords = ( + coords_flatten[:, :, None] - coords_flatten[:, None, :]) + # relative_coords shape is (Wh*Ww, Wh*Ww, 2) + relative_coords = relative_coords.permute(1, 2, 0).contiguous() + # shift to start from 0 + relative_coords[:, :, 0] += Wh - 1 + relative_coords[:, :, 1] += Ww - 1 + relative_coords[:, :, 0] *= 2 * Ww - 1 + relative_position_index = torch.zeros( + size=(Wh * Ww + 1, ) * 2, dtype=relative_coords.dtype) + # relative_position_index shape is (Wh*Ww, Wh*Ww) + relative_position_index[1:, 1:] = relative_coords.sum(-1) + relative_position_index[0, 0:] = self.num_relative_distance - 3 + relative_position_index[0:, 0] = self.num_relative_distance - 2 + relative_position_index[0, 0] = self.num_relative_distance - 1 + + self.register_buffer('relative_position_index', + relative_position_index) + + def init_weights(self): + trunc_normal_(self.relative_position_bias_table, std=0.02) + + def forward(self, x): + """ + Args: + x (tensor): input features with shape of (num_windows*B, N, C). + """ + B, N, C = x.shape + + if self.bias == 'qv_bias': + k_bias = torch.zeros_like(self.v_bias, requires_grad=False) + qkv_bias = torch.cat((self.q_bias, k_bias, self.v_bias)) + qkv = F.linear(input=x, weight=self.qkv.weight, bias=qkv_bias) + else: + qkv = self.qkv(x) + + qkv = qkv.reshape(B, N, 3, self.num_heads, -1).permute(2, 0, 3, 1, 4) + q, k, v = qkv[0], qkv[1], qkv[2] + q = q * self.scale + attn = (q @ k.transpose(-2, -1)) + if self.relative_position_bias_table is not None: + Wh = self.window_size[0] + Ww = self.window_size[1] + relative_position_bias = self.relative_position_bias_table[ + self.relative_position_index.view(-1)].view( + Wh * Ww + 1, Wh * Ww + 1, -1) + relative_position_bias = relative_position_bias.permute( + 2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww + attn = attn + relative_position_bias.unsqueeze(0) + attn = attn.softmax(dim=-1) + attn = self.attn_drop(attn) + x = (attn @ v).transpose(1, 2).reshape(B, N, C) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class BEiTTransformerEncoderLayer(VisionTransformerEncoderLayer): + """Implements one encoder layer in Vision Transformer. + + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + attn_drop_rate (float): The drop out rate for attention layer. + Default: 0.0. + drop_path_rate (float): Stochastic depth rate. Default 0.0. + num_fcs (int): The number of fully-connected layers for FFNs. + Default: 2. + bias (bool): The option to add leanable bias for q, k, v. If bias is + True, it will add leanable bias. If bias is 'qv_bias', it will only + add leanable bias for q, v. If bias is False, it will not add bias + for q, k, v. Default to 'qv_bias'. + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + window_size (tuple[int], optional): The height and width of the window. + Default: None. + init_values (float, optional): Initialize the values of BEiTAttention + and FFN with learnable scaling. Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + attn_drop_rate=0., + drop_path_rate=0., + num_fcs=2, + bias='qv_bias', + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + window_size=None, + attn_cfg=dict(), + ffn_cfg=dict(add_identity=False), + init_values=None): + attn_cfg.update(dict(window_size=window_size, qk_scale=None)) + + super().__init__( + embed_dims=embed_dims, + num_heads=num_heads, + feedforward_channels=feedforward_channels, + attn_drop_rate=attn_drop_rate, + drop_path_rate=0., + drop_rate=0., + num_fcs=num_fcs, + qkv_bias=bias, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + attn_cfg=attn_cfg, + ffn_cfg=ffn_cfg) + + # NOTE: drop path for stochastic depth, we shall see if + # this is better than dropout here + dropout_layer = dict(type='DropPath', drop_prob=drop_path_rate) + self.drop_path = build_dropout( + dropout_layer) if dropout_layer else nn.Identity() + self.gamma_1 = nn.Parameter( + init_values * torch.ones(embed_dims), requires_grad=True) + self.gamma_2 = nn.Parameter( + init_values * torch.ones(embed_dims), requires_grad=True) + + def build_attn(self, attn_cfg): + self.attn = BEiTAttention(**attn_cfg) + + def forward(self, x): + x = x + self.drop_path(self.gamma_1 * self.attn(self.norm1(x))) + x = x + self.drop_path(self.gamma_2 * self.ffn(self.norm2(x))) + return x + + +@MODELS.register_module() +class BEiT(BaseModule): + """BERT Pre-Training of Image Transformers. + + Args: + img_size (int | tuple): Input image size. Default: 224. + patch_size (int): The patch size. Default: 16. + in_channels (int): Number of input channels. Default: 3. + embed_dims (int): Embedding dimension. Default: 768. + num_layers (int): Depth of transformer. Default: 12. + num_heads (int): Number of attention heads. Default: 12. + mlp_ratio (int): Ratio of mlp hidden dim to embedding dim. + Default: 4. + out_indices (list | tuple | int): Output from which stages. + Default: -1. + qv_bias (bool): Enable bias for qv if True. Default: True. + attn_drop_rate (float): The drop out rate for attention layer. + Default 0.0 + drop_path_rate (float): Stochastic depth rate. Default 0.0. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN') + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + patch_norm (bool): Whether to add a norm in PatchEmbed Block. + Default: False. + final_norm (bool): Whether to add a additional layer to normalize + final feature map. Default: False. + num_fcs (int): The number of fully-connected layers for FFNs. + Default: 2. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + pretrained (str, optional): Model pretrained path. Default: None. + init_values (float): Initialize the values of BEiTAttention and FFN + with learnable scaling. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + img_size=224, + patch_size=16, + in_channels=3, + embed_dims=768, + num_layers=12, + num_heads=12, + mlp_ratio=4, + out_indices=-1, + qv_bias=True, + attn_drop_rate=0., + drop_path_rate=0., + norm_cfg=dict(type='LN'), + act_cfg=dict(type='GELU'), + patch_norm=False, + final_norm=False, + num_fcs=2, + norm_eval=False, + pretrained=None, + init_values=0.1, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + if isinstance(img_size, int): + img_size = to_2tuple(img_size) + elif isinstance(img_size, tuple): + if len(img_size) == 1: + img_size = to_2tuple(img_size[0]) + assert len(img_size) == 2, \ + f'The size of image should have length 1 or 2, ' \ + f'but got {len(img_size)}' + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be set at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is not None: + raise TypeError('pretrained must be a str or None') + + self.in_channels = in_channels + self.img_size = img_size + self.patch_size = patch_size + self.norm_eval = norm_eval + self.pretrained = pretrained + self.num_layers = num_layers + self.embed_dims = embed_dims + self.num_heads = num_heads + self.mlp_ratio = mlp_ratio + self.attn_drop_rate = attn_drop_rate + self.drop_path_rate = drop_path_rate + self.num_fcs = num_fcs + self.qv_bias = qv_bias + self.act_cfg = act_cfg + self.norm_cfg = norm_cfg + self.patch_norm = patch_norm + self.init_values = init_values + self.window_size = (img_size[0] // patch_size, + img_size[1] // patch_size) + self.patch_shape = self.window_size + self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dims)) + + self._build_patch_embedding() + self._build_layers() + + if isinstance(out_indices, int): + if out_indices == -1: + out_indices = num_layers - 1 + self.out_indices = [out_indices] + elif isinstance(out_indices, list) or isinstance(out_indices, tuple): + self.out_indices = out_indices + else: + raise TypeError('out_indices must be type of int, list or tuple') + + self.final_norm = final_norm + if final_norm: + self.norm1_name, norm1 = build_norm_layer( + norm_cfg, embed_dims, postfix=1) + self.add_module(self.norm1_name, norm1) + + def _build_patch_embedding(self): + """Build patch embedding layer.""" + self.patch_embed = PatchEmbed( + in_channels=self.in_channels, + embed_dims=self.embed_dims, + conv_type='Conv2d', + kernel_size=self.patch_size, + stride=self.patch_size, + padding=0, + norm_cfg=self.norm_cfg if self.patch_norm else None, + init_cfg=None) + + def _build_layers(self): + """Build transformer encoding layers.""" + + dpr = [ + x.item() + for x in torch.linspace(0, self.drop_path_rate, self.num_layers) + ] + self.layers = ModuleList() + for i in range(self.num_layers): + self.layers.append( + BEiTTransformerEncoderLayer( + embed_dims=self.embed_dims, + num_heads=self.num_heads, + feedforward_channels=self.mlp_ratio * self.embed_dims, + attn_drop_rate=self.attn_drop_rate, + drop_path_rate=dpr[i], + num_fcs=self.num_fcs, + bias='qv_bias' if self.qv_bias else False, + act_cfg=self.act_cfg, + norm_cfg=self.norm_cfg, + window_size=self.window_size, + init_values=self.init_values)) + + @property + def norm1(self): + return getattr(self, self.norm1_name) + + def _geometric_sequence_interpolation(self, src_size, dst_size, sequence, + num): + """Get new sequence via geometric sequence interpolation. + + Args: + src_size (int): Pos_embedding size in pre-trained model. + dst_size (int): Pos_embedding size in the current model. + sequence (tensor): The relative position bias of the pretrain + model after removing the extra tokens. + num (int): Number of attention heads. + Returns: + new_sequence (tensor): Geometric sequence interpolate the + pre-trained relative position bias to the size of + the current model. + """ + + def geometric_progression(a, r, n): + return a * (1.0 - r**n) / (1.0 - r) + + # Here is a binary function. + left, right = 1.01, 1.5 + while right - left > 1e-6: + q = (left + right) / 2.0 + gp = geometric_progression(1, q, src_size // 2) + if gp > dst_size // 2: + right = q + else: + left = q + # The position of each interpolated point is determined + # by the ratio obtained by dichotomy. + dis = [] + cur = 1 + for i in range(src_size // 2): + dis.append(cur) + cur += q**(i + 1) + r_ids = [-_ for _ in reversed(dis)] + x = r_ids + [0] + dis + y = r_ids + [0] + dis + t = dst_size // 2.0 + dx = np.arange(-t, t + 0.1, 1.0) + dy = np.arange(-t, t + 0.1, 1.0) + # Interpolation functions are being executed and called. + new_sequence = [] + for i in range(num): + z = sequence[:, i].view(src_size, src_size).float().numpy() + f = interpolate.interp2d(x, y, z, kind='cubic') + new_sequence.append( + torch.Tensor(f(dx, dy)).contiguous().view(-1, 1).to(sequence)) + new_sequence = torch.cat(new_sequence, dim=-1) + return new_sequence + + def resize_rel_pos_embed(self, checkpoint): + """Resize relative pos_embed weights. + + This function is modified from + https://github.com/microsoft/unilm/blob/master/beit/semantic_segmentation/mmcv_custom/checkpoint.py. # noqa: E501 + Copyright (c) Microsoft Corporation + Licensed under the MIT License + Args: + checkpoint (dict): Key and value of the pretrain model. + Returns: + state_dict (dict): Interpolate the relative pos_embed weights + in the pre-train model to the current model size. + """ + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + else: + state_dict = checkpoint + + all_keys = list(state_dict.keys()) + for key in all_keys: + if 'relative_position_index' in key: + state_dict.pop(key) + # In order to keep the center of pos_bias as consistent as + # possible after interpolation, and vice versa in the edge + # area, the geometric sequence interpolation method is adopted. + if 'relative_position_bias_table' in key: + rel_pos_bias = state_dict[key] + src_num_pos, num_attn_heads = rel_pos_bias.size() + dst_num_pos, _ = self.state_dict()[key].size() + dst_patch_shape = self.patch_shape + if dst_patch_shape[0] != dst_patch_shape[1]: + raise NotImplementedError() + # Count the number of extra tokens. + num_extra_tokens = dst_num_pos - ( + dst_patch_shape[0] * 2 - 1) * ( + dst_patch_shape[1] * 2 - 1) + src_size = int((src_num_pos - num_extra_tokens)**0.5) + dst_size = int((dst_num_pos - num_extra_tokens)**0.5) + if src_size != dst_size: + extra_tokens = rel_pos_bias[-num_extra_tokens:, :] + rel_pos_bias = rel_pos_bias[:-num_extra_tokens, :] + new_rel_pos_bias = self._geometric_sequence_interpolation( + src_size, dst_size, rel_pos_bias, num_attn_heads) + new_rel_pos_bias = torch.cat( + (new_rel_pos_bias, extra_tokens), dim=0) + state_dict[key] = new_rel_pos_bias + + return state_dict + + def init_weights(self): + + def _init_weights(m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=.02) + if isinstance(m, nn.Linear) and m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.LayerNorm): + nn.init.constant_(m.bias, 0) + nn.init.constant_(m.weight, 1.0) + + self.apply(_init_weights) + + if (isinstance(self.init_cfg, dict) + and self.init_cfg.get('type') == 'Pretrained'): + checkpoint = _load_checkpoint( + self.init_cfg['checkpoint'], logger=None, map_location='cpu') + state_dict = self.resize_rel_pos_embed(checkpoint) + self.load_state_dict(state_dict, False) + elif self.init_cfg is not None: + super().init_weights() + else: + # We only implement the 'jax_impl' initialization implemented at + # https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py#L353 # noqa: E501 + # Copyright 2019 Ross Wightman + # Licensed under the Apache License, Version 2.0 (the "License") + trunc_normal_(self.cls_token, std=.02) + for n, m in self.named_modules(): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=.02) + if m.bias is not None: + if 'ffn' in n: + nn.init.normal_(m.bias, mean=0., std=1e-6) + else: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Conv2d): + kaiming_init(m, mode='fan_in', bias=0.) + elif isinstance(m, (_BatchNorm, nn.GroupNorm, nn.LayerNorm)): + constant_init(m, val=1.0, bias=0.) + + def forward(self, inputs): + B = inputs.shape[0] + + x, hw_shape = self.patch_embed(inputs) + + # stole cls_tokens impl from Phil Wang, thanks + cls_tokens = self.cls_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, x), dim=1) + + outs = [] + for i, layer in enumerate(self.layers): + x = layer(x) + if i == len(self.layers) - 1: + if self.final_norm: + x = self.norm1(x) + if i in self.out_indices: + # Remove class token and reshape token for decoder head + out = x[:, 1:] + B, _, C = out.shape + out = out.reshape(B, hw_shape[0], hw_shape[1], + C).permute(0, 3, 1, 2).contiguous() + outs.append(out) + + return tuple(outs) + + def train(self, mode=True): + super().train(mode) + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, nn.LayerNorm): + m.eval() diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/bisenetv1.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/bisenetv1.py new file mode 100644 index 0000000..ca58bf9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/bisenetv1.py @@ -0,0 +1,332 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..utils import resize + + +class SpatialPath(BaseModule): + """Spatial Path to preserve the spatial size of the original input image + and encode affluent spatial information. + + Args: + in_channels(int): The number of channels of input + image. Default: 3. + num_channels (Tuple[int]): The number of channels of + each layers in Spatial Path. + Default: (64, 64, 64, 128). + Returns: + x (torch.Tensor): Feature map for Feature Fusion Module. + """ + + def __init__(self, + in_channels=3, + num_channels=(64, 64, 64, 128), + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + assert len(num_channels) == 4, 'Length of input channels \ + of Spatial Path must be 4!' + + self.layers = [] + for i in range(len(num_channels)): + layer_name = f'layer{i + 1}' + self.layers.append(layer_name) + if i == 0: + self.add_module( + layer_name, + ConvModule( + in_channels=in_channels, + out_channels=num_channels[i], + kernel_size=7, + stride=2, + padding=3, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + elif i == len(num_channels) - 1: + self.add_module( + layer_name, + ConvModule( + in_channels=num_channels[i - 1], + out_channels=num_channels[i], + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + else: + self.add_module( + layer_name, + ConvModule( + in_channels=num_channels[i - 1], + out_channels=num_channels[i], + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + def forward(self, x): + for i, layer_name in enumerate(self.layers): + layer_stage = getattr(self, layer_name) + x = layer_stage(x) + return x + + +class AttentionRefinementModule(BaseModule): + """Attention Refinement Module (ARM) to refine the features of each stage. + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels. + Returns: + x_out (torch.Tensor): Feature map of Attention Refinement Module. + """ + + def __init__(self, + in_channels, + out_channel, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.conv_layer = ConvModule( + in_channels=in_channels, + out_channels=out_channel, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.atten_conv_layer = nn.Sequential( + nn.AdaptiveAvgPool2d((1, 1)), + ConvModule( + in_channels=out_channel, + out_channels=out_channel, + kernel_size=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), nn.Sigmoid()) + + def forward(self, x): + x = self.conv_layer(x) + x_atten = self.atten_conv_layer(x) + x_out = x * x_atten + return x_out + + +class ContextPath(BaseModule): + """Context Path to provide sufficient receptive field. + + Args: + backbone_cfg:(dict): Config of backbone of + Context Path. + context_channels (Tuple[int]): The number of channel numbers + of various modules in Context Path. + Default: (128, 256, 512). + align_corners (bool, optional): The align_corners argument of + resize operation. Default: False. + Returns: + x_16_up, x_32_up (torch.Tensor, torch.Tensor): Two feature maps + undergoing upsampling from 1/16 and 1/32 downsampling + feature maps. These two feature maps are used for Feature + Fusion Module and Auxiliary Head. + """ + + def __init__(self, + backbone_cfg, + context_channels=(128, 256, 512), + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + assert len(context_channels) == 3, 'Length of input channels \ + of Context Path must be 3!' + + self.backbone = MODELS.build(backbone_cfg) + + self.align_corners = align_corners + self.arm16 = AttentionRefinementModule(context_channels[1], + context_channels[0]) + self.arm32 = AttentionRefinementModule(context_channels[2], + context_channels[0]) + self.conv_head32 = ConvModule( + in_channels=context_channels[0], + out_channels=context_channels[0], + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.conv_head16 = ConvModule( + in_channels=context_channels[0], + out_channels=context_channels[0], + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.gap_conv = nn.Sequential( + nn.AdaptiveAvgPool2d((1, 1)), + ConvModule( + in_channels=context_channels[2], + out_channels=context_channels[0], + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + def forward(self, x): + x_4, x_8, x_16, x_32 = self.backbone(x) + x_gap = self.gap_conv(x_32) + + x_32_arm = self.arm32(x_32) + x_32_sum = x_32_arm + x_gap + x_32_up = resize(input=x_32_sum, size=x_16.shape[2:], mode='nearest') + x_32_up = self.conv_head32(x_32_up) + + x_16_arm = self.arm16(x_16) + x_16_sum = x_16_arm + x_32_up + x_16_up = resize(input=x_16_sum, size=x_8.shape[2:], mode='nearest') + x_16_up = self.conv_head16(x_16_up) + + return x_16_up, x_32_up + + +class FeatureFusionModule(BaseModule): + """Feature Fusion Module to fuse low level output feature of Spatial Path + and high level output feature of Context Path. + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels. + Returns: + x_out (torch.Tensor): Feature map of Feature Fusion Module. + """ + + def __init__(self, + in_channels, + out_channels, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.conv1 = ConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.gap = nn.AdaptiveAvgPool2d((1, 1)) + self.conv_atten = nn.Sequential( + ConvModule( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), nn.Sigmoid()) + + def forward(self, x_sp, x_cp): + x_concat = torch.cat([x_sp, x_cp], dim=1) + x_fuse = self.conv1(x_concat) + x_atten = self.gap(x_fuse) + # Note: No BN and more 1x1 conv in paper. + x_atten = self.conv_atten(x_atten) + x_atten = x_fuse * x_atten + x_out = x_atten + x_fuse + return x_out + + +@MODELS.register_module() +class BiSeNetV1(BaseModule): + """BiSeNetV1 backbone. + + This backbone is the implementation of `BiSeNet: Bilateral + Segmentation Network for Real-time Semantic + Segmentation `_. + + Args: + backbone_cfg:(dict): Config of backbone of + Context Path. + in_channels (int): The number of channels of input + image. Default: 3. + spatial_channels (Tuple[int]): Size of channel numbers of + various layers in Spatial Path. + Default: (64, 64, 64, 128). + context_channels (Tuple[int]): Size of channel numbers of + various modules in Context Path. + Default: (128, 256, 512). + out_indices (Tuple[int] | int, optional): Output from which stages. + Default: (0, 1, 2). + align_corners (bool, optional): The align_corners argument of + resize operation in Bilateral Guided Aggregation Layer. + Default: False. + out_channels(int): The number of channels of output. + It must be the same with `in_channels` of decode_head. + Default: 256. + """ + + def __init__(self, + backbone_cfg, + in_channels=3, + spatial_channels=(64, 64, 64, 128), + context_channels=(128, 256, 512), + out_indices=(0, 1, 2), + align_corners=False, + out_channels=256, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='ReLU'), + init_cfg=None): + + super().__init__(init_cfg=init_cfg) + assert len(spatial_channels) == 4, 'Length of input channels \ + of Spatial Path must be 4!' + + assert len(context_channels) == 3, 'Length of input channels \ + of Context Path must be 3!' + + self.out_indices = out_indices + self.align_corners = align_corners + self.context_path = ContextPath(backbone_cfg, context_channels, + self.align_corners) + self.spatial_path = SpatialPath(in_channels, spatial_channels) + self.ffm = FeatureFusionModule(context_channels[1], out_channels) + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + def forward(self, x): + # stole refactoring code from Coin Cheung, thanks + x_context8, x_context16 = self.context_path(x) + x_spatial = self.spatial_path(x) + x_fuse = self.ffm(x_spatial, x_context8) + + outs = [x_fuse, x_context8, x_context16] + outs = [outs[i] for i in self.out_indices] + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/bisenetv2.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/bisenetv2.py new file mode 100644 index 0000000..32aa498 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/bisenetv2.py @@ -0,0 +1,622 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import (ConvModule, DepthwiseSeparableConvModule, + build_activation_layer, build_norm_layer) +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..utils import resize + + +class DetailBranch(BaseModule): + """Detail Branch with wide channels and shallow layers to capture low-level + details and generate high-resolution feature representation. + + Args: + detail_channels (Tuple[int]): Size of channel numbers of each stage + in Detail Branch, in paper it has 3 stages. + Default: (64, 64, 128). + in_channels (int): Number of channels of input image. Default: 3. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Feature map of Detail Branch. + """ + + def __init__(self, + detail_channels=(64, 64, 128), + in_channels=3, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + detail_branch = [] + for i in range(len(detail_channels)): + if i == 0: + detail_branch.append( + nn.Sequential( + ConvModule( + in_channels=in_channels, + out_channels=detail_channels[i], + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + in_channels=detail_channels[i], + out_channels=detail_channels[i], + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg))) + else: + detail_branch.append( + nn.Sequential( + ConvModule( + in_channels=detail_channels[i - 1], + out_channels=detail_channels[i], + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + in_channels=detail_channels[i], + out_channels=detail_channels[i], + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + in_channels=detail_channels[i], + out_channels=detail_channels[i], + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg))) + self.detail_branch = nn.ModuleList(detail_branch) + + def forward(self, x): + for stage in self.detail_branch: + x = stage(x) + return x + + +class StemBlock(BaseModule): + """Stem Block at the beginning of Semantic Branch. + + Args: + in_channels (int): Number of input channels. + Default: 3. + out_channels (int): Number of output channels. + Default: 16. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): First feature map in Semantic Branch. + """ + + def __init__(self, + in_channels=3, + out_channels=16, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.conv_first = ConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.convs = nn.Sequential( + ConvModule( + in_channels=out_channels, + out_channels=out_channels // 2, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + in_channels=out_channels // 2, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.pool = nn.MaxPool2d( + kernel_size=3, stride=2, padding=1, ceil_mode=False) + self.fuse_last = ConvModule( + in_channels=out_channels * 2, + out_channels=out_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + x = self.conv_first(x) + x_left = self.convs(x) + x_right = self.pool(x) + x = self.fuse_last(torch.cat([x_left, x_right], dim=1)) + return x + + +class GELayer(BaseModule): + """Gather-and-Expansion Layer. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + exp_ratio (int): Expansion ratio for middle channels. + Default: 6. + stride (int): Stride of GELayer. Default: 1 + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Intermediate feature map in + Semantic Branch. + """ + + def __init__(self, + in_channels, + out_channels, + exp_ratio=6, + stride=1, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + mid_channel = in_channels * exp_ratio + self.conv1 = ConvModule( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + if stride == 1: + self.dwconv = nn.Sequential( + # ReLU in ConvModule not shown in paper + ConvModule( + in_channels=in_channels, + out_channels=mid_channel, + kernel_size=3, + stride=stride, + padding=1, + groups=in_channels, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.shortcut = None + else: + self.dwconv = nn.Sequential( + ConvModule( + in_channels=in_channels, + out_channels=mid_channel, + kernel_size=3, + stride=stride, + padding=1, + groups=in_channels, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), + # ReLU in ConvModule not shown in paper + ConvModule( + in_channels=mid_channel, + out_channels=mid_channel, + kernel_size=3, + stride=1, + padding=1, + groups=mid_channel, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ) + self.shortcut = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=norm_cfg, + pw_act_cfg=None, + )) + + self.conv2 = nn.Sequential( + ConvModule( + in_channels=mid_channel, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None, + )) + + self.act = build_activation_layer(act_cfg) + + def forward(self, x): + identity = x + x = self.conv1(x) + x = self.dwconv(x) + x = self.conv2(x) + if self.shortcut is not None: + shortcut = self.shortcut(identity) + x = x + shortcut + else: + x = x + identity + x = self.act(x) + return x + + +class CEBlock(BaseModule): + """Context Embedding Block for large receptive filed in Semantic Branch. + + Args: + in_channels (int): Number of input channels. + Default: 3. + out_channels (int): Number of output channels. + Default: 16. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Last feature map in Semantic Branch. + """ + + def __init__(self, + in_channels=3, + out_channels=16, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + self.gap = nn.Sequential( + nn.AdaptiveAvgPool2d((1, 1)), + build_norm_layer(norm_cfg, self.in_channels)[1]) + self.conv_gap = ConvModule( + in_channels=self.in_channels, + out_channels=self.out_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + # Note: in paper here is naive conv2d, no bn-relu + self.conv_last = ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + identity = x + x = self.gap(x) + x = self.conv_gap(x) + x = identity + x + x = self.conv_last(x) + return x + + +class SemanticBranch(BaseModule): + """Semantic Branch which is lightweight with narrow channels and deep + layers to obtain high-level semantic context. + + Args: + semantic_channels(Tuple[int]): Size of channel numbers of + various stages in Semantic Branch. + Default: (16, 32, 64, 128). + in_channels (int): Number of channels of input image. Default: 3. + exp_ratio (int): Expansion ratio for middle channels. + Default: 6. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + semantic_outs (List[torch.Tensor]): List of several feature maps + for auxiliary heads (Booster) and Bilateral + Guided Aggregation Layer. + """ + + def __init__(self, + semantic_channels=(16, 32, 64, 128), + in_channels=3, + exp_ratio=6, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.semantic_channels = semantic_channels + self.semantic_stages = [] + for i in range(len(semantic_channels)): + stage_name = f'stage{i + 1}' + self.semantic_stages.append(stage_name) + if i == 0: + self.add_module( + stage_name, + StemBlock(self.in_channels, semantic_channels[i])) + elif i == (len(semantic_channels) - 1): + self.add_module( + stage_name, + nn.Sequential( + GELayer(semantic_channels[i - 1], semantic_channels[i], + exp_ratio, 2), + GELayer(semantic_channels[i], semantic_channels[i], + exp_ratio, 1), + GELayer(semantic_channels[i], semantic_channels[i], + exp_ratio, 1), + GELayer(semantic_channels[i], semantic_channels[i], + exp_ratio, 1))) + else: + self.add_module( + stage_name, + nn.Sequential( + GELayer(semantic_channels[i - 1], semantic_channels[i], + exp_ratio, 2), + GELayer(semantic_channels[i], semantic_channels[i], + exp_ratio, 1))) + + self.add_module(f'stage{len(semantic_channels)}_CEBlock', + CEBlock(semantic_channels[-1], semantic_channels[-1])) + self.semantic_stages.append(f'stage{len(semantic_channels)}_CEBlock') + + def forward(self, x): + semantic_outs = [] + for stage_name in self.semantic_stages: + semantic_stage = getattr(self, stage_name) + x = semantic_stage(x) + semantic_outs.append(x) + return semantic_outs + + +class BGALayer(BaseModule): + """Bilateral Guided Aggregation Layer to fuse the complementary information + from both Detail Branch and Semantic Branch. + + Args: + out_channels (int): Number of output channels. + Default: 128. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + output (torch.Tensor): Output feature map for Segment heads. + """ + + def __init__(self, + out_channels=128, + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.out_channels = out_channels + self.align_corners = align_corners + self.detail_dwconv = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=None, + pw_act_cfg=None, + )) + self.detail_down = nn.Sequential( + ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=2, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), + nn.AvgPool2d(kernel_size=3, stride=2, padding=1, ceil_mode=False)) + self.semantic_conv = nn.Sequential( + ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None)) + self.semantic_dwconv = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=None, + pw_act_cfg=None, + )) + self.conv = ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + inplace=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + + def forward(self, x_d, x_s): + detail_dwconv = self.detail_dwconv(x_d) + detail_down = self.detail_down(x_d) + semantic_conv = self.semantic_conv(x_s) + semantic_dwconv = self.semantic_dwconv(x_s) + semantic_conv = resize( + input=semantic_conv, + size=detail_dwconv.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + fuse_1 = detail_dwconv * torch.sigmoid(semantic_conv) + fuse_2 = detail_down * torch.sigmoid(semantic_dwconv) + fuse_2 = resize( + input=fuse_2, + size=fuse_1.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + output = self.conv(fuse_1 + fuse_2) + return output + + +@MODELS.register_module() +class BiSeNetV2(BaseModule): + """BiSeNetV2: Bilateral Network with Guided Aggregation for + Real-time Semantic Segmentation. + + This backbone is the implementation of + `BiSeNetV2 `_. + + Args: + in_channels (int): Number of channel of input image. Default: 3. + detail_channels (Tuple[int], optional): Channels of each stage + in Detail Branch. Default: (64, 64, 128). + semantic_channels (Tuple[int], optional): Channels of each stage + in Semantic Branch. Default: (16, 32, 64, 128). + See Table 1 and Figure 3 of paper for more details. + semantic_expansion_ratio (int, optional): The expansion factor + expanding channel number of middle channels in Semantic Branch. + Default: 6. + bga_channels (int, optional): Number of middle channels in + Bilateral Guided Aggregation Layer. Default: 128. + out_indices (Tuple[int] | int, optional): Output from which stages. + Default: (0, 1, 2, 3, 4). + align_corners (bool, optional): The align_corners argument of + resize operation in Bilateral Guided Aggregation Layer. + Default: False. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels=3, + detail_channels=(64, 64, 128), + semantic_channels=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + if init_cfg is None: + init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', val=1, layer=['_BatchNorm', 'GroupNorm']) + ] + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_indices = out_indices + self.detail_channels = detail_channels + self.semantic_channels = semantic_channels + self.semantic_expansion_ratio = semantic_expansion_ratio + self.bga_channels = bga_channels + self.align_corners = align_corners + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.detail = DetailBranch(self.detail_channels, self.in_channels) + self.semantic = SemanticBranch(self.semantic_channels, + self.in_channels, + self.semantic_expansion_ratio) + self.bga = BGALayer(self.bga_channels, self.align_corners) + + def forward(self, x): + # stole refactoring code from Coin Cheung, thanks + x_detail = self.detail(x) + x_semantic_lst = self.semantic(x) + x_head = self.bga(x_detail, x_semantic_lst[-1]) + outs = [x_head] + x_semantic_lst[:-1] + outs = [outs[i] for i in self.out_indices] + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/cgnet.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/cgnet.py new file mode 100644 index 0000000..b74b494 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/cgnet.py @@ -0,0 +1,372 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import ConvModule, build_conv_layer, build_norm_layer +from mmengine.model import BaseModule +from mmengine.utils.dl_utils.parrots_wrapper import _BatchNorm + +from mmseg.registry import MODELS + + +class GlobalContextExtractor(nn.Module): + """Global Context Extractor for CGNet. + + This class is employed to refine the joint feature of both local feature + and surrounding context. + + Args: + channel (int): Number of input feature channels. + reduction (int): Reductions for global context extractor. Default: 16. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + """ + + def __init__(self, channel, reduction=16, with_cp=False): + super().__init__() + self.channel = channel + self.reduction = reduction + assert reduction >= 1 and channel >= reduction + self.with_cp = with_cp + self.avg_pool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Sequential( + nn.Linear(channel, channel // reduction), nn.ReLU(inplace=True), + nn.Linear(channel // reduction, channel), nn.Sigmoid()) + + def forward(self, x): + + def _inner_forward(x): + num_batch, num_channel = x.size()[:2] + y = self.avg_pool(x).view(num_batch, num_channel) + y = self.fc(y).view(num_batch, num_channel, 1, 1) + return x * y + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + return out + + +class ContextGuidedBlock(nn.Module): + """Context Guided Block for CGNet. + + This class consists of four components: local feature extractor, + surrounding feature extractor, joint feature extractor and global + context extractor. + + Args: + in_channels (int): Number of input feature channels. + out_channels (int): Number of output feature channels. + dilation (int): Dilation rate for surrounding context extractor. + Default: 2. + reduction (int): Reduction for global context extractor. Default: 16. + skip_connect (bool): Add input to output or not. Default: True. + downsample (bool): Downsample the input to 1/2 or not. Default: False. + conv_cfg (dict): Config dict for convolution layer. + Default: None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN', requires_grad=True). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='PReLU'). + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + """ + + def __init__(self, + in_channels, + out_channels, + dilation=2, + reduction=16, + skip_connect=True, + downsample=False, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='PReLU'), + with_cp=False): + super().__init__() + self.with_cp = with_cp + self.downsample = downsample + + channels = out_channels if downsample else out_channels // 2 + if 'type' in act_cfg and act_cfg['type'] == 'PReLU': + act_cfg['num_parameters'] = channels + kernel_size = 3 if downsample else 1 + stride = 2 if downsample else 1 + padding = (kernel_size - 1) // 2 + + self.conv1x1 = ConvModule( + in_channels, + channels, + kernel_size, + stride, + padding, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + self.f_loc = build_conv_layer( + conv_cfg, + channels, + channels, + kernel_size=3, + padding=1, + groups=channels, + bias=False) + self.f_sur = build_conv_layer( + conv_cfg, + channels, + channels, + kernel_size=3, + padding=dilation, + groups=channels, + dilation=dilation, + bias=False) + + self.bn = build_norm_layer(norm_cfg, 2 * channels)[1] + self.activate = nn.PReLU(2 * channels) + + if downsample: + self.bottleneck = build_conv_layer( + conv_cfg, + 2 * channels, + out_channels, + kernel_size=1, + bias=False) + + self.skip_connect = skip_connect and not downsample + self.f_glo = GlobalContextExtractor(out_channels, reduction, with_cp) + + def forward(self, x): + + def _inner_forward(x): + out = self.conv1x1(x) + loc = self.f_loc(out) + sur = self.f_sur(out) + + joi_feat = torch.cat([loc, sur], 1) # the joint feature + joi_feat = self.bn(joi_feat) + joi_feat = self.activate(joi_feat) + if self.downsample: + joi_feat = self.bottleneck(joi_feat) # channel = out_channels + # f_glo is employed to refine the joint feature + out = self.f_glo(joi_feat) + + if self.skip_connect: + return x + out + else: + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + return out + + +class InputInjection(nn.Module): + """Downsampling module for CGNet.""" + + def __init__(self, num_downsampling): + super().__init__() + self.pool = nn.ModuleList() + for i in range(num_downsampling): + self.pool.append(nn.AvgPool2d(3, stride=2, padding=1)) + + def forward(self, x): + for pool in self.pool: + x = pool(x) + return x + + +@MODELS.register_module() +class CGNet(BaseModule): + """CGNet backbone. + + This backbone is the implementation of `A Light-weight Context Guided + Network for Semantic Segmentation `_. + + Args: + in_channels (int): Number of input image channels. Normally 3. + num_channels (tuple[int]): Numbers of feature channels at each stages. + Default: (32, 64, 128). + num_blocks (tuple[int]): Numbers of CG blocks at stage 1 and stage 2. + Default: (3, 21). + dilations (tuple[int]): Dilation rate for surrounding context + extractors at stage 1 and stage 2. Default: (2, 4). + reductions (tuple[int]): Reductions for global context extractors at + stage 1 and stage 2. Default: (8, 16). + conv_cfg (dict): Config dict for convolution layer. + Default: None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN', requires_grad=True). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='PReLU'). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + in_channels=3, + num_channels=(32, 64, 128), + num_blocks=(3, 21), + dilations=(2, 4), + reductions=(8, 16), + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='PReLU'), + norm_eval=False, + with_cp=False, + pretrained=None, + init_cfg=None): + + super().__init__(init_cfg) + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be setting at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is a deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer=['Conv2d', 'Linear']), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']), + dict(type='Constant', val=0, layer='PReLU') + ] + else: + raise TypeError('pretrained must be a str or None') + + self.in_channels = in_channels + self.num_channels = num_channels + assert isinstance(self.num_channels, tuple) and len( + self.num_channels) == 3 + self.num_blocks = num_blocks + assert isinstance(self.num_blocks, tuple) and len(self.num_blocks) == 2 + self.dilations = dilations + assert isinstance(self.dilations, tuple) and len(self.dilations) == 2 + self.reductions = reductions + assert isinstance(self.reductions, tuple) and len(self.reductions) == 2 + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + if 'type' in self.act_cfg and self.act_cfg['type'] == 'PReLU': + self.act_cfg['num_parameters'] = num_channels[0] + self.norm_eval = norm_eval + self.with_cp = with_cp + + cur_channels = in_channels + self.stem = nn.ModuleList() + for i in range(3): + self.stem.append( + ConvModule( + cur_channels, + num_channels[0], + 3, + 2 if i == 0 else 1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + cur_channels = num_channels[0] + + self.inject_2x = InputInjection(1) # down-sample for Input, factor=2 + self.inject_4x = InputInjection(2) # down-sample for Input, factor=4 + + cur_channels += in_channels + self.norm_prelu_0 = nn.Sequential( + build_norm_layer(norm_cfg, cur_channels)[1], + nn.PReLU(cur_channels)) + + # stage 1 + self.level1 = nn.ModuleList() + for i in range(num_blocks[0]): + self.level1.append( + ContextGuidedBlock( + cur_channels if i == 0 else num_channels[1], + num_channels[1], + dilations[0], + reductions[0], + downsample=(i == 0), + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + with_cp=with_cp)) # CG block + + cur_channels = 2 * num_channels[1] + in_channels + self.norm_prelu_1 = nn.Sequential( + build_norm_layer(norm_cfg, cur_channels)[1], + nn.PReLU(cur_channels)) + + # stage 2 + self.level2 = nn.ModuleList() + for i in range(num_blocks[1]): + self.level2.append( + ContextGuidedBlock( + cur_channels if i == 0 else num_channels[2], + num_channels[2], + dilations[1], + reductions[1], + downsample=(i == 0), + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + with_cp=with_cp)) # CG block + + cur_channels = 2 * num_channels[2] + self.norm_prelu_2 = nn.Sequential( + build_norm_layer(norm_cfg, cur_channels)[1], + nn.PReLU(cur_channels)) + + def forward(self, x): + output = [] + + # stage 0 + inp_2x = self.inject_2x(x) + inp_4x = self.inject_4x(x) + for layer in self.stem: + x = layer(x) + x = self.norm_prelu_0(torch.cat([x, inp_2x], 1)) + output.append(x) + + # stage 1 + for i, layer in enumerate(self.level1): + x = layer(x) + if i == 0: + down1 = x + x = self.norm_prelu_1(torch.cat([x, down1, inp_4x], 1)) + output.append(x) + + # stage 2 + for i, layer in enumerate(self.level2): + x = layer(x) + if i == 0: + down2 = x + x = self.norm_prelu_2(torch.cat([down2, x], 1)) + output.append(x) + + return output + + def train(self, mode=True): + """Convert the model into training mode will keeping the normalization + layer freezed.""" + super().train(mode) + if mode and self.norm_eval: + for m in self.modules(): + # trick: eval have effect on BatchNorm only + if isinstance(m, _BatchNorm): + m.eval() diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/ddrnet.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/ddrnet.py new file mode 100644 index 0000000..4508aad --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/ddrnet.py @@ -0,0 +1,222 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import ConvModule, build_norm_layer +from mmengine.model import BaseModule + +from mmseg.models.utils import DAPPM, BasicBlock, Bottleneck, resize +from mmseg.registry import MODELS +from mmseg.utils import OptConfigType + + +@MODELS.register_module() +class DDRNet(BaseModule): + """DDRNet backbone. + + This backbone is the implementation of `Deep Dual-resolution Networks for + Real-time and Accurate Semantic Segmentation of Road Scenes + `_. + Modified from https://github.com/ydhongHIT/DDRNet. + + Args: + in_channels (int): Number of input image channels. Default: 3. + channels: (int): The base channels of DDRNet. Default: 32. + ppm_channels (int): The channels of PPM module. Default: 128. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + norm_cfg (dict): Config dict to build norm layer. + Default: dict(type='BN', requires_grad=True). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU', inplace=True). + init_cfg (dict, optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels: int = 3, + channels: int = 32, + ppm_channels: int = 128, + align_corners: bool = False, + norm_cfg: OptConfigType = dict(type='BN', requires_grad=True), + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + init_cfg: OptConfigType = None): + super().__init__(init_cfg) + + self.in_channels = in_channels + self.ppm_channels = ppm_channels + + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.align_corners = align_corners + + # stage 0-2 + self.stem = self._make_stem_layer(in_channels, channels, num_blocks=2) + self.relu = nn.ReLU() + + # low resolution(context) branch + self.context_branch_layers = nn.ModuleList() + for i in range(3): + self.context_branch_layers.append( + self._make_layer( + block=BasicBlock if i < 2 else Bottleneck, + inplanes=channels * 2**(i + 1), + planes=channels * 8 if i > 0 else channels * 4, + num_blocks=2 if i < 2 else 1, + stride=2)) + + # bilateral fusion + self.compression_1 = ConvModule( + channels * 4, + channels * 2, + kernel_size=1, + norm_cfg=self.norm_cfg, + act_cfg=None) + self.down_1 = ConvModule( + channels * 2, + channels * 4, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=None) + + self.compression_2 = ConvModule( + channels * 8, + channels * 2, + kernel_size=1, + norm_cfg=self.norm_cfg, + act_cfg=None) + self.down_2 = nn.Sequential( + ConvModule( + channels * 2, + channels * 4, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + ConvModule( + channels * 4, + channels * 8, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=None)) + + # high resolution(spatial) branch + self.spatial_branch_layers = nn.ModuleList() + for i in range(3): + self.spatial_branch_layers.append( + self._make_layer( + block=BasicBlock if i < 2 else Bottleneck, + inplanes=channels * 2, + planes=channels * 2, + num_blocks=2 if i < 2 else 1, + )) + + self.spp = DAPPM( + channels * 16, ppm_channels, channels * 4, num_scales=5) + + def _make_stem_layer(self, in_channels, channels, num_blocks): + layers = [ + ConvModule( + in_channels, + channels, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + ConvModule( + channels, + channels, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + ] + + layers.extend([ + self._make_layer(BasicBlock, channels, channels, num_blocks), + nn.ReLU(), + self._make_layer( + BasicBlock, channels, channels * 2, num_blocks, stride=2), + nn.ReLU(), + ]) + + return nn.Sequential(*layers) + + def _make_layer(self, block, inplanes, planes, num_blocks, stride=1): + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d( + inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False), + build_norm_layer(self.norm_cfg, planes * block.expansion)[1]) + + layers = [ + block( + in_channels=inplanes, + channels=planes, + stride=stride, + downsample=downsample) + ] + inplanes = planes * block.expansion + for i in range(1, num_blocks): + layers.append( + block( + in_channels=inplanes, + channels=planes, + stride=1, + norm_cfg=self.norm_cfg, + act_cfg_out=None if i == num_blocks - 1 else self.act_cfg)) + + return nn.Sequential(*layers) + + def forward(self, x): + """Forward function.""" + out_size = (x.shape[-2] // 8, x.shape[-1] // 8) + + # stage 0-2 + x = self.stem(x) + + # stage3 + x_c = self.context_branch_layers[0](x) + x_s = self.spatial_branch_layers[0](x) + comp_c = self.compression_1(self.relu(x_c)) + x_c += self.down_1(self.relu(x_s)) + x_s += resize( + comp_c, + size=out_size, + mode='bilinear', + align_corners=self.align_corners) + if self.training: + temp_context = x_s.clone() + + # stage4 + x_c = self.context_branch_layers[1](self.relu(x_c)) + x_s = self.spatial_branch_layers[1](self.relu(x_s)) + comp_c = self.compression_2(self.relu(x_c)) + x_c += self.down_2(self.relu(x_s)) + x_s += resize( + comp_c, + size=out_size, + mode='bilinear', + align_corners=self.align_corners) + + # stage5 + x_s = self.spatial_branch_layers[2](self.relu(x_s)) + x_c = self.context_branch_layers[2](self.relu(x_c)) + x_c = self.spp(x_c) + x_c = resize( + x_c, + size=out_size, + mode='bilinear', + align_corners=self.align_corners) + + return (temp_context, x_s + x_c) if self.training else x_s + x_c diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/en_bisenetv2.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/en_bisenetv2.py new file mode 100644 index 0000000..35cae6e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/en_bisenetv2.py @@ -0,0 +1,418 @@ +# === Begin: Final corrected code to be appended for EnBiSeNetV2 === +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, build_conv_layer, build_norm_layer +from mmengine.model import BaseModule, Sequential +from mmseg.registry import MODELS +# Import existing BiSeNetV2 components to inherit from them +from.bisenetv2 import BiSeNetV2, GELayer, StemBlock + +# --- Helper Modules for EnBiSeNetV2 --- + +class DepthwiseSeparableConvModule(BaseModule): + """Depthwise Separable Convolutional Module.""" + def __init__(self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg) + self.depthwise_conv = ConvModule( + in_channels, + in_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=in_channels, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.pointwise_conv = ConvModule( + in_channels, + out_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + x = self.depthwise_conv(x) + x = self.pointwise_conv(x) + return x + +class SpatialAttentionModule(BaseModule): + """Spatial Attention Module.""" + def __init__(self, init_cfg=None): + super().__init__(init_cfg) + self.conv = ConvModule( + 2, 1, kernel_size=7, padding=3, act_cfg=None, norm_cfg=None) + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + avg_out = torch.mean(x, dim=1, keepdim=True) + max_out, _ = torch.max(x, dim=1, keepdim=True) + attention_map = torch.cat([avg_out, max_out], dim=1) + attention_map = self.conv(attention_map) + attention_map = self.sigmoid(attention_map) + return x * attention_map + +class ChannelAttentionModule(BaseModule): + """Channel Attention Module.""" + def __init__(self, in_channels, reduction=16, init_cfg=None): + super().__init__(init_cfg) + self.avg_pool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Sequential( + nn.Linear(in_channels, in_channels // reduction, bias=False), + nn.ReLU(inplace=True), + nn.Linear(in_channels // reduction, in_channels, bias=False), + nn.Sigmoid() + ) + + def forward(self, x): + b, c, _, _ = x.size() + y = self.avg_pool(x).view(b, c) + y = self.fc(y).view(b, c, 1, 1) + return x * y.expand_as(x) + +class SEModule(BaseModule): + """Squeeze-and-Excitation Module.""" + def __init__(self, channels, reduction=16, init_cfg=None): + super().__init__(init_cfg) + self.avg_pool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Sequential( + nn.Linear(channels, channels // reduction, bias=False), + nn.ReLU(inplace=True), + nn.Linear(channels // reduction, channels, bias=False), + nn.Sigmoid(), + ) + + def forward(self, x): + b, c, _, _ = x.size() + y = self.avg_pool(x).view(b, c) + y = self.fc(y).view(b, c, 1, 1) + return x * y + +class CEBlockSimAspp(BaseModule): + """Simplified ASPP for Context Embedding.""" + def __init__(self, in_channels, out_channels, rates=(1, 6, 12), norm_cfg=dict(type='BN'), act_cfg=dict(type='ReLU'), init_cfg=None): + super().__init__(init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + + self.global_avg_pool = nn.Sequential( + nn.AdaptiveAvgPool2d((1, 1)), + ConvModule(in_channels, out_channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg) + ) + + self.aspp_branches = nn.ModuleList() + for rate in rates: + reduced_channels = in_channels // 2 + if reduced_channels == 0: + reduced_channels = 1 + self.aspp_branches.append( + nn.Sequential( + ConvModule(in_channels, reduced_channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg), + ConvModule(reduced_channels, out_channels, 3, padding=rate, dilation=rate, norm_cfg=norm_cfg, act_cfg=act_cfg) + ) + ) + + self.project = ConvModule(out_channels * (len(rates) + 1), out_channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg) + + def forward(self, x): + res = []# <-- FIX + res.append(nn.functional.interpolate(self.global_avg_pool(x), size=x.shape[2:], mode='bilinear', align_corners=False)) + for branch in self.aspp_branches: + res.append(branch(x)) + + res = torch.cat(res, dim=1) + return self.project(res) + +# In en_bisenetv2.py, replace the whole GELayerWithSE class with this: + +class GELayerWithSE(BaseModule): + """Gather-and-Expansion Layer with optional SE Module.""" + def __init__(self, in_channels, out_channels, mid_channels, stride, add_se_module=False, + norm_cfg=dict(type='BN'), act_cfg=dict(type='ReLU'), init_cfg=None): + super().__init__(init_cfg) + + # 1. Pointwise Conv for channel expansion + self.pw_conv1 = ConvModule( + in_channels, mid_channels, kernel_size=1, norm_cfg=norm_cfg, act_cfg=act_cfg) + + # 2. Depthwise Conv + self.dw_conv = ConvModule( + mid_channels, + mid_channels, + kernel_size=3, + stride=stride, + padding=1, + groups=mid_channels, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + # 3. Squeeze-and-Excitation module + self.add_se_module = add_se_module + if self.add_se_module: + self.se = SEModule(mid_channels) + + # 4. Pointwise Conv for channel projection + self.pw_conv2 = ConvModule( + mid_channels, out_channels, kernel_size=1, norm_cfg=norm_cfg, act_cfg=None) + + # 5. Shortcut connection + if stride == 1 and in_channels == out_channels: + # Use forward_v1 for identity shortcut + self.shortcut = nn.Identity() + self.forward = self.forward_v1 if not add_se_module else self.forward_v1_se + else: + # Use forward_v2 for projection shortcut + self.shortcut = ConvModule( + in_channels, + out_channels, + kernel_size=1, + stride=stride, + norm_cfg=norm_cfg, + act_cfg=None) + self.forward = self.forward_v2 if not add_se_module else self.forward_v2_se + + def forward_v1(self, x): + out = self.pw_conv1(x) + out = self.dw_conv(out) + out = self.pw_conv2(out) + out = out + self.shortcut(x) + return out + + def forward_v2(self, x): + out = self.pw_conv1(x) + out = self.dw_conv(out) + out = self.pw_conv2(out) + out = out + self.shortcut(x) + return out + + def forward_v1_se(self, x): + out = self.pw_conv1(x) + out = self.se(out) + out = self.dw_conv(out) + out = self.pw_conv2(out) + out = out + self.shortcut(x) + return out + + def forward_v2_se(self, x): + out = self.pw_conv1(x) + out = self.se(out) + out = self.dw_conv(out) + out = self.pw_conv2(out) + out = out + self.shortcut(x) + return out + +class FuseModule(BaseModule): + """Fuse features from different levels.""" + def __init__(self, in_channels_list, out_channels, norm_cfg=dict(type='BN'), act_cfg=dict(type='ReLU'), init_cfg=None): + super().__init__(init_cfg) + self.conv = ConvModule(sum(in_channels_list), out_channels, 3, padding=1, norm_cfg=norm_cfg, act_cfg=act_cfg) + + def forward(self, features): + target_size = features[-1].shape[2:] + + upsampled_features = []# <-- FIX + for feat in features: + upsampled_features.append( + nn.functional.interpolate(feat, size=target_size, mode='bilinear', align_corners=False) + ) + + fused_feature = torch.cat(upsampled_features, dim=1) + return self.conv(fused_feature) + +class SimBGABlock(BaseModule): + """Simplified Bilateral Guided Aggregation Block.""" + def __init__(self, detail_channels, semantic_channels, out_channels, norm_cfg=dict(type='BN'), act_cfg=dict(type='ReLU'), init_cfg=None): + super().__init__(init_cfg) + self.conv_detail = ConvModule(detail_channels, out_channels, 3, padding=1, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.conv_semantic = ConvModule(semantic_channels, out_channels, 3, padding=1, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.conv_out = ConvModule(out_channels, out_channels, 3, padding=1, norm_cfg=norm_cfg, act_cfg=act_cfg) + + def forward(self, detail_feat, semantic_feat): + semantic_feat_upsampled = nn.functional.interpolate( + semantic_feat, size=detail_feat.shape[2:], mode='bilinear', align_corners=False + ) + + detail_feat_processed = self.conv_detail(detail_feat) + semantic_feat_processed = self.conv_semantic(semantic_feat_upsampled) + + fused_feat = detail_feat_processed + semantic_feat_processed + return self.conv_out(fused_feat) + + +# --- Main EnBiSeNetV2 Backbone --- + +@MODELS.register_module() +class EnBiSeNetV2(BiSeNetV2): + """ + This class is the implementation of En_bisenetv2. + """ + def __init__(self, + in_channels, + detail_channels_stages=(64, 64, 128), + semantic_channels_stages=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + align_corners=False, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super(BiSeNetV2, self).__init__(init_cfg) + self.in_channels = in_channels + self.out_indices = out_indices + self.align_corners = align_corners + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.detail_branch = self._make_detail_branch(detail_channels_stages) + self.spatial_attention = SpatialAttentionModule() + + self.semantic_branch = self._make_semantic_branch( + semantic_channels_stages, semantic_expansion_ratio) + self.channel_attention = ChannelAttentionModule(semantic_channels_stages[-1]) + + self.fuse_module = FuseModule( + in_channels_list=[semantic_channels_stages[3], semantic_channels_stages[3]], + out_channels=semantic_channels_stages[-1] + ) + + self.bga_layer = SimBGABlock( + detail_channels=detail_channels_stages[-1], + semantic_channels=semantic_channels_stages[-1], + out_channels=bga_channels + ) + + def _make_detail_branch(self, channels_stages): + layers = [] + # stage 1 + layers.append( + DepthwiseSeparableConvModule( + self.in_channels, channels_stages[0], 3, stride=2, padding=1, + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + layers.append( + DepthwiseSeparableConvModule( + channels_stages[0], channels_stages[0], 3, stride=1, padding=1, + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + # stage 2 + layers.append( + DepthwiseSeparableConvModule( + channels_stages[0], channels_stages[1], 3, stride=2, padding=1, + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + layers.append( + DepthwiseSeparableConvModule( + channels_stages[1], channels_stages[1], 3, stride=1, padding=1, + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + # stage 3 + layers.append( + DepthwiseSeparableConvModule( + channels_stages[1], channels_stages[2], 3, stride=2, padding=1, + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + layers.append( + DepthwiseSeparableConvModule( + channels_stages[2], channels_stages[2], 3, stride=1, padding=1, + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + return Sequential(*layers) + + # def _make_detail_branch(self, channels_stages): + # layers = [] # <-- FIX + # layers.append( + # DepthwiseSeparableConvModule( + # self.in_channels, channels_stages, 3, stride=2, padding=1, + # norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + # layers.append( + # DepthwiseSeparableConvModule( + # channels_stages, channels_stages, 3, stride=1, padding=1, + # norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + # layers.append( + # DepthwiseSeparableConvModule( + # channels_stages, channels_stages[3], 3, stride=2, padding=1, + # norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + # layers.append( + # DepthwiseSeparableConvModule( + # channels_stages[3], channels_stages[3], 3, stride=1, padding=1, + # norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + # layers.append( + # DepthwiseSeparableConvModule( + # channels_stages[3], channels_stages[1], 3, stride=2, padding=1, + # norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + # layers.append( + # DepthwiseSeparableConvModule( + # channels_stages[1], channels_stages[1], 3, stride=1, padding=1, + # norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + # return Sequential(*layers) + + def _make_semantic_branch(self, channels_stages, expansion_ratio): + # stage 1 + stage1 = StemBlock(self.in_channels, channels_stages[0], + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg) + # stage 2 + stage2 = self._make_ge_layer( + channels_stages[0], channels_stages[1], 2, expansion_ratio, add_se=False) + # stage 3 + stage3 = self._make_ge_layer( + channels_stages[1], channels_stages[2], 2, expansion_ratio, add_se=True) + # stage 4 + stage4 = self._make_ge_layer( + channels_stages[2], channels_stages[3], 2, expansion_ratio, add_se=True) + # stage 5: Simplified ASPP instead of CEBlock + stage5 = CEBlockSimAspp(channels_stages[3], channels_stages[3], + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg) + return nn.ModuleDict( + {'1': stage1, '2': stage2, '3': stage3, '4': stage4, '5': stage5} + ) + + # def _make_semantic_branch(self, channels_stages, expansion_ratio): + # stage1 = StemBlock(self.in_channels, channels_stages, + # norm_cfg=self.norm_cfg, act_cfg=self.act_cfg) + # stage2 = self._make_ge_layer( + # channels_stages, channels_stages[3], 2, expansion_ratio, add_se=False) + # stage3 = self._make_ge_layer( + # channels_stages[3], channels_stages[1], 2, expansion_ratio, add_se=True) + # stage4 = self._make_ge_layer( + # channels_stages[1], channels_stages[2], 2, expansion_ratio, add_se=True) + # stage5 = CEBlockSimAspp(channels_stages[2], channels_stages[2], + # norm_cfg=self.norm_cfg, act_cfg=self.act_cfg) + # return nn.ModuleDict( + # {'1': stage1, '2': stage2, '3': stage3, '4': stage4, '5': stage5} + # ) + + def _make_ge_layer(self, in_channels, out_channels, stride, expansion_ratio, add_se=False): + mid_channels = in_channels * expansion_ratio + layers = []# <-- FIX + layers.append( + GELayerWithSE(in_channels, out_channels, mid_channels, stride, add_se_module=add_se, + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + for _ in range(1, 3): + layers.append( + GELayerWithSE(out_channels, out_channels, mid_channels, 1, add_se_module=add_se, + norm_cfg=self.norm_cfg, act_cfg=self.act_cfg)) + return Sequential(*layers) + + def forward(self, x): + detail_out = self.detail_branch(x) + detail_out = self.spatial_attention(detail_out) + + semantic_out_s1 = self.semantic_branch['1'](x) + semantic_out_s2 = self.semantic_branch['2'](semantic_out_s1) + semantic_out_s3 = self.semantic_branch['3'](semantic_out_s2) + semantic_out_s4 = self.semantic_branch['4'](semantic_out_s3) + semantic_out_s5 = self.semantic_branch['5'](semantic_out_s4) + + semantic_fused = self.fuse_module([semantic_out_s4, semantic_out_s5]) + semantic_fused = self.channel_attention(semantic_fused) + + out = self.bga_layer(detail_out, semantic_fused) + + outs = [detail_out, semantic_out_s3, semantic_out_s4, semantic_fused, out] + outs = [outs[i] for i in self.out_indices] + return tuple(outs) + +# === End: Final corrected code to be appended for EnBiSeNetV2 === \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/erfnet.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/erfnet.py new file mode 100644 index 0000000..2c5ec67 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/erfnet.py @@ -0,0 +1,329 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import build_activation_layer, build_conv_layer, build_norm_layer +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..utils import resize + + +class DownsamplerBlock(BaseModule): + """Downsampler block of ERFNet. + + This module is a little different from basical ConvModule. + The features from Conv and MaxPool layers are + concatenated before BatchNorm. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + conv_cfg=None, + norm_cfg=dict(type='BN', eps=1e-3), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.conv = build_conv_layer( + self.conv_cfg, + in_channels, + out_channels - in_channels, + kernel_size=3, + stride=2, + padding=1) + self.pool = nn.MaxPool2d(kernel_size=2, stride=2) + self.bn = build_norm_layer(self.norm_cfg, out_channels)[1] + self.act = build_activation_layer(self.act_cfg) + + def forward(self, input): + conv_out = self.conv(input) + pool_out = self.pool(input) + pool_out = resize( + input=pool_out, + size=conv_out.size()[2:], + mode='bilinear', + align_corners=False) + output = torch.cat([conv_out, pool_out], 1) + output = self.bn(output) + output = self.act(output) + return output + + +class NonBottleneck1d(BaseModule): + """Non-bottleneck block of ERFNet. + + Args: + channels (int): Number of channels in Non-bottleneck block. + drop_rate (float): Probability of an element to be zeroed. + Default 0. + dilation (int): Dilation rate for last two conv layers. + Default 1. + num_conv_layer (int): Number of 3x1 and 1x3 convolution layers. + Default 2. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + channels, + drop_rate=0, + dilation=1, + num_conv_layer=2, + conv_cfg=None, + norm_cfg=dict(type='BN', eps=1e-3), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.act = build_activation_layer(self.act_cfg) + + self.convs_layers = nn.ModuleList() + for conv_layer in range(num_conv_layer): + first_conv_padding = (1, 0) if conv_layer == 0 else (dilation, 0) + first_conv_dilation = 1 if conv_layer == 0 else (dilation, 1) + second_conv_padding = (0, 1) if conv_layer == 0 else (0, dilation) + second_conv_dilation = 1 if conv_layer == 0 else (1, dilation) + + self.convs_layers.append( + build_conv_layer( + self.conv_cfg, + channels, + channels, + kernel_size=(3, 1), + stride=1, + padding=first_conv_padding, + bias=True, + dilation=first_conv_dilation)) + self.convs_layers.append(self.act) + self.convs_layers.append( + build_conv_layer( + self.conv_cfg, + channels, + channels, + kernel_size=(1, 3), + stride=1, + padding=second_conv_padding, + bias=True, + dilation=second_conv_dilation)) + self.convs_layers.append( + build_norm_layer(self.norm_cfg, channels)[1]) + if conv_layer == 0: + self.convs_layers.append(self.act) + else: + self.convs_layers.append(nn.Dropout(p=drop_rate)) + + def forward(self, input): + output = input + for conv in self.convs_layers: + output = conv(output) + output = self.act(output + input) + return output + + +class UpsamplerBlock(BaseModule): + """Upsampler block of ERFNet. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + conv_cfg=None, + norm_cfg=dict(type='BN', eps=1e-3), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.conv = nn.ConvTranspose2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + output_padding=1, + bias=True) + self.bn = build_norm_layer(self.norm_cfg, out_channels)[1] + self.act = build_activation_layer(self.act_cfg) + + def forward(self, input): + output = self.conv(input) + output = self.bn(output) + output = self.act(output) + return output + + +@MODELS.register_module() +class ERFNet(BaseModule): + """ERFNet backbone. + + This backbone is the implementation of `ERFNet: Efficient Residual + Factorized ConvNet for Real-time SemanticSegmentation + `_. + + Args: + in_channels (int): The number of channels of input + image. Default: 3. + enc_downsample_channels (Tuple[int]): Size of channel + numbers of various Downsampler block in encoder. + Default: (16, 64, 128). + enc_stage_non_bottlenecks (Tuple[int]): Number of stages of + Non-bottleneck block in encoder. + Default: (5, 8). + enc_non_bottleneck_dilations (Tuple[int]): Dilation rate of each + stage of Non-bottleneck block of encoder. + Default: (2, 4, 8, 16). + enc_non_bottleneck_channels (Tuple[int]): Size of channel + numbers of various Non-bottleneck block in encoder. + Default: (64, 128). + dec_upsample_channels (Tuple[int]): Size of channel numbers of + various Deconvolution block in decoder. + Default: (64, 16). + dec_stages_non_bottleneck (Tuple[int]): Number of stages of + Non-bottleneck block in decoder. + Default: (2, 2). + dec_non_bottleneck_channels (Tuple[int]): Size of channel + numbers of various Non-bottleneck block in decoder. + Default: (64, 16). + drop_rate (float): Probability of an element to be zeroed. + Default 0.1. + """ + + def __init__(self, + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128), + dec_upsample_channels=(64, 16), + dec_stages_non_bottleneck=(2, 2), + dec_non_bottleneck_channels=(64, 16), + dropout_ratio=0.1, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='ReLU'), + init_cfg=None): + + super().__init__(init_cfg=init_cfg) + assert len(enc_downsample_channels) \ + == len(dec_upsample_channels)+1, 'Number of downsample\ + block of encoder does not \ + match number of upsample block of decoder!' + assert len(enc_downsample_channels) \ + == len(enc_stage_non_bottlenecks)+1, 'Number of \ + downsample block of encoder does not match \ + number of Non-bottleneck block of encoder!' + assert len(enc_downsample_channels) \ + == len(enc_non_bottleneck_channels)+1, 'Number of \ + downsample block of encoder does not match \ + number of channels of Non-bottleneck block of encoder!' + assert enc_stage_non_bottlenecks[-1] \ + % len(enc_non_bottleneck_dilations) == 0, 'Number of \ + Non-bottleneck block of encoder does not match \ + number of Non-bottleneck block of encoder!' + assert len(dec_upsample_channels) \ + == len(dec_stages_non_bottleneck), 'Number of \ + upsample block of decoder does not match \ + number of Non-bottleneck block of decoder!' + assert len(dec_stages_non_bottleneck) \ + == len(dec_non_bottleneck_channels), 'Number of \ + Non-bottleneck block of decoder does not match \ + number of channels of Non-bottleneck block of decoder!' + + self.in_channels = in_channels + self.enc_downsample_channels = enc_downsample_channels + self.enc_stage_non_bottlenecks = enc_stage_non_bottlenecks + self.enc_non_bottleneck_dilations = enc_non_bottleneck_dilations + self.enc_non_bottleneck_channels = enc_non_bottleneck_channels + self.dec_upsample_channels = dec_upsample_channels + self.dec_stages_non_bottleneck = dec_stages_non_bottleneck + self.dec_non_bottleneck_channels = dec_non_bottleneck_channels + self.dropout_ratio = dropout_ratio + + self.encoder = nn.ModuleList() + self.decoder = nn.ModuleList() + + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.encoder.append( + DownsamplerBlock(self.in_channels, enc_downsample_channels[0])) + + for i in range(len(enc_downsample_channels) - 1): + self.encoder.append( + DownsamplerBlock(enc_downsample_channels[i], + enc_downsample_channels[i + 1])) + # Last part of encoder is some dilated NonBottleneck1d blocks. + if i == len(enc_downsample_channels) - 2: + iteration_times = int(enc_stage_non_bottlenecks[-1] / + len(enc_non_bottleneck_dilations)) + for j in range(iteration_times): + for k in range(len(enc_non_bottleneck_dilations)): + self.encoder.append( + NonBottleneck1d(enc_downsample_channels[-1], + self.dropout_ratio, + enc_non_bottleneck_dilations[k])) + else: + for j in range(enc_stage_non_bottlenecks[i]): + self.encoder.append( + NonBottleneck1d(enc_downsample_channels[i + 1], + self.dropout_ratio)) + + for i in range(len(dec_upsample_channels)): + if i == 0: + self.decoder.append( + UpsamplerBlock(enc_downsample_channels[-1], + dec_non_bottleneck_channels[i])) + else: + self.decoder.append( + UpsamplerBlock(dec_non_bottleneck_channels[i - 1], + dec_non_bottleneck_channels[i])) + for j in range(dec_stages_non_bottleneck[i]): + self.decoder.append( + NonBottleneck1d(dec_non_bottleneck_channels[i])) + + def forward(self, x): + for enc in self.encoder: + x = enc(x) + for dec in self.decoder: + x = dec(x) + return [x] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/fast_scnn.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/fast_scnn.py new file mode 100644 index 0000000..6ff7a31 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/fast_scnn.py @@ -0,0 +1,408 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.model import BaseModule + +from mmseg.models.decode_heads.psp_head import PPM +from mmseg.registry import MODELS +from ..utils import InvertedResidual, resize + + +class LearningToDownsample(nn.Module): + """Learning to downsample module. + + Args: + in_channels (int): Number of input channels. + dw_channels (tuple[int]): Number of output channels of the first and + the second depthwise conv (dwconv) layers. + out_channels (int): Number of output channels of the whole + 'learning to downsample' module. + conv_cfg (dict | None): Config of conv layers. Default: None + norm_cfg (dict | None): Config of norm layers. Default: + dict(type='BN') + act_cfg (dict): Config of activation layers. Default: + dict(type='ReLU') + dw_act_cfg (dict): In DepthwiseSeparableConvModule, activation config + of depthwise ConvModule. If it is 'default', it will be the same + as `act_cfg`. Default: None. + """ + + def __init__(self, + in_channels, + dw_channels, + out_channels, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + dw_act_cfg=None): + super().__init__() + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.dw_act_cfg = dw_act_cfg + dw_channels1 = dw_channels[0] + dw_channels2 = dw_channels[1] + + self.conv = ConvModule( + in_channels, + dw_channels1, + 3, + stride=2, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.dsconv1 = DepthwiseSeparableConvModule( + dw_channels1, + dw_channels2, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + dw_act_cfg=self.dw_act_cfg) + + self.dsconv2 = DepthwiseSeparableConvModule( + dw_channels2, + out_channels, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + dw_act_cfg=self.dw_act_cfg) + + def forward(self, x): + x = self.conv(x) + x = self.dsconv1(x) + x = self.dsconv2(x) + return x + + +class GlobalFeatureExtractor(nn.Module): + """Global feature extractor module. + + Args: + in_channels (int): Number of input channels of the GFE module. + Default: 64 + block_channels (tuple[int]): Tuple of ints. Each int specifies the + number of output channels of each Inverted Residual module. + Default: (64, 96, 128) + out_channels(int): Number of output channels of the GFE module. + Default: 128 + expand_ratio (int): Adjusts number of channels of the hidden layer + in InvertedResidual by this amount. + Default: 6 + num_blocks (tuple[int]): Tuple of ints. Each int specifies the + number of times each Inverted Residual module is repeated. + The repeated Inverted Residual modules are called a 'group'. + Default: (3, 3, 3) + strides (tuple[int]): Tuple of ints. Each int specifies + the downsampling factor of each 'group'. + Default: (2, 2, 1) + pool_scales (tuple[int]): Tuple of ints. Each int specifies + the parameter required in 'global average pooling' within PPM. + Default: (1, 2, 3, 6) + conv_cfg (dict | None): Config of conv layers. Default: None + norm_cfg (dict | None): Config of norm layers. Default: + dict(type='BN') + act_cfg (dict): Config of activation layers. Default: + dict(type='ReLU') + align_corners (bool): align_corners argument of F.interpolate. + Default: False + """ + + def __init__(self, + in_channels=64, + block_channels=(64, 96, 128), + out_channels=128, + expand_ratio=6, + num_blocks=(3, 3, 3), + strides=(2, 2, 1), + pool_scales=(1, 2, 3, 6), + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + align_corners=False): + super().__init__() + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + assert len(block_channels) == len(num_blocks) == 3 + self.bottleneck1 = self._make_layer(in_channels, block_channels[0], + num_blocks[0], strides[0], + expand_ratio) + self.bottleneck2 = self._make_layer(block_channels[0], + block_channels[1], num_blocks[1], + strides[1], expand_ratio) + self.bottleneck3 = self._make_layer(block_channels[1], + block_channels[2], num_blocks[2], + strides[2], expand_ratio) + self.ppm = PPM( + pool_scales, + block_channels[2], + block_channels[2] // 4, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + align_corners=align_corners) + + self.out = ConvModule( + block_channels[2] * 2, + out_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def _make_layer(self, + in_channels, + out_channels, + blocks, + stride=1, + expand_ratio=6): + layers = [ + InvertedResidual( + in_channels, + out_channels, + stride, + expand_ratio, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + ] + for i in range(1, blocks): + layers.append( + InvertedResidual( + out_channels, + out_channels, + 1, + expand_ratio, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + return nn.Sequential(*layers) + + def forward(self, x): + x = self.bottleneck1(x) + x = self.bottleneck2(x) + x = self.bottleneck3(x) + x = torch.cat([x, *self.ppm(x)], dim=1) + x = self.out(x) + return x + + +class FeatureFusionModule(nn.Module): + """Feature fusion module. + + Args: + higher_in_channels (int): Number of input channels of the + higher-resolution branch. + lower_in_channels (int): Number of input channels of the + lower-resolution branch. + out_channels (int): Number of output channels. + conv_cfg (dict | None): Config of conv layers. Default: None + norm_cfg (dict | None): Config of norm layers. Default: + dict(type='BN') + dwconv_act_cfg (dict): Config of activation layers in 3x3 conv. + Default: dict(type='ReLU'). + conv_act_cfg (dict): Config of activation layers in the two 1x1 conv. + Default: None. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + """ + + def __init__(self, + higher_in_channels, + lower_in_channels, + out_channels, + conv_cfg=None, + norm_cfg=dict(type='BN'), + dwconv_act_cfg=dict(type='ReLU'), + conv_act_cfg=None, + align_corners=False): + super().__init__() + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.dwconv_act_cfg = dwconv_act_cfg + self.conv_act_cfg = conv_act_cfg + self.align_corners = align_corners + self.dwconv = ConvModule( + lower_in_channels, + out_channels, + 3, + padding=1, + groups=out_channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.dwconv_act_cfg) + self.conv_lower_res = ConvModule( + out_channels, + out_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.conv_act_cfg) + + self.conv_higher_res = ConvModule( + higher_in_channels, + out_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.conv_act_cfg) + + self.relu = nn.ReLU(True) + + def forward(self, higher_res_feature, lower_res_feature): + lower_res_feature = resize( + lower_res_feature, + size=higher_res_feature.size()[2:], + mode='bilinear', + align_corners=self.align_corners) + lower_res_feature = self.dwconv(lower_res_feature) + lower_res_feature = self.conv_lower_res(lower_res_feature) + + higher_res_feature = self.conv_higher_res(higher_res_feature) + out = higher_res_feature + lower_res_feature + return self.relu(out) + + +@MODELS.register_module() +class FastSCNN(BaseModule): + """Fast-SCNN Backbone. + + This backbone is the implementation of `Fast-SCNN: Fast Semantic + Segmentation Network `_. + + Args: + in_channels (int): Number of input image channels. Default: 3. + downsample_dw_channels (tuple[int]): Number of output channels after + the first conv layer & the second conv layer in + Learning-To-Downsample (LTD) module. + Default: (32, 48). + global_in_channels (int): Number of input channels of + Global Feature Extractor(GFE). + Equal to number of output channels of LTD. + Default: 64. + global_block_channels (tuple[int]): Tuple of integers that describe + the output channels for each of the MobileNet-v2 bottleneck + residual blocks in GFE. + Default: (64, 96, 128). + global_block_strides (tuple[int]): Tuple of integers + that describe the strides (downsampling factors) for each of the + MobileNet-v2 bottleneck residual blocks in GFE. + Default: (2, 2, 1). + global_out_channels (int): Number of output channels of GFE. + Default: 128. + higher_in_channels (int): Number of input channels of the higher + resolution branch in FFM. + Equal to global_in_channels. + Default: 64. + lower_in_channels (int): Number of input channels of the lower + resolution branch in FFM. + Equal to global_out_channels. + Default: 128. + fusion_out_channels (int): Number of output channels of FFM. + Default: 128. + out_indices (tuple): Tuple of indices of list + [higher_res_features, lower_res_features, fusion_output]. + Often set to (0,1,2) to enable aux. heads. + Default: (0, 1, 2). + conv_cfg (dict | None): Config of conv layers. Default: None + norm_cfg (dict | None): Config of norm layers. Default: + dict(type='BN') + act_cfg (dict): Config of activation layers. Default: + dict(type='ReLU') + align_corners (bool): align_corners argument of F.interpolate. + Default: False + dw_act_cfg (dict): In DepthwiseSeparableConvModule, activation config + of depthwise ConvModule. If it is 'default', it will be the same + as `act_cfg`. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + def __init__(self, + in_channels=3, + downsample_dw_channels=(32, 48), + global_in_channels=64, + global_block_channels=(64, 96, 128), + global_block_strides=(2, 2, 1), + global_out_channels=128, + higher_in_channels=64, + lower_in_channels=128, + fusion_out_channels=128, + out_indices=(0, 1, 2), + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + align_corners=False, + dw_act_cfg=None, + init_cfg=None): + + super().__init__(init_cfg) + + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', val=1, layer=['_BatchNorm', 'GroupNorm']) + ] + + if global_in_channels != higher_in_channels: + raise AssertionError('Global Input Channels must be the same \ + with Higher Input Channels!') + elif global_out_channels != lower_in_channels: + raise AssertionError('Global Output Channels must be the same \ + with Lower Input Channels!') + + self.in_channels = in_channels + self.downsample_dw_channels1 = downsample_dw_channels[0] + self.downsample_dw_channels2 = downsample_dw_channels[1] + self.global_in_channels = global_in_channels + self.global_block_channels = global_block_channels + self.global_block_strides = global_block_strides + self.global_out_channels = global_out_channels + self.higher_in_channels = higher_in_channels + self.lower_in_channels = lower_in_channels + self.fusion_out_channels = fusion_out_channels + self.out_indices = out_indices + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.align_corners = align_corners + self.learning_to_downsample = LearningToDownsample( + in_channels, + downsample_dw_channels, + global_in_channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + dw_act_cfg=dw_act_cfg) + self.global_feature_extractor = GlobalFeatureExtractor( + global_in_channels, + global_block_channels, + global_out_channels, + strides=self.global_block_strides, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + align_corners=self.align_corners) + self.feature_fusion = FeatureFusionModule( + higher_in_channels, + lower_in_channels, + fusion_out_channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + dwconv_act_cfg=self.act_cfg, + align_corners=self.align_corners) + + def forward(self, x): + higher_res_features = self.learning_to_downsample(x) + lower_res_features = self.global_feature_extractor(higher_res_features) + fusion_output = self.feature_fusion(higher_res_features, + lower_res_features) + + outs = [higher_res_features, lower_res_features, fusion_output] + outs = [outs[i] for i in self.out_indices] + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/hrnet.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/hrnet.py new file mode 100644 index 0000000..2da755e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/hrnet.py @@ -0,0 +1,642 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.model import BaseModule, ModuleList, Sequential +from mmengine.utils.dl_utils.parrots_wrapper import _BatchNorm + +from mmseg.registry import MODELS +from ..utils import Upsample, resize +from .resnet import BasicBlock, Bottleneck + + +class HRModule(BaseModule): + """High-Resolution Module for HRNet. + + In this module, every branch has 4 BasicBlocks/Bottlenecks. Fusion/Exchange + is in this module. + """ + + def __init__(self, + num_branches, + blocks, + num_blocks, + in_channels, + num_channels, + multiscale_output=True, + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + block_init_cfg=None, + init_cfg=None): + super().__init__(init_cfg) + self.block_init_cfg = block_init_cfg + self._check_branches(num_branches, num_blocks, in_channels, + num_channels) + + self.in_channels = in_channels + self.num_branches = num_branches + + self.multiscale_output = multiscale_output + self.norm_cfg = norm_cfg + self.conv_cfg = conv_cfg + self.with_cp = with_cp + self.branches = self._make_branches(num_branches, blocks, num_blocks, + num_channels) + self.fuse_layers = self._make_fuse_layers() + self.relu = nn.ReLU(inplace=False) + + def _check_branches(self, num_branches, num_blocks, in_channels, + num_channels): + """Check branches configuration.""" + if num_branches != len(num_blocks): + error_msg = f'NUM_BRANCHES({num_branches}) <> NUM_BLOCKS(' \ + f'{len(num_blocks)})' + raise ValueError(error_msg) + + if num_branches != len(num_channels): + error_msg = f'NUM_BRANCHES({num_branches}) <> NUM_CHANNELS(' \ + f'{len(num_channels)})' + raise ValueError(error_msg) + + if num_branches != len(in_channels): + error_msg = f'NUM_BRANCHES({num_branches}) <> NUM_INCHANNELS(' \ + f'{len(in_channels)})' + raise ValueError(error_msg) + + def _make_one_branch(self, + branch_index, + block, + num_blocks, + num_channels, + stride=1): + """Build one branch.""" + downsample = None + if stride != 1 or \ + self.in_channels[branch_index] != \ + num_channels[branch_index] * block.expansion: + downsample = nn.Sequential( + build_conv_layer( + self.conv_cfg, + self.in_channels[branch_index], + num_channels[branch_index] * block.expansion, + kernel_size=1, + stride=stride, + bias=False), + build_norm_layer(self.norm_cfg, num_channels[branch_index] * + block.expansion)[1]) + + layers = [] + layers.append( + block( + self.in_channels[branch_index], + num_channels[branch_index], + stride, + downsample=downsample, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + init_cfg=self.block_init_cfg)) + self.in_channels[branch_index] = \ + num_channels[branch_index] * block.expansion + for i in range(1, num_blocks[branch_index]): + layers.append( + block( + self.in_channels[branch_index], + num_channels[branch_index], + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + init_cfg=self.block_init_cfg)) + + return Sequential(*layers) + + def _make_branches(self, num_branches, block, num_blocks, num_channels): + """Build multiple branch.""" + branches = [] + + for i in range(num_branches): + branches.append( + self._make_one_branch(i, block, num_blocks, num_channels)) + + return ModuleList(branches) + + def _make_fuse_layers(self): + """Build fuse layer.""" + if self.num_branches == 1: + return None + + num_branches = self.num_branches + in_channels = self.in_channels + fuse_layers = [] + num_out_branches = num_branches if self.multiscale_output else 1 + for i in range(num_out_branches): + fuse_layer = [] + for j in range(num_branches): + if j > i: + fuse_layer.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels[j], + in_channels[i], + kernel_size=1, + stride=1, + padding=0, + bias=False), + build_norm_layer(self.norm_cfg, in_channels[i])[1], + # we set align_corners=False for HRNet + Upsample( + scale_factor=2**(j - i), + mode='bilinear', + align_corners=False))) + elif j == i: + fuse_layer.append(None) + else: + conv_downsamples = [] + for k in range(i - j): + if k == i - j - 1: + conv_downsamples.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels[j], + in_channels[i], + kernel_size=3, + stride=2, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, + in_channels[i])[1])) + else: + conv_downsamples.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels[j], + in_channels[j], + kernel_size=3, + stride=2, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, + in_channels[j])[1], + nn.ReLU(inplace=False))) + fuse_layer.append(nn.Sequential(*conv_downsamples)) + fuse_layers.append(nn.ModuleList(fuse_layer)) + + return nn.ModuleList(fuse_layers) + + def forward(self, x): + """Forward function.""" + if self.num_branches == 1: + return [self.branches[0](x[0])] + + for i in range(self.num_branches): + x[i] = self.branches[i](x[i]) + + x_fuse = [] + for i in range(len(self.fuse_layers)): + y = 0 + for j in range(self.num_branches): + if i == j: + y += x[j] + elif j > i: + y = y + resize( + self.fuse_layers[i][j](x[j]), + size=x[i].shape[2:], + mode='bilinear', + align_corners=False) + else: + y += self.fuse_layers[i][j](x[j]) + x_fuse.append(self.relu(y)) + return x_fuse + + +@MODELS.register_module() +class HRNet(BaseModule): + """HRNet backbone. + + This backbone is the implementation of `High-Resolution Representations + for Labeling Pixels and Regions `_. + + Args: + extra (dict): Detailed configuration for each stage of HRNet. + There must be 4 stages, the configuration for each stage must have + 5 keys: + + - num_modules (int): The number of HRModule in this stage. + - num_branches (int): The number of branches in the HRModule. + - block (str): The type of convolution block. + - num_blocks (tuple): The number of blocks in each branch. + The length must be equal to num_branches. + - num_channels (tuple): The number of channels in each branch. + The length must be equal to num_branches. + in_channels (int): Number of input image channels. Normally 3. + conv_cfg (dict): Dictionary to construct and config conv layer. + Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Use `BN` by default. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + -1 means not freezing any parameters. Default: -1. + zero_init_residual (bool): Whether to use zero init for last norm layer + in resblocks to let them behave as identity. Default: False. + multiscale_output (bool): Whether to output multi-level features + produced by multiple branches. If False, only the first level + feature will be output. Default: True. + pretrained (str, optional): Model pretrained path. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + + Example: + >>> from mmseg.models import HRNet + >>> import torch + >>> extra = dict( + >>> stage1=dict( + >>> num_modules=1, + >>> num_branches=1, + >>> block='BOTTLENECK', + >>> num_blocks=(4, ), + >>> num_channels=(64, )), + >>> stage2=dict( + >>> num_modules=1, + >>> num_branches=2, + >>> block='BASIC', + >>> num_blocks=(4, 4), + >>> num_channels=(32, 64)), + >>> stage3=dict( + >>> num_modules=4, + >>> num_branches=3, + >>> block='BASIC', + >>> num_blocks=(4, 4, 4), + >>> num_channels=(32, 64, 128)), + >>> stage4=dict( + >>> num_modules=3, + >>> num_branches=4, + >>> block='BASIC', + >>> num_blocks=(4, 4, 4, 4), + >>> num_channels=(32, 64, 128, 256))) + >>> self = HRNet(extra, in_channels=1) + >>> self.eval() + >>> inputs = torch.rand(1, 1, 32, 32) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + (1, 32, 8, 8) + (1, 64, 4, 4) + (1, 128, 2, 2) + (1, 256, 1, 1) + """ + + blocks_dict = {'BASIC': BasicBlock, 'BOTTLENECK': Bottleneck} + + def __init__(self, + extra, + in_channels=3, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=False, + with_cp=False, + frozen_stages=-1, + zero_init_residual=False, + multiscale_output=True, + pretrained=None, + init_cfg=None): + super().__init__(init_cfg) + + self.pretrained = pretrained + self.zero_init_residual = zero_init_residual + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be setting at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + else: + raise TypeError('pretrained must be a str or None') + + # Assert configurations of 4 stages are in extra + assert 'stage1' in extra and 'stage2' in extra \ + and 'stage3' in extra and 'stage4' in extra + # Assert whether the length of `num_blocks` and `num_channels` are + # equal to `num_branches` + for i in range(4): + cfg = extra[f'stage{i + 1}'] + assert len(cfg['num_blocks']) == cfg['num_branches'] and \ + len(cfg['num_channels']) == cfg['num_branches'] + + self.extra = extra + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.norm_eval = norm_eval + self.with_cp = with_cp + self.frozen_stages = frozen_stages + + # stem net + self.norm1_name, norm1 = build_norm_layer(self.norm_cfg, 64, postfix=1) + self.norm2_name, norm2 = build_norm_layer(self.norm_cfg, 64, postfix=2) + + self.conv1 = build_conv_layer( + self.conv_cfg, + in_channels, + 64, + kernel_size=3, + stride=2, + padding=1, + bias=False) + + self.add_module(self.norm1_name, norm1) + self.conv2 = build_conv_layer( + self.conv_cfg, + 64, + 64, + kernel_size=3, + stride=2, + padding=1, + bias=False) + + self.add_module(self.norm2_name, norm2) + self.relu = nn.ReLU(inplace=True) + + # stage 1 + self.stage1_cfg = self.extra['stage1'] + num_channels = self.stage1_cfg['num_channels'][0] + block_type = self.stage1_cfg['block'] + num_blocks = self.stage1_cfg['num_blocks'][0] + + block = self.blocks_dict[block_type] + stage1_out_channels = num_channels * block.expansion + self.layer1 = self._make_layer(block, 64, num_channels, num_blocks) + + # stage 2 + self.stage2_cfg = self.extra['stage2'] + num_channels = self.stage2_cfg['num_channels'] + block_type = self.stage2_cfg['block'] + + block = self.blocks_dict[block_type] + num_channels = [channel * block.expansion for channel in num_channels] + self.transition1 = self._make_transition_layer([stage1_out_channels], + num_channels) + self.stage2, pre_stage_channels = self._make_stage( + self.stage2_cfg, num_channels) + + # stage 3 + self.stage3_cfg = self.extra['stage3'] + num_channels = self.stage3_cfg['num_channels'] + block_type = self.stage3_cfg['block'] + + block = self.blocks_dict[block_type] + num_channels = [channel * block.expansion for channel in num_channels] + self.transition2 = self._make_transition_layer(pre_stage_channels, + num_channels) + self.stage3, pre_stage_channels = self._make_stage( + self.stage3_cfg, num_channels) + + # stage 4 + self.stage4_cfg = self.extra['stage4'] + num_channels = self.stage4_cfg['num_channels'] + block_type = self.stage4_cfg['block'] + + block = self.blocks_dict[block_type] + num_channels = [channel * block.expansion for channel in num_channels] + self.transition3 = self._make_transition_layer(pre_stage_channels, + num_channels) + self.stage4, pre_stage_channels = self._make_stage( + self.stage4_cfg, num_channels, multiscale_output=multiscale_output) + + self._freeze_stages() + + @property + def norm1(self): + """nn.Module: the normalization layer named "norm1" """ + return getattr(self, self.norm1_name) + + @property + def norm2(self): + """nn.Module: the normalization layer named "norm2" """ + return getattr(self, self.norm2_name) + + def _make_transition_layer(self, num_channels_pre_layer, + num_channels_cur_layer): + """Make transition layer.""" + num_branches_cur = len(num_channels_cur_layer) + num_branches_pre = len(num_channels_pre_layer) + + transition_layers = [] + for i in range(num_branches_cur): + if i < num_branches_pre: + if num_channels_cur_layer[i] != num_channels_pre_layer[i]: + transition_layers.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + num_channels_pre_layer[i], + num_channels_cur_layer[i], + kernel_size=3, + stride=1, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, + num_channels_cur_layer[i])[1], + nn.ReLU(inplace=True))) + else: + transition_layers.append(None) + else: + conv_downsamples = [] + for j in range(i + 1 - num_branches_pre): + in_channels = num_channels_pre_layer[-1] + out_channels = num_channels_cur_layer[i] \ + if j == i - num_branches_pre else in_channels + conv_downsamples.append( + nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels, + out_channels, + kernel_size=3, + stride=2, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, out_channels)[1], + nn.ReLU(inplace=True))) + transition_layers.append(nn.Sequential(*conv_downsamples)) + + return nn.ModuleList(transition_layers) + + def _make_layer(self, block, inplanes, planes, blocks, stride=1): + """Make each layer.""" + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = nn.Sequential( + build_conv_layer( + self.conv_cfg, + inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False), + build_norm_layer(self.norm_cfg, planes * block.expansion)[1]) + + layers = [] + block_init_cfg = None + if self.pretrained is None and not hasattr( + self, 'init_cfg') and self.zero_init_residual: + if block is BasicBlock: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm2')) + elif block is Bottleneck: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm3')) + + layers.append( + block( + inplanes, + planes, + stride, + downsample=downsample, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + init_cfg=block_init_cfg)) + inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append( + block( + inplanes, + planes, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + init_cfg=block_init_cfg)) + + return Sequential(*layers) + + def _make_stage(self, layer_config, in_channels, multiscale_output=True): + """Make each stage.""" + num_modules = layer_config['num_modules'] + num_branches = layer_config['num_branches'] + num_blocks = layer_config['num_blocks'] + num_channels = layer_config['num_channels'] + block = self.blocks_dict[layer_config['block']] + + hr_modules = [] + block_init_cfg = None + if self.pretrained is None and not hasattr( + self, 'init_cfg') and self.zero_init_residual: + if block is BasicBlock: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm2')) + elif block is Bottleneck: + block_init_cfg = dict( + type='Constant', val=0, override=dict(name='norm3')) + + for i in range(num_modules): + # multi_scale_output is only used for the last module + if not multiscale_output and i == num_modules - 1: + reset_multiscale_output = False + else: + reset_multiscale_output = True + + hr_modules.append( + HRModule( + num_branches, + block, + num_blocks, + in_channels, + num_channels, + reset_multiscale_output, + with_cp=self.with_cp, + norm_cfg=self.norm_cfg, + conv_cfg=self.conv_cfg, + block_init_cfg=block_init_cfg)) + + return Sequential(*hr_modules), in_channels + + def _freeze_stages(self): + """Freeze stages param and norm stats.""" + if self.frozen_stages >= 0: + + self.norm1.eval() + self.norm2.eval() + for m in [self.conv1, self.norm1, self.conv2, self.norm2]: + for param in m.parameters(): + param.requires_grad = False + + for i in range(1, self.frozen_stages + 1): + if i == 1: + m = getattr(self, f'layer{i}') + t = getattr(self, f'transition{i}') + elif i == 4: + m = getattr(self, f'stage{i}') + else: + m = getattr(self, f'stage{i}') + t = getattr(self, f'transition{i}') + m.eval() + for param in m.parameters(): + param.requires_grad = False + t.eval() + for param in t.parameters(): + param.requires_grad = False + + def forward(self, x): + """Forward function.""" + + x = self.conv1(x) + x = self.norm1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.norm2(x) + x = self.relu(x) + x = self.layer1(x) + + x_list = [] + for i in range(self.stage2_cfg['num_branches']): + if self.transition1[i] is not None: + x_list.append(self.transition1[i](x)) + else: + x_list.append(x) + y_list = self.stage2(x_list) + + x_list = [] + for i in range(self.stage3_cfg['num_branches']): + if self.transition2[i] is not None: + x_list.append(self.transition2[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage3(x_list) + + x_list = [] + for i in range(self.stage4_cfg['num_branches']): + if self.transition3[i] is not None: + x_list.append(self.transition3[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage4(x_list) + + return y_list + + def train(self, mode=True): + """Convert the model into training mode will keeping the normalization + layer freezed.""" + super().train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + # trick: eval have effect on BatchNorm only + if isinstance(m, _BatchNorm): + m.eval() diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/icnet.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/icnet.py new file mode 100644 index 0000000..8ff3448 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/icnet.py @@ -0,0 +1,166 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..decode_heads.psp_head import PPM +from ..utils import resize + + +@MODELS.register_module() +class ICNet(BaseModule): + """ICNet for Real-Time Semantic Segmentation on High-Resolution Images. + + This backbone is the implementation of + `ICNet `_. + + Args: + backbone_cfg (dict): Config dict to build backbone. Usually it is + ResNet but it can also be other backbones. + in_channels (int): The number of input image channels. Default: 3. + layer_channels (Sequence[int]): The numbers of feature channels at + layer 2 and layer 4 in ResNet. It can also be other backbones. + Default: (512, 2048). + light_branch_middle_channels (int): The number of channels of the + middle layer in light branch. Default: 32. + psp_out_channels (int): The number of channels of the output of PSP + module. Default: 512. + out_channels (Sequence[int]): The numbers of output feature channels + at each branches. Default: (64, 256, 256). + pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid + Module. Default: (1, 2, 3, 6). + conv_cfg (dict): Dictionary to construct and config conv layer. + Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN'). + act_cfg (dict): Dictionary to construct and config act layer. + Default: dict(type='ReLU'). + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + backbone_cfg, + in_channels=3, + layer_channels=(512, 2048), + light_branch_middle_channels=32, + psp_out_channels=512, + out_channels=(64, 256, 256), + pool_scales=(1, 2, 3, 6), + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='ReLU'), + align_corners=False, + init_cfg=None): + if backbone_cfg is None: + raise TypeError('backbone_cfg must be passed from config file!') + if init_cfg is None: + init_cfg = [ + dict(type='Kaiming', mode='fan_out', layer='Conv2d'), + dict(type='Constant', val=1, layer='_BatchNorm'), + dict(type='Normal', mean=0.01, layer='Linear') + ] + super().__init__(init_cfg=init_cfg) + self.align_corners = align_corners + self.backbone = MODELS.build(backbone_cfg) + + # Note: Default `ceil_mode` is false in nn.MaxPool2d, set + # `ceil_mode=True` to keep information in the corner of feature map. + self.backbone.maxpool = nn.MaxPool2d( + kernel_size=3, stride=2, padding=1, ceil_mode=True) + + self.psp_modules = PPM( + pool_scales=pool_scales, + in_channels=layer_channels[1], + channels=psp_out_channels, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + align_corners=align_corners) + + self.psp_bottleneck = ConvModule( + layer_channels[1] + len(pool_scales) * psp_out_channels, + psp_out_channels, + 3, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + self.conv_sub1 = nn.Sequential( + ConvModule( + in_channels=in_channels, + out_channels=light_branch_middle_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg), + ConvModule( + in_channels=light_branch_middle_channels, + out_channels=light_branch_middle_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg), + ConvModule( + in_channels=light_branch_middle_channels, + out_channels=out_channels[0], + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg)) + + self.conv_sub2 = ConvModule( + layer_channels[0], + out_channels[1], + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg) + + self.conv_sub4 = ConvModule( + psp_out_channels, + out_channels[2], + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg) + + def forward(self, x): + output = [] + + # sub 1 + output.append(self.conv_sub1(x)) + + # sub 2 + x = resize( + x, + scale_factor=0.5, + mode='bilinear', + align_corners=self.align_corners) + x = self.backbone.stem(x) + x = self.backbone.maxpool(x) + x = self.backbone.layer1(x) + x = self.backbone.layer2(x) + output.append(self.conv_sub2(x)) + + # sub 4 + x = resize( + x, + scale_factor=0.5, + mode='bilinear', + align_corners=self.align_corners) + x = self.backbone.layer3(x) + x = self.backbone.layer4(x) + psp_outs = self.psp_modules(x) + [x] + psp_outs = torch.cat(psp_outs, dim=1) + x = self.psp_bottleneck(psp_outs) + + output.append(self.conv_sub4(x)) + + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/mae.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mae.py new file mode 100644 index 0000000..a1f243f --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mae.py @@ -0,0 +1,260 @@ +# Copyright (c) OpenMMLab. All rights reserved.import math +import math + +import torch +import torch.nn as nn +from mmengine.model import ModuleList +from mmengine.model.weight_init import (constant_init, kaiming_init, + trunc_normal_) +from mmengine.runner.checkpoint import _load_checkpoint +from torch.nn.modules.batchnorm import _BatchNorm + +from mmseg.registry import MODELS +from .beit import BEiT, BEiTAttention, BEiTTransformerEncoderLayer + + +class MAEAttention(BEiTAttention): + """Multi-head self-attention with relative position bias used in MAE. + + This module is different from ``BEiTAttention`` by initializing the + relative bias table with zeros. + """ + + def init_weights(self): + """Initialize relative position bias with zeros.""" + + # As MAE initializes relative position bias as zeros and this class + # inherited from BEiT which initializes relative position bias + # with `trunc_normal`, `init_weights` here does + # nothing and just passes directly + + pass + + +class MAETransformerEncoderLayer(BEiTTransformerEncoderLayer): + """Implements one encoder layer in Vision Transformer. + + This module is different from ``BEiTTransformerEncoderLayer`` by replacing + ``BEiTAttention`` with ``MAEAttention``. + """ + + def build_attn(self, attn_cfg): + self.attn = MAEAttention(**attn_cfg) + + +@MODELS.register_module() +class MAE(BEiT): + """VisionTransformer with support for patch. + + Args: + img_size (int | tuple): Input image size. Default: 224. + patch_size (int): The patch size. Default: 16. + in_channels (int): Number of input channels. Default: 3. + embed_dims (int): embedding dimension. Default: 768. + num_layers (int): depth of transformer. Default: 12. + num_heads (int): number of attention heads. Default: 12. + mlp_ratio (int): ratio of mlp hidden dim to embedding dim. + Default: 4. + out_indices (list | tuple | int): Output from which stages. + Default: -1. + attn_drop_rate (float): The drop out rate for attention layer. + Default 0.0 + drop_path_rate (float): stochastic depth rate. Default 0.0. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN') + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + patch_norm (bool): Whether to add a norm in PatchEmbed Block. + Default: False. + final_norm (bool): Whether to add a additional layer to normalize + final feature map. Default: False. + num_fcs (int): The number of fully-connected layers for FFNs. + Default: 2. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + pretrained (str, optional): model pretrained path. Default: None. + init_values (float): Initialize the values of Attention and FFN + with learnable scaling. Defaults to 0.1. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + img_size=224, + patch_size=16, + in_channels=3, + embed_dims=768, + num_layers=12, + num_heads=12, + mlp_ratio=4, + out_indices=-1, + attn_drop_rate=0., + drop_path_rate=0., + norm_cfg=dict(type='LN'), + act_cfg=dict(type='GELU'), + patch_norm=False, + final_norm=False, + num_fcs=2, + norm_eval=False, + pretrained=None, + init_values=0.1, + init_cfg=None): + super().__init__( + img_size=img_size, + patch_size=patch_size, + in_channels=in_channels, + embed_dims=embed_dims, + num_layers=num_layers, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + out_indices=out_indices, + qv_bias=False, + attn_drop_rate=attn_drop_rate, + drop_path_rate=drop_path_rate, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + patch_norm=patch_norm, + final_norm=final_norm, + num_fcs=num_fcs, + norm_eval=norm_eval, + pretrained=pretrained, + init_values=init_values, + init_cfg=init_cfg) + + self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dims)) + + self.num_patches = self.patch_shape[0] * self.patch_shape[1] + self.pos_embed = nn.Parameter( + torch.zeros(1, self.num_patches + 1, embed_dims)) + + def _build_layers(self): + dpr = [ + x.item() + for x in torch.linspace(0, self.drop_path_rate, self.num_layers) + ] + self.layers = ModuleList() + for i in range(self.num_layers): + self.layers.append( + MAETransformerEncoderLayer( + embed_dims=self.embed_dims, + num_heads=self.num_heads, + feedforward_channels=self.mlp_ratio * self.embed_dims, + attn_drop_rate=self.attn_drop_rate, + drop_path_rate=dpr[i], + num_fcs=self.num_fcs, + bias=True, + act_cfg=self.act_cfg, + norm_cfg=self.norm_cfg, + window_size=self.patch_shape, + init_values=self.init_values)) + + def fix_init_weight(self): + """Rescale the initialization according to layer id. + + This function is copied from https://github.com/microsoft/unilm/blob/master/beit/modeling_pretrain.py. # noqa: E501 + Copyright (c) Microsoft Corporation + Licensed under the MIT License + """ + + def rescale(param, layer_id): + param.div_(math.sqrt(2.0 * layer_id)) + + for layer_id, layer in enumerate(self.layers): + rescale(layer.attn.proj.weight.data, layer_id + 1) + rescale(layer.ffn.layers[1].weight.data, layer_id + 1) + + def init_weights(self): + + def _init_weights(m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=.02) + if isinstance(m, nn.Linear) and m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.LayerNorm): + nn.init.constant_(m.bias, 0) + nn.init.constant_(m.weight, 1.0) + + self.apply(_init_weights) + self.fix_init_weight() + + if (isinstance(self.init_cfg, dict) + and self.init_cfg.get('type') == 'Pretrained'): + checkpoint = _load_checkpoint( + self.init_cfg['checkpoint'], logger=None, map_location='cpu') + state_dict = self.resize_rel_pos_embed(checkpoint) + state_dict = self.resize_abs_pos_embed(state_dict) + self.load_state_dict(state_dict, False) + elif self.init_cfg is not None: + super().init_weights() + else: + # We only implement the 'jax_impl' initialization implemented at + # https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py#L353 # noqa: E501 + # Copyright 2019 Ross Wightman + # Licensed under the Apache License, Version 2.0 (the "License") + trunc_normal_(self.cls_token, std=.02) + for n, m in self.named_modules(): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=.02) + if m.bias is not None: + if 'ffn' in n: + nn.init.normal_(m.bias, mean=0., std=1e-6) + else: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Conv2d): + kaiming_init(m, mode='fan_in', bias=0.) + elif isinstance(m, (_BatchNorm, nn.GroupNorm, nn.LayerNorm)): + constant_init(m, val=1.0, bias=0.) + + def resize_abs_pos_embed(self, state_dict): + if 'pos_embed' in state_dict: + pos_embed_checkpoint = state_dict['pos_embed'] + embedding_size = pos_embed_checkpoint.shape[-1] + num_extra_tokens = self.pos_embed.shape[-2] - self.num_patches + # height (== width) for the checkpoint position embedding + orig_size = int( + (pos_embed_checkpoint.shape[-2] - num_extra_tokens)**0.5) + # height (== width) for the new position embedding + new_size = int(self.num_patches**0.5) + # class_token and dist_token are kept unchanged + if orig_size != new_size: + extra_tokens = pos_embed_checkpoint[:, :num_extra_tokens] + # only the position tokens are interpolated + pos_tokens = pos_embed_checkpoint[:, num_extra_tokens:] + pos_tokens = pos_tokens.reshape(-1, orig_size, orig_size, + embedding_size).permute( + 0, 3, 1, 2) + pos_tokens = torch.nn.functional.interpolate( + pos_tokens, + size=(new_size, new_size), + mode='bicubic', + align_corners=False) + pos_tokens = pos_tokens.permute(0, 2, 3, 1).flatten(1, 2) + new_pos_embed = torch.cat((extra_tokens, pos_tokens), dim=1) + state_dict['pos_embed'] = new_pos_embed + return state_dict + + def forward(self, inputs): + B = inputs.shape[0] + + x, hw_shape = self.patch_embed(inputs) + + # stole cls_tokens impl from Phil Wang, thanks + cls_tokens = self.cls_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, x), dim=1) + x = x + self.pos_embed + + outs = [] + for i, layer in enumerate(self.layers): + x = layer(x) + if i == len(self.layers) - 1: + if self.final_norm: + x = self.norm1(x) + if i in self.out_indices: + out = x[:, 1:] + B, _, C = out.shape + out = out.reshape(B, hw_shape[0], hw_shape[1], + C).permute(0, 3, 1, 2).contiguous() + outs.append(out) + + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/mit.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mit.py new file mode 100644 index 0000000..66556bd --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mit.py @@ -0,0 +1,450 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import warnings + +import torch +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import Conv2d, build_activation_layer, build_norm_layer +from mmcv.cnn.bricks.drop import build_dropout +from mmcv.cnn.bricks.transformer import MultiheadAttention +from mmengine.model import BaseModule, ModuleList, Sequential +from mmengine.model.weight_init import (constant_init, normal_init, + trunc_normal_init) + +from mmseg.registry import MODELS +from ..utils import PatchEmbed, nchw_to_nlc, nlc_to_nchw + + +class MixFFN(BaseModule): + """An implementation of MixFFN of Segformer. + + The differences between MixFFN & FFN: + 1. Use 1X1 Conv to replace Linear layer. + 2. Introduce 3X3 Conv to encode positional information. + Args: + embed_dims (int): The feature dimension. Same as + `MultiheadAttention`. Defaults: 256. + feedforward_channels (int): The hidden dimension of FFNs. + Defaults: 1024. + act_cfg (dict, optional): The activation config for FFNs. + Default: dict(type='ReLU') + ffn_drop (float, optional): Probability of an element to be + zeroed in FFN. Default 0.0. + dropout_layer (obj:`ConfigDict`): The dropout_layer used + when adding the shortcut. + init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + feedforward_channels, + act_cfg=dict(type='GELU'), + ffn_drop=0., + dropout_layer=None, + init_cfg=None): + super().__init__(init_cfg) + + self.embed_dims = embed_dims + self.feedforward_channels = feedforward_channels + self.act_cfg = act_cfg + self.activate = build_activation_layer(act_cfg) + + in_channels = embed_dims + fc1 = Conv2d( + in_channels=in_channels, + out_channels=feedforward_channels, + kernel_size=1, + stride=1, + bias=True) + # 3x3 depth wise conv to provide positional encode information + pe_conv = Conv2d( + in_channels=feedforward_channels, + out_channels=feedforward_channels, + kernel_size=3, + stride=1, + padding=(3 - 1) // 2, + bias=True, + groups=feedforward_channels) + fc2 = Conv2d( + in_channels=feedforward_channels, + out_channels=in_channels, + kernel_size=1, + stride=1, + bias=True) + drop = nn.Dropout(ffn_drop) + layers = [fc1, pe_conv, self.activate, drop, fc2, drop] + self.layers = Sequential(*layers) + self.dropout_layer = build_dropout( + dropout_layer) if dropout_layer else torch.nn.Identity() + + def forward(self, x, hw_shape, identity=None): + out = nlc_to_nchw(x, hw_shape) + out = self.layers(out) + out = nchw_to_nlc(out) + if identity is None: + identity = x + return identity + self.dropout_layer(out) + + +class EfficientMultiheadAttention(MultiheadAttention): + """An implementation of Efficient Multi-head Attention of Segformer. + + This module is modified from MultiheadAttention which is a module from + mmcv.cnn.bricks.transformer. + Args: + embed_dims (int): The embedding dimension. + num_heads (int): Parallel attention heads. + attn_drop (float): A Dropout layer on attn_output_weights. + Default: 0.0. + proj_drop (float): A Dropout layer after `nn.MultiheadAttention`. + Default: 0.0. + dropout_layer (obj:`ConfigDict`): The dropout_layer used + when adding the shortcut. Default: None. + init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization. + Default: None. + batch_first (bool): Key, Query and Value are shape of + (batch, n, embed_dim) + or (n, batch, embed_dim). Default: False. + qkv_bias (bool): enable bias for qkv if True. Default True. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + sr_ratio (int): The ratio of spatial reduction of Efficient Multi-head + Attention of Segformer. Default: 1. + """ + + def __init__(self, + embed_dims, + num_heads, + attn_drop=0., + proj_drop=0., + dropout_layer=None, + init_cfg=None, + batch_first=True, + qkv_bias=False, + norm_cfg=dict(type='LN'), + sr_ratio=1): + super().__init__( + embed_dims, + num_heads, + attn_drop, + proj_drop, + dropout_layer=dropout_layer, + init_cfg=init_cfg, + batch_first=batch_first, + bias=qkv_bias) + + self.sr_ratio = sr_ratio + if sr_ratio > 1: + self.sr = Conv2d( + in_channels=embed_dims, + out_channels=embed_dims, + kernel_size=sr_ratio, + stride=sr_ratio) + # The ret[0] of build_norm_layer is norm name. + self.norm = build_norm_layer(norm_cfg, embed_dims)[1] + + # handle the BC-breaking from https://github.com/open-mmlab/mmcv/pull/1418 # noqa + from mmseg import digit_version, mmcv_version + if mmcv_version < digit_version('1.3.17'): + warnings.warn('The legacy version of forward function in' + 'EfficientMultiheadAttention is deprecated in' + 'mmcv>=1.3.17 and will no longer support in the' + 'future. Please upgrade your mmcv.') + self.forward = self.legacy_forward + + def forward(self, x, hw_shape, identity=None): + + x_q = x + if self.sr_ratio > 1: + x_kv = nlc_to_nchw(x, hw_shape) + x_kv = self.sr(x_kv) + x_kv = nchw_to_nlc(x_kv) + x_kv = self.norm(x_kv) + else: + x_kv = x + + if identity is None: + identity = x_q + + # Because the dataflow('key', 'query', 'value') of + # ``torch.nn.MultiheadAttention`` is (num_query, batch, + # embed_dims), We should adjust the shape of dataflow from + # batch_first (batch, num_query, embed_dims) to num_query_first + # (num_query ,batch, embed_dims), and recover ``attn_output`` + # from num_query_first to batch_first. + if self.batch_first: + x_q = x_q.transpose(0, 1) + x_kv = x_kv.transpose(0, 1) + + out = self.attn(query=x_q, key=x_kv, value=x_kv)[0] + + if self.batch_first: + out = out.transpose(0, 1) + + return identity + self.dropout_layer(self.proj_drop(out)) + + def legacy_forward(self, x, hw_shape, identity=None): + """multi head attention forward in mmcv version < 1.3.17.""" + + x_q = x + if self.sr_ratio > 1: + x_kv = nlc_to_nchw(x, hw_shape) + x_kv = self.sr(x_kv) + x_kv = nchw_to_nlc(x_kv) + x_kv = self.norm(x_kv) + else: + x_kv = x + + if identity is None: + identity = x_q + + # `need_weights=True` will let nn.MultiHeadAttention + # `return attn_output, attn_output_weights.sum(dim=1) / num_heads` + # The `attn_output_weights.sum(dim=1)` may cause cuda error. So, we set + # `need_weights=False` to ignore `attn_output_weights.sum(dim=1)`. + # This issue - `https://github.com/pytorch/pytorch/issues/37583` report + # the error that large scale tensor sum operation may cause cuda error. + out = self.attn(query=x_q, key=x_kv, value=x_kv, need_weights=False)[0] + + return identity + self.dropout_layer(self.proj_drop(out)) + + +class TransformerEncoderLayer(BaseModule): + """Implements one encoder layer in Segformer. + + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + drop_rate (float): Probability of an element to be zeroed. + after the feed forward layer. Default 0.0. + attn_drop_rate (float): The drop out rate for attention layer. + Default 0.0. + drop_path_rate (float): stochastic depth rate. Default 0.0. + qkv_bias (bool): enable bias for qkv if True. + Default: True. + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + batch_first (bool): Key, Query and Value are shape of + (batch, n, embed_dim) + or (n, batch, embed_dim). Default: False. + init_cfg (dict, optional): Initialization config dict. + Default:None. + sr_ratio (int): The ratio of spatial reduction of Efficient Multi-head + Attention of Segformer. Default: 1. + with_cp (bool): Use checkpoint or not. Using checkpoint will save + some memory while slowing down the training speed. Default: False. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + qkv_bias=True, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + batch_first=True, + sr_ratio=1, + with_cp=False): + super().__init__() + + # The ret[0] of build_norm_layer is norm name. + self.norm1 = build_norm_layer(norm_cfg, embed_dims)[1] + + self.attn = EfficientMultiheadAttention( + embed_dims=embed_dims, + num_heads=num_heads, + attn_drop=attn_drop_rate, + proj_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + batch_first=batch_first, + qkv_bias=qkv_bias, + norm_cfg=norm_cfg, + sr_ratio=sr_ratio) + + # The ret[0] of build_norm_layer is norm name. + self.norm2 = build_norm_layer(norm_cfg, embed_dims)[1] + + self.ffn = MixFFN( + embed_dims=embed_dims, + feedforward_channels=feedforward_channels, + ffn_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + act_cfg=act_cfg) + + self.with_cp = with_cp + + def forward(self, x, hw_shape): + + def _inner_forward(x): + x = self.attn(self.norm1(x), hw_shape, identity=x) + x = self.ffn(self.norm2(x), hw_shape, identity=x) + return x + + if self.with_cp and x.requires_grad: + x = cp.checkpoint(_inner_forward, x) + else: + x = _inner_forward(x) + return x + + +@MODELS.register_module() +class MixVisionTransformer(BaseModule): + """The backbone of Segformer. + + This backbone is the implementation of `SegFormer: Simple and + Efficient Design for Semantic Segmentation with + Transformers `_. + Args: + in_channels (int): Number of input channels. Default: 3. + embed_dims (int): Embedding dimension. Default: 768. + num_stags (int): The num of stages. Default: 4. + num_layers (Sequence[int]): The layer number of each transformer encode + layer. Default: [3, 4, 6, 3]. + num_heads (Sequence[int]): The attention heads of each transformer + encode layer. Default: [1, 2, 4, 8]. + patch_sizes (Sequence[int]): The patch_size of each overlapped patch + embedding. Default: [7, 3, 3, 3]. + strides (Sequence[int]): The stride of each overlapped patch embedding. + Default: [4, 2, 2, 2]. + sr_ratios (Sequence[int]): The spatial reduction rate of each + transformer encode layer. Default: [8, 4, 2, 1]. + out_indices (Sequence[int] | int): Output from which stages. + Default: (0, 1, 2, 3). + mlp_ratio (int): ratio of mlp hidden dim to embedding dim. + Default: 4. + qkv_bias (bool): Enable bias for qkv if True. Default: True. + drop_rate (float): Probability of an element to be zeroed. + Default 0.0 + attn_drop_rate (float): The drop out rate for attention layer. + Default 0.0 + drop_path_rate (float): stochastic depth rate. Default 0.0 + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN') + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + pretrained (str, optional): model pretrained path. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + with_cp (bool): Use checkpoint or not. Using checkpoint will save + some memory while slowing down the training speed. Default: False. + """ + + def __init__(self, + in_channels=3, + embed_dims=64, + num_stages=4, + num_layers=[3, 4, 6, 3], + num_heads=[1, 2, 4, 8], + patch_sizes=[7, 3, 3, 3], + strides=[4, 2, 2, 2], + sr_ratios=[8, 4, 2, 1], + out_indices=(0, 1, 2, 3), + mlp_ratio=4, + qkv_bias=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN', eps=1e-6), + pretrained=None, + init_cfg=None, + with_cp=False): + super().__init__(init_cfg=init_cfg) + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be set at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is not None: + raise TypeError('pretrained must be a str or None') + + self.embed_dims = embed_dims + self.num_stages = num_stages + self.num_layers = num_layers + self.num_heads = num_heads + self.patch_sizes = patch_sizes + self.strides = strides + self.sr_ratios = sr_ratios + self.with_cp = with_cp + assert num_stages == len(num_layers) == len(num_heads) \ + == len(patch_sizes) == len(strides) == len(sr_ratios) + + self.out_indices = out_indices + assert max(out_indices) < self.num_stages + + # transformer encoder + dpr = [ + x.item() + for x in torch.linspace(0, drop_path_rate, sum(num_layers)) + ] # stochastic num_layer decay rule + + cur = 0 + self.layers = ModuleList() + for i, num_layer in enumerate(num_layers): + embed_dims_i = embed_dims * num_heads[i] + patch_embed = PatchEmbed( + in_channels=in_channels, + embed_dims=embed_dims_i, + kernel_size=patch_sizes[i], + stride=strides[i], + padding=patch_sizes[i] // 2, + norm_cfg=norm_cfg) + layer = ModuleList([ + TransformerEncoderLayer( + embed_dims=embed_dims_i, + num_heads=num_heads[i], + feedforward_channels=mlp_ratio * embed_dims_i, + drop_rate=drop_rate, + attn_drop_rate=attn_drop_rate, + drop_path_rate=dpr[cur + idx], + qkv_bias=qkv_bias, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + with_cp=with_cp, + sr_ratio=sr_ratios[i]) for idx in range(num_layer) + ]) + in_channels = embed_dims_i + # The ret[0] of build_norm_layer is norm name. + norm = build_norm_layer(norm_cfg, embed_dims_i)[1] + self.layers.append(ModuleList([patch_embed, layer, norm])) + cur += num_layer + + def init_weights(self): + if self.init_cfg is None: + for m in self.modules(): + if isinstance(m, nn.Linear): + trunc_normal_init(m, std=.02, bias=0.) + elif isinstance(m, nn.LayerNorm): + constant_init(m, val=1.0, bias=0.) + elif isinstance(m, nn.Conv2d): + fan_out = m.kernel_size[0] * m.kernel_size[ + 1] * m.out_channels + fan_out //= m.groups + normal_init( + m, mean=0, std=math.sqrt(2.0 / fan_out), bias=0) + else: + super().init_weights() + + def forward(self, x): + outs = [] + + for i, layer in enumerate(self.layers): + x, hw_shape = layer[0](x) + for block in layer[1]: + x = block(x, hw_shape) + x = layer[2](x) + x = nlc_to_nchw(x, hw_shape) + if i in self.out_indices: + outs.append(x) + + return outs diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/mobilenet_v2.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mobilenet_v2.py new file mode 100644 index 0000000..1c21b5d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mobilenet_v2.py @@ -0,0 +1,197 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch.nn.modules.batchnorm import _BatchNorm + +from mmseg.registry import MODELS +from ..utils import InvertedResidual, make_divisible + + +@MODELS.register_module() +class MobileNetV2(BaseModule): + """MobileNetV2 backbone. + + This backbone is the implementation of + `MobileNetV2: Inverted Residuals and Linear Bottlenecks + `_. + + Args: + widen_factor (float): Width multiplier, multiply number of + channels in each layer by this amount. Default: 1.0. + strides (Sequence[int], optional): Strides of the first block of each + layer. If not specified, default config in ``arch_setting`` will + be used. + dilations (Sequence[int]): Dilation of each layer. + out_indices (None or Sequence[int]): Output from which stages. + Default: (7, ). + frozen_stages (int): Stages to be frozen (all param fixed). + Default: -1, which means not freezing any parameters. + conv_cfg (dict): Config dict for convolution layer. + Default: None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU6'). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + + # Parameters to build layers. 3 parameters are needed to construct a + # layer, from left to right: expand_ratio, channel, num_blocks. + arch_settings = [[1, 16, 1], [6, 24, 2], [6, 32, 3], [6, 64, 4], + [6, 96, 3], [6, 160, 3], [6, 320, 1]] + + def __init__(self, + widen_factor=1., + strides=(1, 2, 2, 2, 1, 2, 1), + dilations=(1, 1, 1, 1, 1, 1, 1), + out_indices=(1, 2, 4, 6), + frozen_stages=-1, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU6'), + norm_eval=False, + with_cp=False, + pretrained=None, + init_cfg=None): + super().__init__(init_cfg) + + self.pretrained = pretrained + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be setting at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is a deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + else: + raise TypeError('pretrained must be a str or None') + + self.widen_factor = widen_factor + self.strides = strides + self.dilations = dilations + assert len(strides) == len(dilations) == len(self.arch_settings) + self.out_indices = out_indices + for index in out_indices: + if index not in range(0, 7): + raise ValueError('the item in out_indices must in ' + f'range(0, 7). But received {index}') + + if frozen_stages not in range(-1, 7): + raise ValueError('frozen_stages must be in range(-1, 7). ' + f'But received {frozen_stages}') + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.norm_eval = norm_eval + self.with_cp = with_cp + + self.in_channels = make_divisible(32 * widen_factor, 8) + + self.conv1 = ConvModule( + in_channels=3, + out_channels=self.in_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.layers = [] + + for i, layer_cfg in enumerate(self.arch_settings): + expand_ratio, channel, num_blocks = layer_cfg + stride = self.strides[i] + dilation = self.dilations[i] + out_channels = make_divisible(channel * widen_factor, 8) + inverted_res_layer = self.make_layer( + out_channels=out_channels, + num_blocks=num_blocks, + stride=stride, + dilation=dilation, + expand_ratio=expand_ratio) + layer_name = f'layer{i + 1}' + self.add_module(layer_name, inverted_res_layer) + self.layers.append(layer_name) + + def make_layer(self, out_channels, num_blocks, stride, dilation, + expand_ratio): + """Stack InvertedResidual blocks to build a layer for MobileNetV2. + + Args: + out_channels (int): out_channels of block. + num_blocks (int): Number of blocks. + stride (int): Stride of the first block. + dilation (int): Dilation of the first block. + expand_ratio (int): Expand the number of channels of the + hidden layer in InvertedResidual by this ratio. + """ + layers = [] + for i in range(num_blocks): + layers.append( + InvertedResidual( + self.in_channels, + out_channels, + stride if i == 0 else 1, + expand_ratio=expand_ratio, + dilation=dilation if i == 0 else 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + with_cp=self.with_cp)) + self.in_channels = out_channels + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + + outs = [] + for i, layer_name in enumerate(self.layers): + layer = getattr(self, layer_name) + x = layer(x) + if i in self.out_indices: + outs.append(x) + + if len(outs) == 1: + return outs[0] + else: + return tuple(outs) + + def _freeze_stages(self): + if self.frozen_stages >= 0: + for param in self.conv1.parameters(): + param.requires_grad = False + for i in range(1, self.frozen_stages + 1): + layer = getattr(self, f'layer{i}') + layer.eval() + for param in layer.parameters(): + param.requires_grad = False + + def train(self, mode=True): + super().train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, _BatchNorm): + m.eval() diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/mobilenet_v3.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mobilenet_v3.py new file mode 100644 index 0000000..1efb6e0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mobilenet_v3.py @@ -0,0 +1,267 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +from mmcv.cnn import ConvModule +from mmcv.cnn.bricks import Conv2dAdaptivePadding +from mmengine.model import BaseModule +from mmengine.utils import is_tuple_of +from torch.nn.modules.batchnorm import _BatchNorm + +from mmseg.registry import MODELS +from ..utils import InvertedResidualV3 as InvertedResidual + + +@MODELS.register_module() +class MobileNetV3(BaseModule): + """MobileNetV3 backbone. + + This backbone is the improved implementation of `Searching for MobileNetV3 + `_. + + Args: + arch (str): Architecture of mobilnetv3, from {'small', 'large'}. + Default: 'small'. + conv_cfg (dict): Config dict for convolution layer. + Default: None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + out_indices (tuple[int]): Output from which layer. + Default: (0, 1, 12). + frozen_stages (int): Stages to be frozen (all param fixed). + Default: -1, which means not freezing any parameters. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + with_cp (bool): Use checkpoint or not. Using checkpoint will save + some memory while slowing down the training speed. + Default: False. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + """ + # Parameters to build each block: + # [kernel size, mid channels, out channels, with_se, act type, stride] + arch_settings = { + 'small': [[3, 16, 16, True, 'ReLU', 2], # block0 layer1 os=4 + [3, 72, 24, False, 'ReLU', 2], # block1 layer2 os=8 + [3, 88, 24, False, 'ReLU', 1], + [5, 96, 40, True, 'HSwish', 2], # block2 layer4 os=16 + [5, 240, 40, True, 'HSwish', 1], + [5, 240, 40, True, 'HSwish', 1], + [5, 120, 48, True, 'HSwish', 1], # block3 layer7 os=16 + [5, 144, 48, True, 'HSwish', 1], + [5, 288, 96, True, 'HSwish', 2], # block4 layer9 os=32 + [5, 576, 96, True, 'HSwish', 1], + [5, 576, 96, True, 'HSwish', 1]], + 'large': [[3, 16, 16, False, 'ReLU', 1], # block0 layer1 os=2 + [3, 64, 24, False, 'ReLU', 2], # block1 layer2 os=4 + [3, 72, 24, False, 'ReLU', 1], + [5, 72, 40, True, 'ReLU', 2], # block2 layer4 os=8 + [5, 120, 40, True, 'ReLU', 1], + [5, 120, 40, True, 'ReLU', 1], + [3, 240, 80, False, 'HSwish', 2], # block3 layer7 os=16 + [3, 200, 80, False, 'HSwish', 1], + [3, 184, 80, False, 'HSwish', 1], + [3, 184, 80, False, 'HSwish', 1], + [3, 480, 112, True, 'HSwish', 1], # block4 layer11 os=16 + [3, 672, 112, True, 'HSwish', 1], + [5, 672, 160, True, 'HSwish', 2], # block5 layer13 os=32 + [5, 960, 160, True, 'HSwish', 1], + [5, 960, 160, True, 'HSwish', 1]] + } # yapf: disable + + def __init__(self, + arch='small', + conv_cfg=None, + norm_cfg=dict(type='BN'), + out_indices=(0, 1, 12), + frozen_stages=-1, + reduction_factor=1, + norm_eval=False, + with_cp=False, + pretrained=None, + init_cfg=None): + super().__init__(init_cfg) + + self.pretrained = pretrained + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be setting at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is a deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + else: + raise TypeError('pretrained must be a str or None') + + assert arch in self.arch_settings + assert isinstance(reduction_factor, int) and reduction_factor > 0 + assert is_tuple_of(out_indices, int) + for index in out_indices: + if index not in range(0, len(self.arch_settings[arch]) + 2): + raise ValueError( + 'the item in out_indices must in ' + f'range(0, {len(self.arch_settings[arch])+2}). ' + f'But received {index}') + + if frozen_stages not in range(-1, len(self.arch_settings[arch]) + 2): + raise ValueError('frozen_stages must be in range(-1, ' + f'{len(self.arch_settings[arch])+2}). ' + f'But received {frozen_stages}') + self.arch = arch + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.out_indices = out_indices + self.frozen_stages = frozen_stages + self.reduction_factor = reduction_factor + self.norm_eval = norm_eval + self.with_cp = with_cp + self.layers = self._make_layer() + + def _make_layer(self): + layers = [] + + # build the first layer (layer0) + in_channels = 16 + layer = ConvModule( + in_channels=3, + out_channels=in_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=dict(type='Conv2dAdaptivePadding'), + norm_cfg=self.norm_cfg, + act_cfg=dict(type='HSwish')) + self.add_module('layer0', layer) + layers.append('layer0') + + layer_setting = self.arch_settings[self.arch] + for i, params in enumerate(layer_setting): + (kernel_size, mid_channels, out_channels, with_se, act, + stride) = params + + if self.arch == 'large' and i >= 12 or self.arch == 'small' and \ + i >= 8: + mid_channels = mid_channels // self.reduction_factor + out_channels = out_channels // self.reduction_factor + + if with_se: + se_cfg = dict( + channels=mid_channels, + ratio=4, + act_cfg=(dict(type='ReLU'), + dict(type='HSigmoid', bias=3.0, divisor=6.0))) + else: + se_cfg = None + + layer = InvertedResidual( + in_channels=in_channels, + out_channels=out_channels, + mid_channels=mid_channels, + kernel_size=kernel_size, + stride=stride, + se_cfg=se_cfg, + with_expand_conv=(in_channels != mid_channels), + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=dict(type=act), + with_cp=self.with_cp) + in_channels = out_channels + layer_name = f'layer{i + 1}' + self.add_module(layer_name, layer) + layers.append(layer_name) + + # build the last layer + # block5 layer12 os=32 for small model + # block6 layer16 os=32 for large model + layer = ConvModule( + in_channels=in_channels, + out_channels=576 if self.arch == 'small' else 960, + kernel_size=1, + stride=1, + dilation=4, + padding=0, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=dict(type='HSwish')) + layer_name = f'layer{len(layer_setting) + 1}' + self.add_module(layer_name, layer) + layers.append(layer_name) + + # next, convert backbone MobileNetV3 to a semantic segmentation version + if self.arch == 'small': + self.layer4.depthwise_conv.conv.stride = (1, 1) + self.layer9.depthwise_conv.conv.stride = (1, 1) + for i in range(4, len(layers)): + layer = getattr(self, layers[i]) + if isinstance(layer, InvertedResidual): + modified_module = layer.depthwise_conv.conv + else: + modified_module = layer.conv + + if i < 9: + modified_module.dilation = (2, 2) + pad = 2 + else: + modified_module.dilation = (4, 4) + pad = 4 + + if not isinstance(modified_module, Conv2dAdaptivePadding): + # Adjust padding + pad *= (modified_module.kernel_size[0] - 1) // 2 + modified_module.padding = (pad, pad) + else: + self.layer7.depthwise_conv.conv.stride = (1, 1) + self.layer13.depthwise_conv.conv.stride = (1, 1) + for i in range(7, len(layers)): + layer = getattr(self, layers[i]) + if isinstance(layer, InvertedResidual): + modified_module = layer.depthwise_conv.conv + else: + modified_module = layer.conv + + if i < 13: + modified_module.dilation = (2, 2) + pad = 2 + else: + modified_module.dilation = (4, 4) + pad = 4 + + if not isinstance(modified_module, Conv2dAdaptivePadding): + # Adjust padding + pad *= (modified_module.kernel_size[0] - 1) // 2 + modified_module.padding = (pad, pad) + + return layers + + def forward(self, x): + outs = [] + for i, layer_name in enumerate(self.layers): + layer = getattr(self, layer_name) + x = layer(x) + if i in self.out_indices: + outs.append(x) + return outs + + def _freeze_stages(self): + for i in range(self.frozen_stages + 1): + layer = getattr(self, f'layer{i}') + layer.eval() + for param in layer.parameters(): + param.requires_grad = False + + def train(self, mode=True): + super().train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, _BatchNorm): + m.eval() diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/mscan.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mscan.py new file mode 100644 index 0000000..7150cb7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/mscan.py @@ -0,0 +1,467 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Originally from https://github.com/visual-attention-network/segnext +# Licensed under the Apache License, Version 2.0 (the "License") +import math +import warnings + +import torch +import torch.nn as nn +from mmcv.cnn import build_activation_layer, build_norm_layer +from mmcv.cnn.bricks import DropPath +from mmengine.model import BaseModule +from mmengine.model.weight_init import (constant_init, normal_init, + trunc_normal_init) + +from mmseg.registry import MODELS + + +class Mlp(BaseModule): + """Multi Layer Perceptron (MLP) Module. + + Args: + in_features (int): The dimension of input features. + hidden_features (int): The dimension of hidden features. + Defaults: None. + out_features (int): The dimension of output features. + Defaults: None. + act_cfg (dict): Config dict for activation layer in block. + Default: dict(type='GELU'). + drop (float): The number of dropout rate in MLP block. + Defaults: 0.0. + """ + + def __init__(self, + in_features, + hidden_features=None, + out_features=None, + act_cfg=dict(type='GELU'), + drop=0.): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = nn.Conv2d(in_features, hidden_features, 1) + self.dwconv = nn.Conv2d( + hidden_features, + hidden_features, + 3, + 1, + 1, + bias=True, + groups=hidden_features) + self.act = build_activation_layer(act_cfg) + self.fc2 = nn.Conv2d(hidden_features, out_features, 1) + self.drop = nn.Dropout(drop) + + def forward(self, x): + """Forward function.""" + + x = self.fc1(x) + + x = self.dwconv(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + + return x + + +class StemConv(BaseModule): + """Stem Block at the beginning of Semantic Branch. + + Args: + in_channels (int): The dimension of input channels. + out_channels (int): The dimension of output channels. + act_cfg (dict): Config dict for activation layer in block. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Defaults: dict(type='SyncBN', requires_grad=True). + """ + + def __init__(self, + in_channels, + out_channels, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='SyncBN', requires_grad=True)): + super().__init__() + + self.proj = nn.Sequential( + nn.Conv2d( + in_channels, + out_channels // 2, + kernel_size=(3, 3), + stride=(2, 2), + padding=(1, 1)), + build_norm_layer(norm_cfg, out_channels // 2)[1], + build_activation_layer(act_cfg), + nn.Conv2d( + out_channels // 2, + out_channels, + kernel_size=(3, 3), + stride=(2, 2), + padding=(1, 1)), + build_norm_layer(norm_cfg, out_channels)[1], + ) + + def forward(self, x): + """Forward function.""" + + x = self.proj(x) + _, _, H, W = x.size() + x = x.flatten(2).transpose(1, 2) + return x, H, W + + +class MSCAAttention(BaseModule): + """Attention Module in Multi-Scale Convolutional Attention Module (MSCA). + + Args: + channels (int): The dimension of channels. + kernel_sizes (list): The size of attention + kernel. Defaults: [5, [1, 7], [1, 11], [1, 21]]. + paddings (list): The number of + corresponding padding value in attention module. + Defaults: [2, [0, 3], [0, 5], [0, 10]]. + """ + + def __init__(self, + channels, + kernel_sizes=[5, [1, 7], [1, 11], [1, 21]], + paddings=[2, [0, 3], [0, 5], [0, 10]]): + super().__init__() + self.conv0 = nn.Conv2d( + channels, + channels, + kernel_size=kernel_sizes[0], + padding=paddings[0], + groups=channels) + for i, (kernel_size, + padding) in enumerate(zip(kernel_sizes[1:], paddings[1:])): + kernel_size_ = [kernel_size, kernel_size[::-1]] + padding_ = [padding, padding[::-1]] + conv_name = [f'conv{i}_1', f'conv{i}_2'] + for i_kernel, i_pad, i_conv in zip(kernel_size_, padding_, + conv_name): + self.add_module( + i_conv, + nn.Conv2d( + channels, + channels, + tuple(i_kernel), + padding=i_pad, + groups=channels)) + self.conv3 = nn.Conv2d(channels, channels, 1) + + def forward(self, x): + """Forward function.""" + + u = x.clone() + + attn = self.conv0(x) + + # Multi-Scale Feature extraction + attn_0 = self.conv0_1(attn) + attn_0 = self.conv0_2(attn_0) + + attn_1 = self.conv1_1(attn) + attn_1 = self.conv1_2(attn_1) + + attn_2 = self.conv2_1(attn) + attn_2 = self.conv2_2(attn_2) + + attn = attn + attn_0 + attn_1 + attn_2 + # Channel Mixing + attn = self.conv3(attn) + + # Convolutional Attention + x = attn * u + + return x + + +class MSCASpatialAttention(BaseModule): + """Spatial Attention Module in Multi-Scale Convolutional Attention Module + (MSCA). + + Args: + in_channels (int): The dimension of channels. + attention_kernel_sizes (list): The size of attention + kernel. Defaults: [5, [1, 7], [1, 11], [1, 21]]. + attention_kernel_paddings (list): The number of + corresponding padding value in attention module. + Defaults: [2, [0, 3], [0, 5], [0, 10]]. + act_cfg (dict): Config dict for activation layer in block. + Default: dict(type='GELU'). + """ + + def __init__(self, + in_channels, + attention_kernel_sizes=[5, [1, 7], [1, 11], [1, 21]], + attention_kernel_paddings=[2, [0, 3], [0, 5], [0, 10]], + act_cfg=dict(type='GELU')): + super().__init__() + self.proj_1 = nn.Conv2d(in_channels, in_channels, 1) + self.activation = build_activation_layer(act_cfg) + self.spatial_gating_unit = MSCAAttention(in_channels, + attention_kernel_sizes, + attention_kernel_paddings) + self.proj_2 = nn.Conv2d(in_channels, in_channels, 1) + + def forward(self, x): + """Forward function.""" + + shorcut = x.clone() + x = self.proj_1(x) + x = self.activation(x) + x = self.spatial_gating_unit(x) + x = self.proj_2(x) + x = x + shorcut + return x + + +class MSCABlock(BaseModule): + """Basic Multi-Scale Convolutional Attention Block. It leverage the large- + kernel attention (LKA) mechanism to build both channel and spatial + attention. In each branch, it uses two depth-wise strip convolutions to + approximate standard depth-wise convolutions with large kernels. The kernel + size for each branch is set to 7, 11, and 21, respectively. + + Args: + channels (int): The dimension of channels. + attention_kernel_sizes (list): The size of attention + kernel. Defaults: [5, [1, 7], [1, 11], [1, 21]]. + attention_kernel_paddings (list): The number of + corresponding padding value in attention module. + Defaults: [2, [0, 3], [0, 5], [0, 10]]. + mlp_ratio (float): The ratio of multiple input dimension to + calculate hidden feature in MLP layer. Defaults: 4.0. + drop (float): The number of dropout rate in MLP block. + Defaults: 0.0. + drop_path (float): The ratio of drop paths. + Defaults: 0.0. + act_cfg (dict): Config dict for activation layer in block. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Defaults: dict(type='SyncBN', requires_grad=True). + """ + + def __init__(self, + channels, + attention_kernel_sizes=[5, [1, 7], [1, 11], [1, 21]], + attention_kernel_paddings=[2, [0, 3], [0, 5], [0, 10]], + mlp_ratio=4., + drop=0., + drop_path=0., + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='SyncBN', requires_grad=True)): + super().__init__() + self.norm1 = build_norm_layer(norm_cfg, channels)[1] + self.attn = MSCASpatialAttention(channels, attention_kernel_sizes, + attention_kernel_paddings, act_cfg) + self.drop_path = DropPath( + drop_path) if drop_path > 0. else nn.Identity() + self.norm2 = build_norm_layer(norm_cfg, channels)[1] + mlp_hidden_channels = int(channels * mlp_ratio) + self.mlp = Mlp( + in_features=channels, + hidden_features=mlp_hidden_channels, + act_cfg=act_cfg, + drop=drop) + layer_scale_init_value = 1e-2 + self.layer_scale_1 = nn.Parameter( + layer_scale_init_value * torch.ones(channels), requires_grad=True) + self.layer_scale_2 = nn.Parameter( + layer_scale_init_value * torch.ones(channels), requires_grad=True) + + def forward(self, x, H, W): + """Forward function.""" + + B, N, C = x.shape + x = x.permute(0, 2, 1).view(B, C, H, W) + x = x + self.drop_path( + self.layer_scale_1.unsqueeze(-1).unsqueeze(-1) * + self.attn(self.norm1(x))) + x = x + self.drop_path( + self.layer_scale_2.unsqueeze(-1).unsqueeze(-1) * + self.mlp(self.norm2(x))) + x = x.view(B, C, N).permute(0, 2, 1) + return x + + +class OverlapPatchEmbed(BaseModule): + """Image to Patch Embedding. + + Args: + patch_size (int): The patch size. + Defaults: 7. + stride (int): Stride of the convolutional layer. + Default: 4. + in_channels (int): The number of input channels. + Defaults: 3. + embed_dims (int): The dimensions of embedding. + Defaults: 768. + norm_cfg (dict): Config dict for normalization layer. + Defaults: dict(type='SyncBN', requires_grad=True). + """ + + def __init__(self, + patch_size=7, + stride=4, + in_channels=3, + embed_dim=768, + norm_cfg=dict(type='SyncBN', requires_grad=True)): + super().__init__() + + self.proj = nn.Conv2d( + in_channels, + embed_dim, + kernel_size=patch_size, + stride=stride, + padding=patch_size // 2) + self.norm = build_norm_layer(norm_cfg, embed_dim)[1] + + def forward(self, x): + """Forward function.""" + + x = self.proj(x) + _, _, H, W = x.shape + x = self.norm(x) + + x = x.flatten(2).transpose(1, 2) + + return x, H, W + + +@MODELS.register_module() +class MSCAN(BaseModule): + """SegNeXt Multi-Scale Convolutional Attention Network (MCSAN) backbone. + + This backbone is the implementation of `SegNeXt: Rethinking + Convolutional Attention Design for Semantic + Segmentation `_. + Inspiration from https://github.com/visual-attention-network/segnext. + + Args: + in_channels (int): The number of input channels. Defaults: 3. + embed_dims (list[int]): Embedding dimension. + Defaults: [64, 128, 256, 512]. + mlp_ratios (list[int]): Ratio of mlp hidden dim to embedding dim. + Defaults: [4, 4, 4, 4]. + drop_rate (float): Dropout rate. Defaults: 0. + drop_path_rate (float): Stochastic depth rate. Defaults: 0. + depths (list[int]): Depths of each Swin Transformer stage. + Default: [3, 4, 6, 3]. + num_stages (int): MSCAN stages. Default: 4. + attention_kernel_sizes (list): Size of attention kernel in + Attention Module (Figure 2(b) of original paper). + Defaults: [5, [1, 7], [1, 11], [1, 21]]. + attention_kernel_paddings (list): Size of attention paddings + in Attention Module (Figure 2(b) of original paper). + Defaults: [2, [0, 3], [0, 5], [0, 10]]. + norm_cfg (dict): Config of norm layers. + Defaults: dict(type='SyncBN', requires_grad=True). + pretrained (str, optional): model pretrained path. + Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels=3, + embed_dims=[64, 128, 256, 512], + mlp_ratios=[4, 4, 4, 4], + drop_rate=0., + drop_path_rate=0., + depths=[3, 4, 6, 3], + num_stages=4, + attention_kernel_sizes=[5, [1, 7], [1, 11], [1, 21]], + attention_kernel_paddings=[2, [0, 3], [0, 5], [0, 10]], + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='SyncBN', requires_grad=True), + pretrained=None, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be set at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is not None: + raise TypeError('pretrained must be a str or None') + + self.depths = depths + self.num_stages = num_stages + + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, sum(depths)) + ] # stochastic depth decay rule + cur = 0 + + for i in range(num_stages): + if i == 0: + patch_embed = StemConv(3, embed_dims[0], norm_cfg=norm_cfg) + else: + patch_embed = OverlapPatchEmbed( + patch_size=7 if i == 0 else 3, + stride=4 if i == 0 else 2, + in_channels=in_channels if i == 0 else embed_dims[i - 1], + embed_dim=embed_dims[i], + norm_cfg=norm_cfg) + + block = nn.ModuleList([ + MSCABlock( + channels=embed_dims[i], + attention_kernel_sizes=attention_kernel_sizes, + attention_kernel_paddings=attention_kernel_paddings, + mlp_ratio=mlp_ratios[i], + drop=drop_rate, + drop_path=dpr[cur + j], + act_cfg=act_cfg, + norm_cfg=norm_cfg) for j in range(depths[i]) + ]) + norm = nn.LayerNorm(embed_dims[i]) + cur += depths[i] + + setattr(self, f'patch_embed{i + 1}', patch_embed) + setattr(self, f'block{i + 1}', block) + setattr(self, f'norm{i + 1}', norm) + + def init_weights(self): + """Initialize modules of MSCAN.""" + + print('init cfg', self.init_cfg) + if self.init_cfg is None: + for m in self.modules(): + if isinstance(m, nn.Linear): + trunc_normal_init(m, std=.02, bias=0.) + elif isinstance(m, nn.LayerNorm): + constant_init(m, val=1.0, bias=0.) + elif isinstance(m, nn.Conv2d): + fan_out = m.kernel_size[0] * m.kernel_size[ + 1] * m.out_channels + fan_out //= m.groups + normal_init( + m, mean=0, std=math.sqrt(2.0 / fan_out), bias=0) + else: + super().init_weights() + + def forward(self, x): + """Forward function.""" + + B = x.shape[0] + outs = [] + + for i in range(self.num_stages): + patch_embed = getattr(self, f'patch_embed{i + 1}') + block = getattr(self, f'block{i + 1}') + norm = getattr(self, f'norm{i + 1}') + x, H, W = patch_embed(x) + for blk in block: + x = blk(x, H, W) + x = norm(x) + x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous() + outs.append(x) + + return outs diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A1.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A1.py new file mode 100644 index 0000000..785f45d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A1.py @@ -0,0 +1,629 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import (ConvModule, DepthwiseSeparableConvModule, + build_activation_layer, build_norm_layer) +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..utils import resize + + +class DetailBranch(BaseModule): + """Detail Branch with wide channels and shallow layers to capture low-level + details and generate high-resolution feature representation. + + Args: + detail_channels (Tuple[int]): Size of channel numbers of each stage + in Detail Branch, in paper it has 3 stages. + Default: (64, 64, 128). + in_channels (int): Number of channels of input image. Default: 3. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Feature map of Detail Branch. + """ + + def __init__(self, + detail_channels=(64, 64, 128), + in_channels=3, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + detail_branch = [] + for i in range(len(detail_channels)): + if i == 0: + in_ch = in_channels + else: + in_ch = detail_channels[i - 1] + out_ch = detail_channels[i] + + # 使用 DepthwiseSeparableConvModule 替换 ConvModule + if i == 0: + detail_branch.append( + nn.Sequential( + # TODO V1. 修改为深度可分离卷积 + DepthwiseSeparableConvModule( + in_channels=in_ch, + out_channels=out_ch, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + # TODO V1. 修改为深度可分离卷积 + DepthwiseSeparableConvModule( + in_channels=out_ch, + out_channels=out_ch, + kernel_size=3, + stride=1, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg))) + else: + detail_branch.append( + nn.Sequential( + # TODO V1. 修改为深度可分离卷积 + DepthwiseSeparableConvModule( + in_channels=in_ch, + out_channels=out_ch, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + # TODO V1. 修改为深度可分离卷积 + DepthwiseSeparableConvModule( + in_channels=out_ch, + out_channels=out_ch, + kernel_size=3, + stride=1, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + # TODO V1. 修改为深度可分离卷积 + DepthwiseSeparableConvModule( + in_channels=out_ch, + out_channels=out_ch, + kernel_size=3, + stride=1, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg))) + self.detail_branch = nn.ModuleList(detail_branch) + + def forward(self, x): + for stage in self.detail_branch: + x = stage(x) + return x + + +class StemBlock(BaseModule): + """Stem Block at the beginning of Semantic Branch. + + Args: + in_channels (int): Number of input channels. + Default: 3. + out_channels (int): Number of output channels. + Default: 16. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): First feature map in Semantic Branch. + """ + + def __init__(self, + in_channels=3, + out_channels=16, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.conv_first = ConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.convs = nn.Sequential( + ConvModule( + in_channels=out_channels, + out_channels=out_channels // 2, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + in_channels=out_channels // 2, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.pool = nn.MaxPool2d( + kernel_size=3, stride=2, padding=1, ceil_mode=False) + self.fuse_last = ConvModule( + in_channels=out_channels * 2, + out_channels=out_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + x = self.conv_first(x) + x_left = self.convs(x) + x_right = self.pool(x) + x = self.fuse_last(torch.cat([x_left, x_right], dim=1)) + return x + + +class GELayer(BaseModule): + """Gather-and-Expansion Layer. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + exp_ratio (int): Expansion ratio for middle channels. + Default: 6. + stride (int): Stride of GELayer. Default: 1 + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Intermediate feature map in + Semantic Branch. + """ + + def __init__(self, + in_channels, + out_channels, + exp_ratio=6, + stride=1, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + mid_channel = in_channels * exp_ratio + self.conv1 = ConvModule( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + if stride == 1: + self.dwconv = nn.Sequential( + # ReLU in ConvModule not shown in paper + ConvModule( + in_channels=in_channels, + out_channels=mid_channel, + kernel_size=3, + stride=stride, + padding=1, + groups=in_channels, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.shortcut = None + else: + self.dwconv = nn.Sequential( + ConvModule( + in_channels=in_channels, + out_channels=mid_channel, + kernel_size=3, + stride=stride, + padding=1, + groups=in_channels, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), + # ReLU in ConvModule not shown in paper + ConvModule( + in_channels=mid_channel, + out_channels=mid_channel, + kernel_size=3, + stride=1, + padding=1, + groups=mid_channel, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ) + self.shortcut = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=norm_cfg, + pw_act_cfg=None, + )) + + self.conv2 = nn.Sequential( + ConvModule( + in_channels=mid_channel, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None, + )) + + self.act = build_activation_layer(act_cfg) + + def forward(self, x): + identity = x + x = self.conv1(x) + x = self.dwconv(x) + x = self.conv2(x) + if self.shortcut is not None: + shortcut = self.shortcut(identity) + x = x + shortcut + else: + x = x + identity + x = self.act(x) + return x + + +class CEBlock(BaseModule): + """Context Embedding Block for large receptive filed in Semantic Branch. + + Args: + in_channels (int): Number of input channels. + Default: 3. + out_channels (int): Number of output channels. + Default: 16. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Last feature map in Semantic Branch. + """ + + def __init__(self, + in_channels=3, + out_channels=16, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + self.gap = nn.Sequential( + nn.AdaptiveAvgPool2d((1, 1)), + build_norm_layer(norm_cfg, self.in_channels)[1]) + self.conv_gap = ConvModule( + in_channels=self.in_channels, + out_channels=self.out_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + # Note: in paper here is naive conv2d, no bn-relu + self.conv_last = ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + identity = x + x = self.gap(x) + x = self.conv_gap(x) + x = identity + x + x = self.conv_last(x) + return x + + +class SemanticBranch(BaseModule): + """Semantic Branch which is lightweight with narrow channels and deep + layers to obtain high-level semantic context. + + Args: + semantic_channels(Tuple[int]): Size of channel numbers of + various stages in Semantic Branch. + Default: (16, 32, 64, 128). + in_channels (int): Number of channels of input image. Default: 3. + exp_ratio (int): Expansion ratio for middle channels. + Default: 6. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + semantic_outs (List[torch.Tensor]): List of several feature maps + for auxiliary heads (Booster) and Bilateral + Guided Aggregation Layer. + """ + + def __init__(self, + semantic_channels=(16, 32, 64, 128), + in_channels=3, + exp_ratio=6, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.semantic_channels = semantic_channels + self.semantic_stages = [] + for i in range(len(semantic_channels)): + stage_name = f'stage{i + 1}' + self.semantic_stages.append(stage_name) + if i == 0: + self.add_module( + stage_name, + StemBlock(self.in_channels, semantic_channels[i])) + elif i == (len(semantic_channels) - 1): + self.add_module( + stage_name, + nn.Sequential( + GELayer(semantic_channels[i - 1], semantic_channels[i], + exp_ratio, 2), + GELayer(semantic_channels[i], semantic_channels[i], + exp_ratio, 1), + GELayer(semantic_channels[i], semantic_channels[i], + exp_ratio, 1), + GELayer(semantic_channels[i], semantic_channels[i], + exp_ratio, 1))) + else: + self.add_module( + stage_name, + nn.Sequential( + GELayer(semantic_channels[i - 1], semantic_channels[i], + exp_ratio, 2), + GELayer(semantic_channels[i], semantic_channels[i], + exp_ratio, 1))) + + self.add_module(f'stage{len(semantic_channels)}_CEBlock', + CEBlock(semantic_channels[-1], semantic_channels[-1])) + self.semantic_stages.append(f'stage{len(semantic_channels)}_CEBlock') + + def forward(self, x): + semantic_outs = [] + for stage_name in self.semantic_stages: + semantic_stage = getattr(self, stage_name) + x = semantic_stage(x) + semantic_outs.append(x) + return semantic_outs + + +class BGALayer(BaseModule): + """Bilateral Guided Aggregation Layer to fuse the complementary information + from both Detail Branch and Semantic Branch. + + Args: + out_channels (int): Number of output channels. + Default: 128. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + output (torch.Tensor): Output feature map for Segment heads. + """ + + def __init__(self, + out_channels=128, + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.out_channels = out_channels + self.align_corners = align_corners + self.detail_dwconv = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=None, + pw_act_cfg=None, + )) + self.detail_down = nn.Sequential( + ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=2, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), + nn.AvgPool2d(kernel_size=3, stride=2, padding=1, ceil_mode=False)) + self.semantic_conv = nn.Sequential( + ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None)) + self.semantic_dwconv = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=None, + pw_act_cfg=None, + )) + self.conv = ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + inplace=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + + def forward(self, x_d, x_s): + detail_dwconv = self.detail_dwconv(x_d) + detail_down = self.detail_down(x_d) + semantic_conv = self.semantic_conv(x_s) + semantic_dwconv = self.semantic_dwconv(x_s) + semantic_conv = resize( + input=semantic_conv, + size=detail_dwconv.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + fuse_1 = detail_dwconv * torch.sigmoid(semantic_conv) + fuse_2 = detail_down * torch.sigmoid(semantic_dwconv) + fuse_2 = resize( + input=fuse_2, + size=fuse_1.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + output = self.conv(fuse_1 + fuse_2) + return output + + +@MODELS.register_module() +class My_BiSeNetV2_A1(BaseModule): # A1:深度可分离卷积 + """BiSeNetV2: Bilateral Network with Guided Aggregation for + Real-time Semantic Segmentation. + + This backbone is the implementation of + `BiSeNetV2 `_. + + Args: + in_channels (int): Number of channel of input image. Default: 3. + detail_channels (Tuple[int], optional): Channels of each stage + in Detail Branch. Default: (64, 64, 128). + semantic_channels (Tuple[int], optional): Channels of each stage + in Semantic Branch. Default: (16, 32, 64, 128). + See Table 1 and Figure 3 of paper for more details. + semantic_expansion_ratio (int, optional): The expansion factor + expanding channel number of middle channels in Semantic Branch. + Default: 6. + bga_channels (int, optional): Number of middle channels in + Bilateral Guided Aggregation Layer. Default: 128. + out_indices (Tuple[int] | int, optional): Output from which stages. + Default: (0, 1, 2, 3, 4). + align_corners (bool, optional): The align_corners argument of + resize operation in Bilateral Guided Aggregation Layer. + Default: False. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels=3, + detail_channels=(64, 64, 128), + semantic_channels=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + if init_cfg is None: + init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', val=1, layer=['_BatchNorm', 'GroupNorm']) + ] + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_indices = out_indices + self.detail_channels = detail_channels + self.semantic_channels = semantic_channels + self.semantic_expansion_ratio = semantic_expansion_ratio + self.bga_channels = bga_channels + self.align_corners = align_corners + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + + self.detail = DetailBranch(self.detail_channels, self.in_channels) + self.semantic = SemanticBranch(self.semantic_channels, + self.in_channels, + self.semantic_expansion_ratio) + self.bga = BGALayer(self.bga_channels, self.align_corners) + + def forward(self, x): + # stole refactoring code from Coin Cheung, thanks + x_detail = self.detail(x) + x_semantic_lst = self.semantic(x) + x_head = self.bga(x_detail, x_semantic_lst[-1]) + outs = [x_head] + x_semantic_lst[:-1] + outs = [outs[i] for i in self.out_indices] + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A1_add_A2.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A1_add_A2.py new file mode 100644 index 0000000..924596b --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A1_add_A2.py @@ -0,0 +1,591 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import (ConvModule, DepthwiseSeparableConvModule, + build_activation_layer, build_norm_layer) +from mmengine.model import BaseModule +from mmseg.registry import MODELS +from ..utils import resize +import torch.nn.functional as F +from einops import rearrange +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +# =================================================================== +# Part 1: CAFM 模块 (稍作修改以简化输出) +# =================================================================== +class CAFM(nn.Module): + def __init__(self, channels): + super(CAFM, self).__init__() + self.conv1_spatial = nn.Conv2d(2, 1, 3, stride=1, padding=1, groups=1) + self.conv2_spatial = nn.Conv2d(1, 1, 3, stride=1, padding=1, groups=1) + self.avg1 = nn.Conv2d(channels, 64, 1, stride=1, padding=0) + self.avg2 = nn.Conv2d(channels, 64, 1, stride=1, padding=0) + self.max1 = nn.Conv2d(channels, 64, 1, stride=1, padding=0) + self.max2 = nn.Conv2d(channels, 64, 1, stride=1, padding=0) + self.avg11 = nn.Conv2d(64, channels, 1, stride=1, padding=0) + self.avg22 = nn.Conv2d(64, channels, 1, stride=1, padding=0) + self.max11 = nn.Conv2d(64, channels, 1, stride=1, padding=0) + self.max22 = nn.Conv2d(64, channels, 1, stride=1, padding=0) + + def forward(self, f1, f2): + b, c, h, w = f1.size() + f1_reshaped = f1.reshape([b, c, -1]) + f2_reshaped = f2.reshape([b, c, -1]) + + avg_1 = torch.mean(f1_reshaped, dim=-1, keepdim=True).unsqueeze(-1) + max_1, _ = torch.max(f1_reshaped, dim=-1, keepdim=True) + max_1 = max_1.unsqueeze(-1) + avg_1 = F.relu(self.avg1(avg_1)) + max_1 = F.relu(self.max1(max_1)) + avg_1 = self.avg11(avg_1).squeeze(-1) + max_1 = self.max11(max_1).squeeze(-1) + a1 = avg_1 + max_1 + + avg_2 = torch.mean(f2_reshaped, dim=-1, keepdim=True).unsqueeze(-1) + max_2, _ = torch.max(f2_reshaped, dim=-1, keepdim=True) + max_2 = max_2.unsqueeze(-1) + avg_2 = F.relu(self.avg2(avg_2)) + max_2 = F.relu(self.max2(max_2)) + avg_2 = self.avg22(avg_2).squeeze(-1) + max_2 = self.max22(max_2).squeeze(-1) + a2 = avg_2 + max_2 + + cross = torch.matmul(a1, a2.transpose(1, 2)) + a1 = torch.matmul(F.softmax(cross, dim=-1), f1_reshaped) + a2 = torch.matmul(F.softmax(cross.transpose(1, 2), dim=-1), f2_reshaped) + + # Spatial attention for f1 + a1_spatial = a1.reshape([b, c, h, w]) + avg_out = torch.mean(a1_spatial, dim=1, keepdim=True) + max_out, _ = torch.max(a1_spatial, dim=1, keepdim=True) + a1_spatial = torch.cat([avg_out, max_out], dim=1) + a1_spatial = F.relu(self.conv1_spatial(a1_spatial)) + a1_spatial = self.conv2_spatial(a1_spatial) + a1_spatial = a1_spatial.reshape([b, 1, -1]) + a1_spatial = F.softmax(a1_spatial, dim=-1) + + # Spatial attention for f2 + a2_spatial = a2.reshape([b, c, h, w]) + avg_out = torch.mean(a2_spatial, dim=1, keepdim=True) + max_out, _ = torch.max(a2_spatial, dim=1, keepdim=True) + a2_spatial = torch.cat([avg_out, max_out], dim=1) + a2_spatial = F.relu(self.conv1_spatial(a2_spatial)) + a2_spatial = self.conv2_spatial(a2_spatial) + a2_spatial = a2_spatial.reshape([b, 1, -1]) + a2_spatial = F.softmax(a2_spatial, dim=-1) + + f1_out = f1_reshaped * a1_spatial + f1_reshaped + f2_out = f2_reshaped * a2_spatial + f2_reshaped + + # 修改点: 直接返回 [B, C, H, W] 格式 + return f1_out.reshape(b, c, h, w), f2_out.reshape(b, c, h, w) + + +# =================================================================== +# Part 2: 全新的 CrossFusedBranches 模块 +# =================================================================== +class CrossFusedBranches(BaseModule): + def __init__(self, + detail_channels, + in_channels, + semantic_channels, + semantic_expansion_ratio, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + # 1. --- Porting Detail Branch Layers (with Depthwise Separable Conv) --- + # 将这里的 ConvModule 全部替换为 DepthwiseSeparableConvModule + self.d_stage1 = nn.Sequential( + DepthwiseSeparableConvModule(in_channels, detail_channels[0], 3, stride=2, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + DepthwiseSeparableConvModule(detail_channels[0], detail_channels[0], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + ) + self.d_stage2 = nn.Sequential( + DepthwiseSeparableConvModule(detail_channels[0], detail_channels[1], 3, stride=2, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + DepthwiseSeparableConvModule(detail_channels[1], detail_channels[1], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + DepthwiseSeparableConvModule(detail_channels[1], detail_channels[1], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + ) + self.d_stage3 = nn.Sequential( + DepthwiseSeparableConvModule(detail_channels[1], detail_channels[2], 3, stride=2, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + DepthwiseSeparableConvModule(detail_channels[2], detail_channels[2], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + DepthwiseSeparableConvModule(detail_channels[2], detail_channels[2], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + ) + + # 2. --- Porting Semantic Branch Layers --- + # (这部分保持不变) + self.s_stage1 = StemBlock(in_channels, semantic_channels[0]) + self.s_stage2 = nn.Sequential( + GELayer(semantic_channels[0], semantic_channels[1], semantic_expansion_ratio, 2), + GELayer(semantic_channels[1], semantic_channels[1], semantic_expansion_ratio, 1) + ) + self.s_stage3 = nn.Sequential( + GELayer(semantic_channels[1], semantic_channels[2], semantic_expansion_ratio, 2), + GELayer(semantic_channels[2], semantic_channels[2], semantic_expansion_ratio, 1) + ) + self.s_stage4 = nn.Sequential( + GELayer(semantic_channels[2], semantic_channels[3], semantic_expansion_ratio, 2), + GELayer(semantic_channels[3], semantic_channels[3], semantic_expansion_ratio, 1), + GELayer(semantic_channels[3], semantic_channels[3], semantic_expansion_ratio, 1), + GELayer(semantic_channels[3], semantic_channels[3], semantic_expansion_ratio, 1) + ) + self.s_ce_block = CEBlock(semantic_channels[3], semantic_channels[3]) + + # 3. --- Cross Fusion Modules --- + # (这部分保持不变) + fusion_channels = detail_channels[1] + self.adapter_d = ConvModule(detail_channels[1], fusion_channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.adapter_s = ConvModule(semantic_channels[2], fusion_channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.cafm = CAFM(channels=fusion_channels) + self.injection_conv = ConvModule(fusion_channels, detail_channels[1], 3, padding=1, norm_cfg=norm_cfg, act_cfg=act_cfg) + + def forward(self, x): + # Forward 逻辑完全保持不变 + semantic_outs = [] + + d1 = self.d_stage1(x) + d2 = self.d_stage2(d1) + s1 = self.s_stage1(x) + semantic_outs.append(s1) + s2 = self.s_stage2(s1) + semantic_outs.append(s2) + s3 = self.s_stage3(s2) + semantic_outs.append(s3) + + s_for_fusion = resize(s3, size=d2.shape[2:], mode='bilinear', align_corners=False) + d_adapted = self.adapter_d(d2) + s_adapted = self.adapter_s(s_for_fusion) + fused_d, _ = self.cafm(d_adapted, s_adapted) + fused_processed = self.injection_conv(fused_d) + d2_enhanced = d2 + fused_processed + + detail_final = self.d_stage3(d2_enhanced) + s4 = self.s_stage4(s3) + semantic_outs.append(s4) + s_final = self.s_ce_block(s4) + semantic_outs.append(s_final) + + return detail_final, semantic_outs + + +class StemBlock(BaseModule): + """Stem Block at the beginning of Semantic Branch. + + Args: + in_channels (int): Number of input channels. + Default: 3. + out_channels (int): Number of output channels. + Default: 16. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): First feature map in Semantic Branch. + """ + + def __init__(self, + in_channels=3, + out_channels=16, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.conv_first = ConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.convs = nn.Sequential( + ConvModule( + in_channels=out_channels, + out_channels=out_channels // 2, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + in_channels=out_channels // 2, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.pool = nn.MaxPool2d( + kernel_size=3, stride=2, padding=1, ceil_mode=False) + self.fuse_last = ConvModule( + in_channels=out_channels * 2, + out_channels=out_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + x = self.conv_first(x) + x_left = self.convs(x) + x_right = self.pool(x) + x = self.fuse_last(torch.cat([x_left, x_right], dim=1)) + return x + + +class GELayer(BaseModule): + """Gather-and-Expansion Layer. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + exp_ratio (int): Expansion ratio for middle channels. + Default: 6. + stride (int): Stride of GELayer. Default: 1 + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Intermediate feature map in + Semantic Branch. + """ + + def __init__(self, + in_channels, + out_channels, + exp_ratio=6, + stride=1, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + mid_channel = in_channels * exp_ratio + self.conv1 = ConvModule( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + if stride == 1: + self.dwconv = nn.Sequential( + # ReLU in ConvModule not shown in paper + ConvModule( + in_channels=in_channels, + out_channels=mid_channel, + kernel_size=3, + stride=stride, + padding=1, + groups=in_channels, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.shortcut = None + else: + self.dwconv = nn.Sequential( + ConvModule( + in_channels=in_channels, + out_channels=mid_channel, + kernel_size=3, + stride=stride, + padding=1, + groups=in_channels, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), + # ReLU in ConvModule not shown in paper + ConvModule( + in_channels=mid_channel, + out_channels=mid_channel, + kernel_size=3, + stride=1, + padding=1, + groups=mid_channel, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ) + self.shortcut = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=norm_cfg, + pw_act_cfg=None, + )) + + self.conv2 = nn.Sequential( + ConvModule( + in_channels=mid_channel, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None, + )) + + self.act = build_activation_layer(act_cfg) + + def forward(self, x): + identity = x + x = self.conv1(x) + x = self.dwconv(x) + x = self.conv2(x) + if self.shortcut is not None: + shortcut = self.shortcut(identity) + x = x + shortcut + else: + x = x + identity + x = self.act(x) + return x + + +class CEBlock(BaseModule): + """Context Embedding Block for large receptive filed in Semantic Branch. + + Args: + in_channels (int): Number of input channels. + Default: 3. + out_channels (int): Number of output channels. + Default: 16. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Last feature map in Semantic Branch. + """ + + def __init__(self, + in_channels=3, + out_channels=16, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + self.gap = nn.Sequential( + nn.AdaptiveAvgPool2d((1, 1)), + build_norm_layer(norm_cfg, self.in_channels)[1]) + self.conv_gap = ConvModule( + in_channels=self.in_channels, + out_channels=self.out_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + # Note: in paper here is naive conv2d, no bn-relu + self.conv_last = ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + identity = x + x = self.gap(x) + x = self.conv_gap(x) + x = identity + x + x = self.conv_last(x) + return x + + +class BGALayer(BaseModule): + """Bilateral Guided Aggregation Layer to fuse the complementary information + from both Detail Branch and Semantic Branch. + + Args: + out_channels (int): Number of output channels. + Default: 128. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + output (torch.Tensor): Output feature map for Segment heads. + """ + + def __init__(self, + out_channels=128, + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.out_channels = out_channels + self.align_corners = align_corners + self.detail_dwconv = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=None, + pw_act_cfg=None, + )) + self.detail_down = nn.Sequential( + ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=2, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), + nn.AvgPool2d(kernel_size=3, stride=2, padding=1, ceil_mode=False)) + self.semantic_conv = nn.Sequential( + ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None)) + self.semantic_dwconv = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=None, + pw_act_cfg=None, + )) + self.conv = ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + inplace=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + + def forward(self, x_d, x_s): + detail_dwconv = self.detail_dwconv(x_d) + detail_down = self.detail_down(x_d) + semantic_conv = self.semantic_conv(x_s) + semantic_dwconv = self.semantic_dwconv(x_s) + semantic_conv = resize( + input=semantic_conv, + size=detail_dwconv.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + fuse_1 = detail_dwconv * torch.sigmoid(semantic_conv) + fuse_2 = detail_down * torch.sigmoid(semantic_dwconv) + fuse_2 = resize( + input=fuse_2, + size=fuse_1.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + output = self.conv(fuse_1 + fuse_2) + return output + + +@MODELS.register_module() +class My_BiSeNetV2_A1_add_A2(BaseModule): + def __init__(self, + in_channels=3, + detail_channels=(64, 64, 128), + semantic_channels=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + if init_cfg is None: + init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict(type='Constant', val=1, layer=['_BatchNorm', 'GroupNorm']) + ] + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_indices = out_indices + self.align_corners = align_corners + + # --- 核心修改点 --- + # 用一个 CrossFusedBranches 模块替换掉旧的 self.detail 和 self.semantic + self.branches = CrossFusedBranches( + detail_channels=detail_channels, + in_channels=in_channels, + semantic_channels=semantic_channels, + semantic_expansion_ratio=semantic_expansion_ratio, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg + ) + + self.bga = BGALayer(bga_channels, self.align_corners) + + def forward(self, x): + # --- 核心修改点 --- + # 一次调用即可获得两个分支的最终输出 + x_detail, x_semantic_lst = self.branches(x) + + # 后续代码保持不变 + x_head = self.bga(x_detail, x_semantic_lst[-1]) + outs = [x_head] + x_semantic_lst[:-1] + outs = [outs[i] for i in self.out_indices] + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A2.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A2.py new file mode 100644 index 0000000..b58ee3b --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/my_bisnetv2_A2.py @@ -0,0 +1,597 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import (ConvModule, DepthwiseSeparableConvModule, + build_activation_layer, build_norm_layer) +from mmengine.model import BaseModule +from mmseg.registry import MODELS +from ..utils import resize +import torch.nn.functional as F +from einops import rearrange + +# =================================================================== +# Part 1: CAFM 模块 (稍作修改以简化输出) +# =================================================================== +class CAFM(nn.Module): + def __init__(self, channels): + super(CAFM, self).__init__() + self.conv1_spatial = nn.Conv2d(2, 1, 3, stride=1, padding=1, groups=1) + self.conv2_spatial = nn.Conv2d(1, 1, 3, stride=1, padding=1, groups=1) + self.avg1 = nn.Conv2d(channels, 64, 1, stride=1, padding=0) + self.avg2 = nn.Conv2d(channels, 64, 1, stride=1, padding=0) + self.max1 = nn.Conv2d(channels, 64, 1, stride=1, padding=0) + self.max2 = nn.Conv2d(channels, 64, 1, stride=1, padding=0) + self.avg11 = nn.Conv2d(64, channels, 1, stride=1, padding=0) + self.avg22 = nn.Conv2d(64, channels, 1, stride=1, padding=0) + self.max11 = nn.Conv2d(64, channels, 1, stride=1, padding=0) + self.max22 = nn.Conv2d(64, channels, 1, stride=1, padding=0) + + def forward(self, f1, f2): + b, c, h, w = f1.size() + f1_reshaped = f1.reshape([b, c, -1]) + f2_reshaped = f2.reshape([b, c, -1]) + + avg_1 = torch.mean(f1_reshaped, dim=-1, keepdim=True).unsqueeze(-1) + max_1, _ = torch.max(f1_reshaped, dim=-1, keepdim=True) + max_1 = max_1.unsqueeze(-1) + avg_1 = F.relu(self.avg1(avg_1)) + max_1 = F.relu(self.max1(max_1)) + avg_1 = self.avg11(avg_1).squeeze(-1) + max_1 = self.max11(max_1).squeeze(-1) + a1 = avg_1 + max_1 + + avg_2 = torch.mean(f2_reshaped, dim=-1, keepdim=True).unsqueeze(-1) + max_2, _ = torch.max(f2_reshaped, dim=-1, keepdim=True) + max_2 = max_2.unsqueeze(-1) + avg_2 = F.relu(self.avg2(avg_2)) + max_2 = F.relu(self.max2(max_2)) + avg_2 = self.avg22(avg_2).squeeze(-1) + max_2 = self.max22(max_2).squeeze(-1) + a2 = avg_2 + max_2 + + cross = torch.matmul(a1, a2.transpose(1, 2)) + a1 = torch.matmul(F.softmax(cross, dim=-1), f1_reshaped) + a2 = torch.matmul(F.softmax(cross.transpose(1, 2), dim=-1), f2_reshaped) + + # Spatial attention for f1 + a1_spatial = a1.reshape([b, c, h, w]) + avg_out = torch.mean(a1_spatial, dim=1, keepdim=True) + max_out, _ = torch.max(a1_spatial, dim=1, keepdim=True) + a1_spatial = torch.cat([avg_out, max_out], dim=1) + a1_spatial = F.relu(self.conv1_spatial(a1_spatial)) + a1_spatial = self.conv2_spatial(a1_spatial) + a1_spatial = a1_spatial.reshape([b, 1, -1]) + a1_spatial = F.softmax(a1_spatial, dim=-1) + + # Spatial attention for f2 + a2_spatial = a2.reshape([b, c, h, w]) + avg_out = torch.mean(a2_spatial, dim=1, keepdim=True) + max_out, _ = torch.max(a2_spatial, dim=1, keepdim=True) + a2_spatial = torch.cat([avg_out, max_out], dim=1) + a2_spatial = F.relu(self.conv1_spatial(a2_spatial)) + a2_spatial = self.conv2_spatial(a2_spatial) + a2_spatial = a2_spatial.reshape([b, 1, -1]) + a2_spatial = F.softmax(a2_spatial, dim=-1) + + f1_out = f1_reshaped * a1_spatial + f1_reshaped + f2_out = f2_reshaped * a2_spatial + f2_reshaped + + # 修改点: 直接返回 [B, C, H, W] 格式 + return f1_out.reshape(b, c, h, w), f2_out.reshape(b, c, h, w) + + +# =================================================================== +# Part 2: 全新的 CrossFusedBranches 模块 +# =================================================================== +class CrossFusedBranches(BaseModule): + def __init__(self, + detail_channels, + in_channels, + semantic_channels, + semantic_expansion_ratio, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + # 1. --- Porting Detail Branch Layers --- + # 复制自原始 DetailBranch 的代码,但拆分为独立的 stage 模块 + self.d_stage1 = nn.Sequential( + ConvModule(in_channels, detail_channels[0], 3, stride=2, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + ConvModule(detail_channels[0], detail_channels[0], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + ) + self.d_stage2 = nn.Sequential( + ConvModule(detail_channels[0], detail_channels[1], 3, stride=2, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + ConvModule(detail_channels[1], detail_channels[1], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + ConvModule(detail_channels[1], detail_channels[1], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + ) + self.d_stage3 = nn.Sequential( + ConvModule(detail_channels[1], detail_channels[2], 3, stride=2, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + ConvModule(detail_channels[2], detail_channels[2], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg), + ConvModule(detail_channels[2], detail_channels[2], 3, stride=1, padding=1, conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg) + ) + + # 2. --- Porting Semantic Branch Layers --- + # 复制自原始 SemanticBranch 的代码 + self.s_stage1 = StemBlock(in_channels, semantic_channels[0]) + self.s_stage2 = nn.Sequential( + GELayer(semantic_channels[0], semantic_channels[1], semantic_expansion_ratio, 2), + GELayer(semantic_channels[1], semantic_channels[1], semantic_expansion_ratio, 1) + ) + self.s_stage3 = nn.Sequential( + GELayer(semantic_channels[1], semantic_channels[2], semantic_expansion_ratio, 2), + GELayer(semantic_channels[2], semantic_channels[2], semantic_expansion_ratio, 1) + ) + self.s_stage4 = nn.Sequential( + GELayer(semantic_channels[2], semantic_channels[3], semantic_expansion_ratio, 2), + GELayer(semantic_channels[3], semantic_channels[3], semantic_expansion_ratio, 1), + GELayer(semantic_channels[3], semantic_channels[3], semantic_expansion_ratio, 1), + GELayer(semantic_channels[3], semantic_channels[3], semantic_expansion_ratio, 1) + ) + self.s_ce_block = CEBlock(semantic_channels[3], semantic_channels[3]) + + # 3. --- Cross Fusion Modules --- + # 定义交叉融合点:Detail Stage 2 (d2) 和 Semantic Stage 3 (s3) + fusion_channels = detail_channels[1] # e.g., 64 + self.adapter_d = ConvModule(detail_channels[1], fusion_channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.adapter_s = ConvModule(semantic_channels[2], fusion_channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.cafm = CAFM(channels=fusion_channels) + self.injection_conv = ConvModule(fusion_channels, detail_channels[1], 3, padding=1, norm_cfg=norm_cfg, act_cfg=act_cfg) + + def forward(self, x): + semantic_outs = [] + + # --- Stage 1 & 2 (Detail and Semantic) --- + d1 = self.d_stage1(x) + d2 = self.d_stage2(d1) + s1 = self.s_stage1(x) + semantic_outs.append(s1) + s2 = self.s_stage2(s1) + semantic_outs.append(s2) + s3 = self.s_stage3(s2) + semantic_outs.append(s3) + + # --- Cross Fusion Step --- + # 1. 对齐空间维度 + s_for_fusion = resize(s3, size=d2.shape[2:], mode='bilinear', align_corners=False) + # 2. 对齐通道维度 + d_adapted = self.adapter_d(d2) + s_adapted = self.adapter_s(s_for_fusion) + # 3. 执行 CAFM 融合 + fused_d, _ = self.cafm(d_adapted, s_adapted) # 我们只使用被语义增强后的细节特征 + # 4. 将融合特征注入回细节分支 + fused_processed = self.injection_conv(fused_d) + d2_enhanced = d2 + fused_processed + + # --- Final Stages --- + detail_final = self.d_stage3(d2_enhanced) + s4 = self.s_stage4(s3) + semantic_outs.append(s4) + s_final = self.s_ce_block(s4) + semantic_outs.append(s_final) + + return detail_final, semantic_outs + + +class StemBlock(BaseModule): + """Stem Block at the beginning of Semantic Branch. + + Args: + in_channels (int): Number of input channels. + Default: 3. + out_channels (int): Number of output channels. + Default: 16. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): First feature map in Semantic Branch. + """ + + def __init__(self, + in_channels=3, + out_channels=16, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.conv_first = ConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.convs = nn.Sequential( + ConvModule( + in_channels=out_channels, + out_channels=out_channels // 2, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + in_channels=out_channels // 2, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.pool = nn.MaxPool2d( + kernel_size=3, stride=2, padding=1, ceil_mode=False) + self.fuse_last = ConvModule( + in_channels=out_channels * 2, + out_channels=out_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + x = self.conv_first(x) + x_left = self.convs(x) + x_right = self.pool(x) + x = self.fuse_last(torch.cat([x_left, x_right], dim=1)) + return x + + +class GELayer(BaseModule): + """Gather-and-Expansion Layer. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + exp_ratio (int): Expansion ratio for middle channels. + Default: 6. + stride (int): Stride of GELayer. Default: 1 + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Intermediate feature map in + Semantic Branch. + """ + + def __init__(self, + in_channels, + out_channels, + exp_ratio=6, + stride=1, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + mid_channel = in_channels * exp_ratio + self.conv1 = ConvModule( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + if stride == 1: + self.dwconv = nn.Sequential( + # ReLU in ConvModule not shown in paper + ConvModule( + in_channels=in_channels, + out_channels=mid_channel, + kernel_size=3, + stride=stride, + padding=1, + groups=in_channels, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.shortcut = None + else: + self.dwconv = nn.Sequential( + ConvModule( + in_channels=in_channels, + out_channels=mid_channel, + kernel_size=3, + stride=stride, + padding=1, + groups=in_channels, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), + # ReLU in ConvModule not shown in paper + ConvModule( + in_channels=mid_channel, + out_channels=mid_channel, + kernel_size=3, + stride=1, + padding=1, + groups=mid_channel, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ) + self.shortcut = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=norm_cfg, + pw_act_cfg=None, + )) + + self.conv2 = nn.Sequential( + ConvModule( + in_channels=mid_channel, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None, + )) + + self.act = build_activation_layer(act_cfg) + + def forward(self, x): + identity = x + x = self.conv1(x) + x = self.dwconv(x) + x = self.conv2(x) + if self.shortcut is not None: + shortcut = self.shortcut(identity) + x = x + shortcut + else: + x = x + identity + x = self.act(x) + return x + + +class CEBlock(BaseModule): + """Context Embedding Block for large receptive filed in Semantic Branch. + + Args: + in_channels (int): Number of input channels. + Default: 3. + out_channels (int): Number of output channels. + Default: 16. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + x (torch.Tensor): Last feature map in Semantic Branch. + """ + + def __init__(self, + in_channels=3, + out_channels=16, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + self.gap = nn.Sequential( + nn.AdaptiveAvgPool2d((1, 1)), + build_norm_layer(norm_cfg, self.in_channels)[1]) + self.conv_gap = ConvModule( + in_channels=self.in_channels, + out_channels=self.out_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + # Note: in paper here is naive conv2d, no bn-relu + self.conv_last = ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + identity = x + x = self.gap(x) + x = self.conv_gap(x) + x = identity + x + x = self.conv_last(x) + return x + + +class BGALayer(BaseModule): + """Bilateral Guided Aggregation Layer to fuse the complementary information + from both Detail Branch and Semantic Branch. + + Args: + out_channels (int): Number of output channels. + Default: 128. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + Returns: + output (torch.Tensor): Output feature map for Segment heads. + """ + + def __init__(self, + out_channels=128, + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.out_channels = out_channels + self.align_corners = align_corners + self.detail_dwconv = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=None, + pw_act_cfg=None, + )) + self.detail_down = nn.Sequential( + ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=2, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None), + nn.AvgPool2d(kernel_size=3, stride=2, padding=1, ceil_mode=False)) + self.semantic_conv = nn.Sequential( + ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + bias=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None)) + self.semantic_dwconv = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=None, + pw_act_cfg=None, + )) + self.conv = ConvModule( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + inplace=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + + def forward(self, x_d, x_s): + detail_dwconv = self.detail_dwconv(x_d) + detail_down = self.detail_down(x_d) + semantic_conv = self.semantic_conv(x_s) + semantic_dwconv = self.semantic_dwconv(x_s) + semantic_conv = resize( + input=semantic_conv, + size=detail_dwconv.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + fuse_1 = detail_dwconv * torch.sigmoid(semantic_conv) + fuse_2 = detail_down * torch.sigmoid(semantic_dwconv) + fuse_2 = resize( + input=fuse_2, + size=fuse_1.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + output = self.conv(fuse_1 + fuse_2) + return output + + +@MODELS.register_module() +class My_BiSeNetV2_A2(BaseModule): + def __init__(self, + in_channels=3, + detail_channels=(64, 64, 128), + semantic_channels=(16, 32, 64, 128), + semantic_expansion_ratio=6, + bga_channels=128, + out_indices=(0, 1, 2, 3, 4), + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + if init_cfg is None: + init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict(type='Constant', val=1, layer=['_BatchNorm', 'GroupNorm']) + ] + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_indices = out_indices + self.align_corners = align_corners + + # --- 核心修改点 --- + # 用一个 CrossFusedBranches 模块替换掉旧的 self.detail 和 self.semantic + self.branches = CrossFusedBranches( + detail_channels=detail_channels, + in_channels=in_channels, + semantic_channels=semantic_channels, + semantic_expansion_ratio=semantic_expansion_ratio, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg + ) + + self.bga = BGALayer(bga_channels, self.align_corners) + + def forward(self, x): + # --- 核心修改点 --- + # 一次调用即可获得两个分支的最终输出 + x_detail, x_semantic_lst = self.branches(x) + + # 后续代码保持不变 + x_head = self.bga(x_detail, x_semantic_lst[-1]) + outs = [x_head] + x_semantic_lst[:-1] + outs = [outs[i] for i in self.out_indices] + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/pidnet.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/pidnet.py new file mode 100644 index 0000000..0b711a3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/pidnet.py @@ -0,0 +1,522 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from mmengine.runner import CheckpointLoader +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.utils import OptConfigType +from ..utils import DAPPM, PAPPM, BasicBlock, Bottleneck + + +class PagFM(BaseModule): + """Pixel-attention-guided fusion module. + + Args: + in_channels (int): The number of input channels. + channels (int): The number of channels. + after_relu (bool): Whether to use ReLU before attention. + Default: False. + with_channel (bool): Whether to use channel attention. + Default: False. + upsample_mode (str): The mode of upsample. Default: 'bilinear'. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(typ='ReLU', inplace=True). + init_cfg (dict): Config dict for initialization. Default: None. + """ + + def __init__(self, + in_channels: int, + channels: int, + after_relu: bool = False, + with_channel: bool = False, + upsample_mode: str = 'bilinear', + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = dict(typ='ReLU', inplace=True), + init_cfg: OptConfigType = None): + super().__init__(init_cfg) + self.after_relu = after_relu + self.with_channel = with_channel + self.upsample_mode = upsample_mode + self.f_i = ConvModule( + in_channels, channels, 1, norm_cfg=norm_cfg, act_cfg=None) + self.f_p = ConvModule( + in_channels, channels, 1, norm_cfg=norm_cfg, act_cfg=None) + if with_channel: + self.up = ConvModule( + channels, in_channels, 1, norm_cfg=norm_cfg, act_cfg=None) + if after_relu: + self.relu = MODELS.build(act_cfg) + + def forward(self, x_p: Tensor, x_i: Tensor) -> Tensor: + """Forward function. + + Args: + x_p (Tensor): The featrue map from P branch. + x_i (Tensor): The featrue map from I branch. + + Returns: + Tensor: The feature map with pixel-attention-guided fusion. + """ + if self.after_relu: + x_p = self.relu(x_p) + x_i = self.relu(x_i) + + f_i = self.f_i(x_i) + f_i = F.interpolate( + f_i, + size=x_p.shape[2:], + mode=self.upsample_mode, + align_corners=False) + + f_p = self.f_p(x_p) + + if self.with_channel: + sigma = torch.sigmoid(self.up(f_p * f_i)) + else: + sigma = torch.sigmoid(torch.sum(f_p * f_i, dim=1).unsqueeze(1)) + + x_i = F.interpolate( + x_i, + size=x_p.shape[2:], + mode=self.upsample_mode, + align_corners=False) + + out = sigma * x_i + (1 - sigma) * x_p + return out + + +class Bag(BaseModule): + """Boundary-attention-guided fusion module. + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels. + kernel_size (int): The kernel size of the convolution. Default: 3. + padding (int): The padding of the convolution. Default: 1. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU', inplace=True). + conv_cfg (dict): Config dict for convolution layer. + Default: dict(order=('norm', 'act', 'conv')). + init_cfg (dict): Config dict for initialization. Default: None. + """ + + def __init__(self, + in_channels: int, + out_channels: int, + kernel_size: int = 3, + padding: int = 1, + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + conv_cfg: OptConfigType = dict(order=('norm', 'act', 'conv')), + init_cfg: OptConfigType = None): + super().__init__(init_cfg) + + self.conv = ConvModule( + in_channels, + out_channels, + kernel_size, + padding=padding, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **conv_cfg) + + def forward(self, x_p: Tensor, x_i: Tensor, x_d: Tensor) -> Tensor: + """Forward function. + + Args: + x_p (Tensor): The featrue map from P branch. + x_i (Tensor): The featrue map from I branch. + x_d (Tensor): The featrue map from D branch. + + Returns: + Tensor: The feature map with boundary-attention-guided fusion. + """ + sigma = torch.sigmoid(x_d) + return self.conv(sigma * x_p + (1 - sigma) * x_i) + + +class LightBag(BaseModule): + """Light Boundary-attention-guided fusion module. + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. Default: None. + init_cfg (dict): Config dict for initialization. Default: None. + """ + + def __init__(self, + in_channels: int, + out_channels: int, + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = None, + init_cfg: OptConfigType = None): + super().__init__(init_cfg) + self.f_p = ConvModule( + in_channels, + out_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.f_i = ConvModule( + in_channels, + out_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x_p: Tensor, x_i: Tensor, x_d: Tensor) -> Tensor: + """Forward function. + Args: + x_p (Tensor): The featrue map from P branch. + x_i (Tensor): The featrue map from I branch. + x_d (Tensor): The featrue map from D branch. + + Returns: + Tensor: The feature map with light boundary-attention-guided + fusion. + """ + sigma = torch.sigmoid(x_d) + + f_p = self.f_p((1 - sigma) * x_i + x_p) + f_i = self.f_i(x_i + sigma * x_p) + + return f_p + f_i + + +@MODELS.register_module() +class PIDNet(BaseModule): + """PIDNet backbone. + + This backbone is the implementation of `PIDNet: A Real-time Semantic + Segmentation Network Inspired from PID Controller + `_. + Modified from https://github.com/XuJiacong/PIDNet. + + Licensed under the MIT License. + + Args: + in_channels (int): The number of input channels. Default: 3. + channels (int): The number of channels in the stem layer. Default: 64. + ppm_channels (int): The number of channels in the PPM layer. + Default: 96. + num_stem_blocks (int): The number of blocks in the stem layer. + Default: 2. + num_branch_blocks (int): The number of blocks in the branch layer. + Default: 3. + align_corners (bool): The align_corners argument of F.interpolate. + Default: False. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU', inplace=True). + init_cfg (dict): Config dict for initialization. Default: None. + """ + + def __init__(self, + in_channels: int = 3, + channels: int = 64, + ppm_channels: int = 96, + num_stem_blocks: int = 2, + num_branch_blocks: int = 3, + align_corners: bool = False, + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + init_cfg: OptConfigType = None, + **kwargs): + super().__init__(init_cfg) + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.align_corners = align_corners + + # stem layer + self.stem = self._make_stem_layer(in_channels, channels, + num_stem_blocks) + self.relu = nn.ReLU() + + # I Branch + self.i_branch_layers = nn.ModuleList() + for i in range(3): + self.i_branch_layers.append( + self._make_layer( + block=BasicBlock if i < 2 else Bottleneck, + in_channels=channels * 2**(i + 1), + channels=channels * 8 if i > 0 else channels * 4, + num_blocks=num_branch_blocks if i < 2 else 2, + stride=2)) + + # P Branch + self.p_branch_layers = nn.ModuleList() + for i in range(3): + self.p_branch_layers.append( + self._make_layer( + block=BasicBlock if i < 2 else Bottleneck, + in_channels=channels * 2, + channels=channels * 2, + num_blocks=num_stem_blocks if i < 2 else 1)) + self.compression_1 = ConvModule( + channels * 4, + channels * 2, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None) + self.compression_2 = ConvModule( + channels * 8, + channels * 2, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None) + self.pag_1 = PagFM(channels * 2, channels) + self.pag_2 = PagFM(channels * 2, channels) + + # D Branch + if num_stem_blocks == 2: + self.d_branch_layers = nn.ModuleList([ + self._make_single_layer(BasicBlock, channels * 2, channels), + self._make_layer(Bottleneck, channels, channels, 1) + ]) + channel_expand = 1 + spp_module = PAPPM + dfm_module = LightBag + act_cfg_dfm = None + else: + self.d_branch_layers = nn.ModuleList([ + self._make_single_layer(BasicBlock, channels * 2, + channels * 2), + self._make_single_layer(BasicBlock, channels * 2, channels * 2) + ]) + channel_expand = 2 + spp_module = DAPPM + dfm_module = Bag + act_cfg_dfm = act_cfg + + self.diff_1 = ConvModule( + channels * 4, + channels * channel_expand, + kernel_size=3, + padding=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None) + self.diff_2 = ConvModule( + channels * 8, + channels * 2, + kernel_size=3, + padding=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None) + + self.spp = spp_module( + channels * 16, ppm_channels, channels * 4, num_scales=5) + self.dfm = dfm_module( + channels * 4, channels * 4, norm_cfg=norm_cfg, act_cfg=act_cfg_dfm) + + self.d_branch_layers.append( + self._make_layer(Bottleneck, channels * 2, channels * 2, 1)) + + def _make_stem_layer(self, in_channels: int, channels: int, + num_blocks: int) -> nn.Sequential: + """Make stem layer. + + Args: + in_channels (int): Number of input channels. + channels (int): Number of output channels. + num_blocks (int): Number of blocks. + + Returns: + nn.Sequential: The stem layer. + """ + + layers = [ + ConvModule( + in_channels, + channels, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + ConvModule( + channels, + channels, + kernel_size=3, + stride=2, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + ] + + layers.append( + self._make_layer(BasicBlock, channels, channels, num_blocks)) + layers.append(nn.ReLU()) + layers.append( + self._make_layer( + BasicBlock, channels, channels * 2, num_blocks, stride=2)) + layers.append(nn.ReLU()) + + return nn.Sequential(*layers) + + def _make_layer(self, + block: BasicBlock, + in_channels: int, + channels: int, + num_blocks: int, + stride: int = 1) -> nn.Sequential: + """Make layer for PIDNet backbone. + Args: + block (BasicBlock): Basic block. + in_channels (int): Number of input channels. + channels (int): Number of output channels. + num_blocks (int): Number of blocks. + stride (int): Stride of the first block. Default: 1. + + Returns: + nn.Sequential: The Branch Layer. + """ + downsample = None + if stride != 1 or in_channels != channels * block.expansion: + downsample = ConvModule( + in_channels, + channels * block.expansion, + kernel_size=1, + stride=stride, + norm_cfg=self.norm_cfg, + act_cfg=None) + + layers = [block(in_channels, channels, stride, downsample)] + in_channels = channels * block.expansion + for i in range(1, num_blocks): + layers.append( + block( + in_channels, + channels, + stride=1, + act_cfg_out=None if i == num_blocks - 1 else self.act_cfg)) + return nn.Sequential(*layers) + + def _make_single_layer(self, + block: Union[BasicBlock, Bottleneck], + in_channels: int, + channels: int, + stride: int = 1) -> nn.Module: + """Make single layer for PIDNet backbone. + Args: + block (BasicBlock or Bottleneck): Basic block or Bottleneck. + in_channels (int): Number of input channels. + channels (int): Number of output channels. + stride (int): Stride of the first block. Default: 1. + + Returns: + nn.Module + """ + + downsample = None + if stride != 1 or in_channels != channels * block.expansion: + downsample = ConvModule( + in_channels, + channels * block.expansion, + kernel_size=1, + stride=stride, + norm_cfg=self.norm_cfg, + act_cfg=None) + return block( + in_channels, channels, stride, downsample, act_cfg_out=None) + + def init_weights(self): + """Initialize the weights in backbone. + + Since the D branch is not initialized by the pre-trained model, we + initialize it with the same method as the ResNet. + """ + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_( + m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + if self.init_cfg is not None: + assert 'checkpoint' in self.init_cfg, f'Only support ' \ + f'specify `Pretrained` in ' \ + f'`init_cfg` in ' \ + f'{self.__class__.__name__} ' + ckpt = CheckpointLoader.load_checkpoint( + self.init_cfg['checkpoint'], map_location='cpu') + self.load_state_dict(ckpt, strict=False) + + def forward(self, x: Tensor) -> Union[Tensor, Tuple[Tensor]]: + """Forward function. + + Args: + x (Tensor): Input tensor with shape (B, C, H, W). + + Returns: + Tensor or tuple[Tensor]: If self.training is True, return + tuple[Tensor], else return Tensor. + """ + w_out = x.shape[-1] // 8 + h_out = x.shape[-2] // 8 + + # stage 0-2 + x = self.stem(x) + + # stage 3 + x_i = self.relu(self.i_branch_layers[0](x)) + x_p = self.p_branch_layers[0](x) + x_d = self.d_branch_layers[0](x) + + comp_i = self.compression_1(x_i) + x_p = self.pag_1(x_p, comp_i) + diff_i = self.diff_1(x_i) + x_d += F.interpolate( + diff_i, + size=[h_out, w_out], + mode='bilinear', + align_corners=self.align_corners) + if self.training: + temp_p = x_p.clone() + + # stage 4 + x_i = self.relu(self.i_branch_layers[1](x_i)) + x_p = self.p_branch_layers[1](self.relu(x_p)) + x_d = self.d_branch_layers[1](self.relu(x_d)) + + comp_i = self.compression_2(x_i) + x_p = self.pag_2(x_p, comp_i) + diff_i = self.diff_2(x_i) + x_d += F.interpolate( + diff_i, + size=[h_out, w_out], + mode='bilinear', + align_corners=self.align_corners) + if self.training: + temp_d = x_d.clone() + + # stage 5 + x_i = self.i_branch_layers[2](x_i) + x_p = self.p_branch_layers[2](self.relu(x_p)) + x_d = self.d_branch_layers[2](self.relu(x_d)) + + x_i = self.spp(x_i) + x_i = F.interpolate( + x_i, + size=[h_out, w_out], + mode='bilinear', + align_corners=self.align_corners) + out = self.dfm(x_p, x_i, x_d) + return (temp_p, out, temp_d) if self.training else out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnest.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnest.py new file mode 100644 index 0000000..3cc380b --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnest.py @@ -0,0 +1,318 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as cp +from mmcv.cnn import build_conv_layer, build_norm_layer + +from mmseg.registry import MODELS +from ..utils import ResLayer +from .resnet import Bottleneck as _Bottleneck +from .resnet import ResNetV1d + + +class RSoftmax(nn.Module): + """Radix Softmax module in ``SplitAttentionConv2d``. + + Args: + radix (int): Radix of input. + groups (int): Groups of input. + """ + + def __init__(self, radix, groups): + super().__init__() + self.radix = radix + self.groups = groups + + def forward(self, x): + batch = x.size(0) + if self.radix > 1: + x = x.view(batch, self.groups, self.radix, -1).transpose(1, 2) + x = F.softmax(x, dim=1) + x = x.reshape(batch, -1) + else: + x = torch.sigmoid(x) + return x + + +class SplitAttentionConv2d(nn.Module): + """Split-Attention Conv2d in ResNeSt. + + Args: + in_channels (int): Same as nn.Conv2d. + out_channels (int): Same as nn.Conv2d. + kernel_size (int | tuple[int]): Same as nn.Conv2d. + stride (int | tuple[int]): Same as nn.Conv2d. + padding (int | tuple[int]): Same as nn.Conv2d. + dilation (int | tuple[int]): Same as nn.Conv2d. + groups (int): Same as nn.Conv2d. + radix (int): Radix of SpltAtConv2d. Default: 2 + reduction_factor (int): Reduction factor of inter_channels. Default: 4. + conv_cfg (dict): Config dict for convolution layer. Default: None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. Default: None. + dcn (dict): Config dict for DCN. Default: None. + """ + + def __init__(self, + in_channels, + channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + radix=2, + reduction_factor=4, + conv_cfg=None, + norm_cfg=dict(type='BN'), + dcn=None): + super().__init__() + inter_channels = max(in_channels * radix // reduction_factor, 32) + self.radix = radix + self.groups = groups + self.channels = channels + self.with_dcn = dcn is not None + self.dcn = dcn + fallback_on_stride = False + if self.with_dcn: + fallback_on_stride = self.dcn.pop('fallback_on_stride', False) + if self.with_dcn and not fallback_on_stride: + assert conv_cfg is None, 'conv_cfg must be None for DCN' + conv_cfg = dcn + self.conv = build_conv_layer( + conv_cfg, + in_channels, + channels * radix, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups * radix, + bias=False) + self.norm0_name, norm0 = build_norm_layer( + norm_cfg, channels * radix, postfix=0) + self.add_module(self.norm0_name, norm0) + self.relu = nn.ReLU(inplace=True) + self.fc1 = build_conv_layer( + None, channels, inter_channels, 1, groups=self.groups) + self.norm1_name, norm1 = build_norm_layer( + norm_cfg, inter_channels, postfix=1) + self.add_module(self.norm1_name, norm1) + self.fc2 = build_conv_layer( + None, inter_channels, channels * radix, 1, groups=self.groups) + self.rsoftmax = RSoftmax(radix, groups) + + @property + def norm0(self): + """nn.Module: the normalization layer named "norm0" """ + return getattr(self, self.norm0_name) + + @property + def norm1(self): + """nn.Module: the normalization layer named "norm1" """ + return getattr(self, self.norm1_name) + + def forward(self, x): + x = self.conv(x) + x = self.norm0(x) + x = self.relu(x) + + batch, rchannel = x.shape[:2] + batch = x.size(0) + if self.radix > 1: + splits = x.view(batch, self.radix, -1, *x.shape[2:]) + gap = splits.sum(dim=1) + else: + gap = x + gap = F.adaptive_avg_pool2d(gap, 1) + gap = self.fc1(gap) + + gap = self.norm1(gap) + gap = self.relu(gap) + + atten = self.fc2(gap) + atten = self.rsoftmax(atten).view(batch, -1, 1, 1) + + if self.radix > 1: + attens = atten.view(batch, self.radix, -1, *atten.shape[2:]) + out = torch.sum(attens * splits, dim=1) + else: + out = atten * x + return out.contiguous() + + +class Bottleneck(_Bottleneck): + """Bottleneck block for ResNeSt. + + Args: + inplane (int): Input planes of this block. + planes (int): Middle planes of this block. + groups (int): Groups of conv2. + width_per_group (int): Width per group of conv2. 64x4d indicates + ``groups=64, width_per_group=4`` and 32x8d indicates + ``groups=32, width_per_group=8``. + radix (int): Radix of SpltAtConv2d. Default: 2 + reduction_factor (int): Reduction factor of inter_channels in + SplitAttentionConv2d. Default: 4. + avg_down_stride (bool): Whether to use average pool for stride in + Bottleneck. Default: True. + kwargs (dict): Key word arguments for base class. + """ + expansion = 4 + + def __init__(self, + inplanes, + planes, + groups=1, + base_width=4, + base_channels=64, + radix=2, + reduction_factor=4, + avg_down_stride=True, + **kwargs): + """Bottleneck block for ResNeSt.""" + super().__init__(inplanes, planes, **kwargs) + + if groups == 1: + width = self.planes + else: + width = math.floor(self.planes * + (base_width / base_channels)) * groups + + self.avg_down_stride = avg_down_stride and self.conv2_stride > 1 + + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, width, postfix=1) + self.norm3_name, norm3 = build_norm_layer( + self.norm_cfg, self.planes * self.expansion, postfix=3) + + self.conv1 = build_conv_layer( + self.conv_cfg, + self.inplanes, + width, + kernel_size=1, + stride=self.conv1_stride, + bias=False) + self.add_module(self.norm1_name, norm1) + self.with_modulated_dcn = False + self.conv2 = SplitAttentionConv2d( + width, + width, + kernel_size=3, + stride=1 if self.avg_down_stride else self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + radix=radix, + reduction_factor=reduction_factor, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + dcn=self.dcn) + delattr(self, self.norm2_name) + + if self.avg_down_stride: + self.avd_layer = nn.AvgPool2d(3, self.conv2_stride, padding=1) + + self.conv3 = build_conv_layer( + self.conv_cfg, + width, + self.planes * self.expansion, + kernel_size=1, + bias=False) + self.add_module(self.norm3_name, norm3) + + def forward(self, x): + + def _inner_forward(x): + identity = x + + out = self.conv1(x) + out = self.norm1(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv1_plugin_names) + + out = self.conv2(out) + + if self.avg_down_stride: + out = self.avd_layer(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv2_plugin_names) + + out = self.conv3(out) + out = self.norm3(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv3_plugin_names) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + out = self.relu(out) + + return out + + +@MODELS.register_module() +class ResNeSt(ResNetV1d): + """ResNeSt backbone. + + This backbone is the implementation of `ResNeSt: + Split-Attention Networks `_. + + Args: + groups (int): Number of groups of Bottleneck. Default: 1 + base_width (int): Base width of Bottleneck. Default: 4 + radix (int): Radix of SpltAtConv2d. Default: 2 + reduction_factor (int): Reduction factor of inter_channels in + SplitAttentionConv2d. Default: 4. + avg_down_stride (bool): Whether to use average pool for stride in + Bottleneck. Default: True. + kwargs (dict): Keyword arguments for ResNet. + """ + + arch_settings = { + 50: (Bottleneck, (3, 4, 6, 3)), + 101: (Bottleneck, (3, 4, 23, 3)), + 152: (Bottleneck, (3, 8, 36, 3)), + 200: (Bottleneck, (3, 24, 36, 3)) + } + + def __init__(self, + groups=1, + base_width=4, + radix=2, + reduction_factor=4, + avg_down_stride=True, + **kwargs): + self.groups = groups + self.base_width = base_width + self.radix = radix + self.reduction_factor = reduction_factor + self.avg_down_stride = avg_down_stride + super().__init__(**kwargs) + + def make_res_layer(self, **kwargs): + """Pack all blocks in a stage into a ``ResLayer``.""" + return ResLayer( + groups=self.groups, + base_width=self.base_width, + base_channels=self.base_channels, + radix=self.radix, + reduction_factor=self.reduction_factor, + avg_down_stride=self.avg_down_stride, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnet.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnet.py new file mode 100644 index 0000000..9226c90 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnet.py @@ -0,0 +1,712 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import build_conv_layer, build_norm_layer, build_plugin_layer +from mmengine.model import BaseModule +from mmengine.utils.dl_utils.parrots_wrapper import _BatchNorm + +from mmseg.registry import MODELS +from ..utils import ResLayer + + +class BasicBlock(BaseModule): + """Basic block for ResNet.""" + + expansion = 1 + + def __init__(self, + inplanes, + planes, + stride=1, + dilation=1, + downsample=None, + style='pytorch', + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + dcn=None, + plugins=None, + init_cfg=None): + super().__init__(init_cfg) + assert dcn is None, 'Not implemented yet.' + assert plugins is None, 'Not implemented yet.' + + self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1) + self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2) + + self.conv1 = build_conv_layer( + conv_cfg, + inplanes, + planes, + 3, + stride=stride, + padding=dilation, + dilation=dilation, + bias=False) + self.add_module(self.norm1_name, norm1) + self.conv2 = build_conv_layer( + conv_cfg, planes, planes, 3, padding=1, bias=False) + self.add_module(self.norm2_name, norm2) + + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + self.dilation = dilation + self.with_cp = with_cp + + @property + def norm1(self): + """nn.Module: normalization layer after the first convolution layer""" + return getattr(self, self.norm1_name) + + @property + def norm2(self): + """nn.Module: normalization layer after the second convolution layer""" + return getattr(self, self.norm2_name) + + def forward(self, x): + """Forward function.""" + + def _inner_forward(x): + identity = x + + out = self.conv1(x) + out = self.norm1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.norm2(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + out = self.relu(out) + + return out + + +class Bottleneck(BaseModule): + """Bottleneck block for ResNet. + + If style is "pytorch", the stride-two layer is the 3x3 conv layer, if it is + "caffe", the stride-two layer is the first 1x1 conv layer. + """ + + expansion = 4 + + def __init__(self, + inplanes, + planes, + stride=1, + dilation=1, + downsample=None, + style='pytorch', + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + dcn=None, + plugins=None, + init_cfg=None): + super().__init__(init_cfg) + assert style in ['pytorch', 'caffe'] + assert dcn is None or isinstance(dcn, dict) + assert plugins is None or isinstance(plugins, list) + if plugins is not None: + allowed_position = ['after_conv1', 'after_conv2', 'after_conv3'] + assert all(p['position'] in allowed_position for p in plugins) + + self.inplanes = inplanes + self.planes = planes + self.stride = stride + self.dilation = dilation + self.style = style + self.with_cp = with_cp + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.dcn = dcn + self.with_dcn = dcn is not None + self.plugins = plugins + self.with_plugins = plugins is not None + + if self.with_plugins: + # collect plugins for conv1/conv2/conv3 + self.after_conv1_plugins = [ + plugin['cfg'] for plugin in plugins + if plugin['position'] == 'after_conv1' + ] + self.after_conv2_plugins = [ + plugin['cfg'] for plugin in plugins + if plugin['position'] == 'after_conv2' + ] + self.after_conv3_plugins = [ + plugin['cfg'] for plugin in plugins + if plugin['position'] == 'after_conv3' + ] + + if self.style == 'pytorch': + self.conv1_stride = 1 + self.conv2_stride = stride + else: + self.conv1_stride = stride + self.conv2_stride = 1 + + self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1) + self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2) + self.norm3_name, norm3 = build_norm_layer( + norm_cfg, planes * self.expansion, postfix=3) + + self.conv1 = build_conv_layer( + conv_cfg, + inplanes, + planes, + kernel_size=1, + stride=self.conv1_stride, + bias=False) + self.add_module(self.norm1_name, norm1) + fallback_on_stride = False + if self.with_dcn: + fallback_on_stride = dcn.pop('fallback_on_stride', False) + if not self.with_dcn or fallback_on_stride: + self.conv2 = build_conv_layer( + conv_cfg, + planes, + planes, + kernel_size=3, + stride=self.conv2_stride, + padding=dilation, + dilation=dilation, + bias=False) + else: + assert self.conv_cfg is None, 'conv_cfg must be None for DCN' + self.conv2 = build_conv_layer( + dcn, + planes, + planes, + kernel_size=3, + stride=self.conv2_stride, + padding=dilation, + dilation=dilation, + bias=False) + + self.add_module(self.norm2_name, norm2) + self.conv3 = build_conv_layer( + conv_cfg, + planes, + planes * self.expansion, + kernel_size=1, + bias=False) + self.add_module(self.norm3_name, norm3) + + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + + if self.with_plugins: + self.after_conv1_plugin_names = self.make_block_plugins( + planes, self.after_conv1_plugins) + self.after_conv2_plugin_names = self.make_block_plugins( + planes, self.after_conv2_plugins) + self.after_conv3_plugin_names = self.make_block_plugins( + planes * self.expansion, self.after_conv3_plugins) + + def make_block_plugins(self, in_channels, plugins): + """make plugins for block. + + Args: + in_channels (int): Input channels of plugin. + plugins (list[dict]): List of plugins cfg to build. + + Returns: + list[str]: List of the names of plugin. + """ + assert isinstance(plugins, list) + plugin_names = [] + for plugin in plugins: + plugin = plugin.copy() + name, layer = build_plugin_layer( + plugin, + in_channels=in_channels, + postfix=plugin.pop('postfix', '')) + assert not hasattr(self, name), f'duplicate plugin {name}' + self.add_module(name, layer) + plugin_names.append(name) + return plugin_names + + def forward_plugin(self, x, plugin_names): + """Forward function for plugins.""" + out = x + for name in plugin_names: + out = getattr(self, name)(x) + return out + + @property + def norm1(self): + """nn.Module: normalization layer after the first convolution layer""" + return getattr(self, self.norm1_name) + + @property + def norm2(self): + """nn.Module: normalization layer after the second convolution layer""" + return getattr(self, self.norm2_name) + + @property + def norm3(self): + """nn.Module: normalization layer after the third convolution layer""" + return getattr(self, self.norm3_name) + + def forward(self, x): + """Forward function.""" + + def _inner_forward(x): + identity = x + + out = self.conv1(x) + out = self.norm1(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv1_plugin_names) + + out = self.conv2(out) + out = self.norm2(out) + out = self.relu(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv2_plugin_names) + + out = self.conv3(out) + out = self.norm3(out) + + if self.with_plugins: + out = self.forward_plugin(out, self.after_conv3_plugin_names) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + out = self.relu(out) + + return out + + +@MODELS.register_module() +class ResNet(BaseModule): + """ResNet backbone. + + This backbone is the improved implementation of `Deep Residual Learning + for Image Recognition `_. + + Args: + depth (int): Depth of resnet, from {18, 34, 50, 101, 152}. + in_channels (int): Number of input image channels. Default: 3. + stem_channels (int): Number of stem channels. Default: 64. + base_channels (int): Number of base channels of res layer. Default: 64. + num_stages (int): Resnet stages, normally 4. Default: 4. + strides (Sequence[int]): Strides of the first block of each stage. + Default: (1, 2, 2, 2). + dilations (Sequence[int]): Dilation of each stage. + Default: (1, 1, 1, 1). + out_indices (Sequence[int]): Output from which stages. + Default: (0, 1, 2, 3). + style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two + layer is the 3x3 conv layer, otherwise the stride-two layer is + the first 1x1 conv layer. Default: 'pytorch'. + deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv. + Default: False. + avg_down (bool): Use AvgPool instead of stride conv when + downsampling in the bottleneck. Default: False. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + -1 means not freezing any parameters. Default: -1. + conv_cfg (dict | None): Dictionary to construct and config conv layer. + When conv_cfg is None, cfg will be set to dict(type='Conv2d'). + Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN', requires_grad=True). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + dcn (dict | None): Dictionary to construct and config DCN conv layer. + When dcn is not None, conv_cfg must be None. Default: None. + stage_with_dcn (Sequence[bool]): Whether to set DCN conv for each + stage. The length of stage_with_dcn is equal to num_stages. + Default: (False, False, False, False). + plugins (list[dict]): List of plugins for stages, each dict contains: + + - cfg (dict, required): Cfg dict to build plugin. + + - position (str, required): Position inside block to insert plugin, + options: 'after_conv1', 'after_conv2', 'after_conv3'. + + - stages (tuple[bool], optional): Stages to apply plugin, length + should be same as 'num_stages'. + Default: None. + multi_grid (Sequence[int]|None): Multi grid dilation rates of last + stage. Default: None. + contract_dilation (bool): Whether contract first dilation of each layer + Default: False. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + zero_init_residual (bool): Whether to use zero init for last norm layer + in resblocks to let them behave as identity. Default: True. + pretrained (str, optional): model pretrained path. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + + Example: + >>> from mmseg.models import ResNet + >>> import torch + >>> self = ResNet(depth=18) + >>> self.eval() + >>> inputs = torch.rand(1, 3, 32, 32) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + (1, 64, 8, 8) + (1, 128, 4, 4) + (1, 256, 2, 2) + (1, 512, 1, 1) + """ + + arch_settings = { + 18: (BasicBlock, (2, 2, 2, 2)), + 34: (BasicBlock, (3, 4, 6, 3)), + 50: (Bottleneck, (3, 4, 6, 3)), + 101: (Bottleneck, (3, 4, 23, 3)), + 152: (Bottleneck, (3, 8, 36, 3)) + } + + def __init__(self, + depth, + in_channels=3, + stem_channels=64, + base_channels=64, + num_stages=4, + strides=(1, 2, 2, 2), + dilations=(1, 1, 1, 1), + out_indices=(0, 1, 2, 3), + style='pytorch', + deep_stem=False, + avg_down=False, + frozen_stages=-1, + conv_cfg=None, + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=False, + dcn=None, + stage_with_dcn=(False, False, False, False), + plugins=None, + multi_grid=None, + contract_dilation=False, + with_cp=False, + zero_init_residual=True, + pretrained=None, + init_cfg=None): + super().__init__(init_cfg) + if depth not in self.arch_settings: + raise KeyError(f'invalid depth {depth} for resnet') + + self.pretrained = pretrained + self.zero_init_residual = zero_init_residual + block_init_cfg = None + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be setting at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is a deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + block = self.arch_settings[depth][0] + if self.zero_init_residual: + if block is BasicBlock: + block_init_cfg = dict( + type='Constant', + val=0, + override=dict(name='norm2')) + elif block is Bottleneck: + block_init_cfg = dict( + type='Constant', + val=0, + override=dict(name='norm3')) + else: + raise TypeError('pretrained must be a str or None') + + self.depth = depth + self.stem_channels = stem_channels + self.base_channels = base_channels + self.num_stages = num_stages + assert num_stages >= 1 and num_stages <= 4 + self.strides = strides + self.dilations = dilations + assert len(strides) == len(dilations) == num_stages + self.out_indices = out_indices + assert max(out_indices) < num_stages + self.style = style + self.deep_stem = deep_stem + self.avg_down = avg_down + self.frozen_stages = frozen_stages + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.with_cp = with_cp + self.norm_eval = norm_eval + self.dcn = dcn + self.stage_with_dcn = stage_with_dcn + if dcn is not None: + assert len(stage_with_dcn) == num_stages + self.plugins = plugins + self.multi_grid = multi_grid + self.contract_dilation = contract_dilation + self.block, stage_blocks = self.arch_settings[depth] + self.stage_blocks = stage_blocks[:num_stages] + self.inplanes = stem_channels + + self._make_stem_layer(in_channels, stem_channels) + + self.res_layers = [] + for i, num_blocks in enumerate(self.stage_blocks): + stride = strides[i] + dilation = dilations[i] + dcn = self.dcn if self.stage_with_dcn[i] else None + if plugins is not None: + stage_plugins = self.make_stage_plugins(plugins, i) + else: + stage_plugins = None + # multi grid is applied to last layer only + stage_multi_grid = multi_grid if i == len( + self.stage_blocks) - 1 else None + planes = base_channels * 2**i + res_layer = self.make_res_layer( + block=self.block, + inplanes=self.inplanes, + planes=planes, + num_blocks=num_blocks, + stride=stride, + dilation=dilation, + style=self.style, + avg_down=self.avg_down, + with_cp=with_cp, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + dcn=dcn, + plugins=stage_plugins, + multi_grid=stage_multi_grid, + contract_dilation=contract_dilation, + init_cfg=block_init_cfg) + self.inplanes = planes * self.block.expansion + layer_name = f'layer{i+1}' + self.add_module(layer_name, res_layer) + self.res_layers.append(layer_name) + + self._freeze_stages() + + self.feat_dim = self.block.expansion * base_channels * 2**( + len(self.stage_blocks) - 1) + + def make_stage_plugins(self, plugins, stage_idx): + """make plugins for ResNet 'stage_idx'th stage . + + Currently we support to insert 'context_block', + 'empirical_attention_block', 'nonlocal_block' into the backbone like + ResNet/ResNeXt. They could be inserted after conv1/conv2/conv3 of + Bottleneck. + + An example of plugins format could be : + >>> plugins=[ + ... dict(cfg=dict(type='xxx', arg1='xxx'), + ... stages=(False, True, True, True), + ... position='after_conv2'), + ... dict(cfg=dict(type='yyy'), + ... stages=(True, True, True, True), + ... position='after_conv3'), + ... dict(cfg=dict(type='zzz', postfix='1'), + ... stages=(True, True, True, True), + ... position='after_conv3'), + ... dict(cfg=dict(type='zzz', postfix='2'), + ... stages=(True, True, True, True), + ... position='after_conv3') + ... ] + >>> self = ResNet(depth=18) + >>> stage_plugins = self.make_stage_plugins(plugins, 0) + >>> assert len(stage_plugins) == 3 + + Suppose 'stage_idx=0', the structure of blocks in the stage would be: + conv1-> conv2->conv3->yyy->zzz1->zzz2 + Suppose 'stage_idx=1', the structure of blocks in the stage would be: + conv1-> conv2->xxx->conv3->yyy->zzz1->zzz2 + + If stages is missing, the plugin would be applied to all stages. + + Args: + plugins (list[dict]): List of plugins cfg to build. The postfix is + required if multiple same type plugins are inserted. + stage_idx (int): Index of stage to build + + Returns: + list[dict]: Plugins for current stage + """ + stage_plugins = [] + for plugin in plugins: + plugin = plugin.copy() + stages = plugin.pop('stages', None) + assert stages is None or len(stages) == self.num_stages + # whether to insert plugin into current stage + if stages is None or stages[stage_idx]: + stage_plugins.append(plugin) + + return stage_plugins + + def make_res_layer(self, **kwargs): + """Pack all blocks in a stage into a ``ResLayer``.""" + return ResLayer(**kwargs) + + @property + def norm1(self): + """nn.Module: the normalization layer named "norm1" """ + return getattr(self, self.norm1_name) + + def _make_stem_layer(self, in_channels, stem_channels): + """Make stem layer for ResNet.""" + if self.deep_stem: + self.stem = nn.Sequential( + build_conv_layer( + self.conv_cfg, + in_channels, + stem_channels // 2, + kernel_size=3, + stride=2, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, stem_channels // 2)[1], + nn.ReLU(inplace=True), + build_conv_layer( + self.conv_cfg, + stem_channels // 2, + stem_channels // 2, + kernel_size=3, + stride=1, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, stem_channels // 2)[1], + nn.ReLU(inplace=True), + build_conv_layer( + self.conv_cfg, + stem_channels // 2, + stem_channels, + kernel_size=3, + stride=1, + padding=1, + bias=False), + build_norm_layer(self.norm_cfg, stem_channels)[1], + nn.ReLU(inplace=True)) + else: + self.conv1 = build_conv_layer( + self.conv_cfg, + in_channels, + stem_channels, + kernel_size=7, + stride=2, + padding=3, + bias=False) + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, stem_channels, postfix=1) + self.add_module(self.norm1_name, norm1) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + + def _freeze_stages(self): + """Freeze stages param and norm stats.""" + if self.frozen_stages >= 0: + if self.deep_stem: + self.stem.eval() + for param in self.stem.parameters(): + param.requires_grad = False + else: + self.norm1.eval() + for m in [self.conv1, self.norm1]: + for param in m.parameters(): + param.requires_grad = False + + for i in range(1, self.frozen_stages + 1): + m = getattr(self, f'layer{i}') + m.eval() + for param in m.parameters(): + param.requires_grad = False + + def forward(self, x): + """Forward function.""" + if self.deep_stem: + x = self.stem(x) + else: + x = self.conv1(x) + x = self.norm1(x) + x = self.relu(x) + x = self.maxpool(x) + outs = [] + for i, layer_name in enumerate(self.res_layers): + res_layer = getattr(self, layer_name) + x = res_layer(x) + if i in self.out_indices: + outs.append(x) + return tuple(outs) + + def train(self, mode=True): + """Convert the model into training mode while keep normalization layer + freezed.""" + super().train(mode) + self._freeze_stages() + if mode and self.norm_eval: + for m in self.modules(): + # trick: eval have effect on BatchNorm only + if isinstance(m, _BatchNorm): + m.eval() + + +@MODELS.register_module() +class ResNetV1c(ResNet): + """ResNetV1c variant described in [1]_. + + Compared with default ResNet(ResNetV1b), ResNetV1c replaces the 7x7 conv in + the input stem with three 3x3 convs. For more details please refer to `Bag + of Tricks for Image Classification with Convolutional Neural Networks + `_. + """ + + def __init__(self, **kwargs): + super().__init__(deep_stem=True, avg_down=False, **kwargs) + + +@MODELS.register_module() +class ResNetV1d(ResNet): + """ResNetV1d variant described in [1]_. + + Compared with default ResNet(ResNetV1b), ResNetV1d replaces the 7x7 conv in + the input stem with three 3x3 convs. And in the downsampling block, a 2x2 + avg_pool with stride 2 is added before conv, whose stride is changed to 1. + """ + + def __init__(self, **kwargs): + super().__init__(deep_stem=True, avg_down=True, **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnext.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnext.py new file mode 100644 index 0000000..67a244a --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/resnext.py @@ -0,0 +1,150 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +from mmcv.cnn import build_conv_layer, build_norm_layer + +from mmseg.registry import MODELS +from ..utils import ResLayer +from .resnet import Bottleneck as _Bottleneck +from .resnet import ResNet + + +class Bottleneck(_Bottleneck): + """Bottleneck block for ResNeXt. + + If style is "pytorch", the stride-two layer is the 3x3 conv layer, if it is + "caffe", the stride-two layer is the first 1x1 conv layer. + """ + + def __init__(self, + inplanes, + planes, + groups=1, + base_width=4, + base_channels=64, + **kwargs): + super().__init__(inplanes, planes, **kwargs) + + if groups == 1: + width = self.planes + else: + width = math.floor(self.planes * + (base_width / base_channels)) * groups + + self.norm1_name, norm1 = build_norm_layer( + self.norm_cfg, width, postfix=1) + self.norm2_name, norm2 = build_norm_layer( + self.norm_cfg, width, postfix=2) + self.norm3_name, norm3 = build_norm_layer( + self.norm_cfg, self.planes * self.expansion, postfix=3) + + self.conv1 = build_conv_layer( + self.conv_cfg, + self.inplanes, + width, + kernel_size=1, + stride=self.conv1_stride, + bias=False) + self.add_module(self.norm1_name, norm1) + fallback_on_stride = False + self.with_modulated_dcn = False + if self.with_dcn: + fallback_on_stride = self.dcn.pop('fallback_on_stride', False) + if not self.with_dcn or fallback_on_stride: + self.conv2 = build_conv_layer( + self.conv_cfg, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + bias=False) + else: + assert self.conv_cfg is None, 'conv_cfg must be None for DCN' + self.conv2 = build_conv_layer( + self.dcn, + width, + width, + kernel_size=3, + stride=self.conv2_stride, + padding=self.dilation, + dilation=self.dilation, + groups=groups, + bias=False) + + self.add_module(self.norm2_name, norm2) + self.conv3 = build_conv_layer( + self.conv_cfg, + width, + self.planes * self.expansion, + kernel_size=1, + bias=False) + self.add_module(self.norm3_name, norm3) + + +@MODELS.register_module() +class ResNeXt(ResNet): + """ResNeXt backbone. + + This backbone is the implementation of `Aggregated + Residual Transformations for Deep Neural + Networks `_. + + Args: + depth (int): Depth of resnet, from {18, 34, 50, 101, 152}. + in_channels (int): Number of input image channels. Normally 3. + num_stages (int): Resnet stages, normally 4. + groups (int): Group of resnext. + base_width (int): Base width of resnext. + strides (Sequence[int]): Strides of the first block of each stage. + dilations (Sequence[int]): Dilation of each stage. + out_indices (Sequence[int]): Output from which stages. + style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two + layer is the 3x3 conv layer, otherwise the stride-two layer is + the first 1x1 conv layer. + frozen_stages (int): Stages to be frozen (all param fixed). -1 means + not freezing any parameters. + norm_cfg (dict): dictionary to construct and config norm layer. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. + zero_init_residual (bool): whether to use zero init for last norm layer + in resblocks to let them behave as identity. + + Example: + >>> from mmseg.models import ResNeXt + >>> import torch + >>> self = ResNeXt(depth=50) + >>> self.eval() + >>> inputs = torch.rand(1, 3, 32, 32) + >>> level_outputs = self.forward(inputs) + >>> for level_out in level_outputs: + ... print(tuple(level_out.shape)) + (1, 256, 8, 8) + (1, 512, 4, 4) + (1, 1024, 2, 2) + (1, 2048, 1, 1) + """ + + arch_settings = { + 50: (Bottleneck, (3, 4, 6, 3)), + 101: (Bottleneck, (3, 4, 23, 3)), + 152: (Bottleneck, (3, 8, 36, 3)) + } + + def __init__(self, groups=1, base_width=4, **kwargs): + self.groups = groups + self.base_width = base_width + super().__init__(**kwargs) + + def make_res_layer(self, **kwargs): + """Pack all blocks in a stage into a ``ResLayer``""" + return ResLayer( + groups=self.groups, + base_width=self.base_width, + base_channels=self.base_channels, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/stdc.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/stdc.py new file mode 100644 index 0000000..758a3c9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/stdc.py @@ -0,0 +1,422 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Modified from https://github.com/MichaelFan01/STDC-Seg.""" +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule, ModuleList, Sequential + +from mmseg.registry import MODELS +from ..utils import resize +from .bisenetv1 import AttentionRefinementModule + + +class STDCModule(BaseModule): + """STDCModule. + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels before scaling. + stride (int): The number of stride for the first conv layer. + norm_cfg (dict): Config dict for normalization layer. Default: None. + act_cfg (dict): The activation config for conv layers. + num_convs (int): Numbers of conv layers. + fusion_type (str): Type of fusion operation. Default: 'add'. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + stride, + norm_cfg=None, + act_cfg=None, + num_convs=4, + fusion_type='add', + init_cfg=None): + super().__init__(init_cfg=init_cfg) + assert num_convs > 1 + assert fusion_type in ['add', 'cat'] + self.stride = stride + self.with_downsample = True if self.stride == 2 else False + self.fusion_type = fusion_type + + self.layers = ModuleList() + conv_0 = ConvModule( + in_channels, out_channels // 2, kernel_size=1, norm_cfg=norm_cfg) + + if self.with_downsample: + self.downsample = ConvModule( + out_channels // 2, + out_channels // 2, + kernel_size=3, + stride=2, + padding=1, + groups=out_channels // 2, + norm_cfg=norm_cfg, + act_cfg=None) + + if self.fusion_type == 'add': + self.layers.append(nn.Sequential(conv_0, self.downsample)) + self.skip = Sequential( + ConvModule( + in_channels, + in_channels, + kernel_size=3, + stride=2, + padding=1, + groups=in_channels, + norm_cfg=norm_cfg, + act_cfg=None), + ConvModule( + in_channels, + out_channels, + 1, + norm_cfg=norm_cfg, + act_cfg=None)) + else: + self.layers.append(conv_0) + self.skip = nn.AvgPool2d(kernel_size=3, stride=2, padding=1) + else: + self.layers.append(conv_0) + + for i in range(1, num_convs): + out_factor = 2**(i + 1) if i != num_convs - 1 else 2**i + self.layers.append( + ConvModule( + out_channels // 2**i, + out_channels // out_factor, + kernel_size=3, + stride=1, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + def forward(self, inputs): + if self.fusion_type == 'add': + out = self.forward_add(inputs) + else: + out = self.forward_cat(inputs) + return out + + def forward_add(self, inputs): + layer_outputs = [] + x = inputs.clone() + for layer in self.layers: + x = layer(x) + layer_outputs.append(x) + if self.with_downsample: + inputs = self.skip(inputs) + + return torch.cat(layer_outputs, dim=1) + inputs + + def forward_cat(self, inputs): + x0 = self.layers[0](inputs) + layer_outputs = [x0] + for i, layer in enumerate(self.layers[1:]): + if i == 0: + if self.with_downsample: + x = layer(self.downsample(x0)) + else: + x = layer(x0) + else: + x = layer(x) + layer_outputs.append(x) + if self.with_downsample: + layer_outputs[0] = self.skip(x0) + return torch.cat(layer_outputs, dim=1) + + +class FeatureFusionModule(BaseModule): + """Feature Fusion Module. This module is different from FeatureFusionModule + in BiSeNetV1. It uses two ConvModules in `self.attention` whose inter + channel number is calculated by given `scale_factor`, while + FeatureFusionModule in BiSeNetV1 only uses one ConvModule in + `self.conv_atten`. + + Args: + in_channels (int): The number of input channels. + out_channels (int): The number of output channels. + scale_factor (int): The number of channel scale factor. + Default: 4. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): The activation config for conv layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + scale_factor=4, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + channels = out_channels // scale_factor + self.conv0 = ConvModule( + in_channels, out_channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.attention = nn.Sequential( + nn.AdaptiveAvgPool2d((1, 1)), + ConvModule( + out_channels, + channels, + 1, + norm_cfg=None, + bias=False, + act_cfg=act_cfg), + ConvModule( + channels, + out_channels, + 1, + norm_cfg=None, + bias=False, + act_cfg=None), nn.Sigmoid()) + + def forward(self, spatial_inputs, context_inputs): + inputs = torch.cat([spatial_inputs, context_inputs], dim=1) + x = self.conv0(inputs) + attn = self.attention(x) + x_attn = x * attn + return x_attn + x + + +@MODELS.register_module() +class STDCNet(BaseModule): + """This backbone is the implementation of `Rethinking BiSeNet For Real-time + Semantic Segmentation `_. + + Args: + stdc_type (int): The type of backbone structure, + `STDCNet1` and`STDCNet2` denotes two main backbones in paper, + whose FLOPs is 813M and 1446M, respectively. + in_channels (int): The num of input_channels. + channels (tuple[int]): The output channels for each stage. + bottleneck_type (str): The type of STDC Module type, the value must + be 'add' or 'cat'. + norm_cfg (dict): Config dict for normalization layer. + act_cfg (dict): The activation config for conv layers. + num_convs (int): Numbers of conv layer at each STDC Module. + Default: 4. + with_final_conv (bool): Whether add a conv layer at the Module output. + Default: True. + pretrained (str, optional): Model pretrained path. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + + Example: + >>> import torch + >>> stdc_type = 'STDCNet1' + >>> in_channels = 3 + >>> channels = (32, 64, 256, 512, 1024) + >>> bottleneck_type = 'cat' + >>> inputs = torch.rand(1, 3, 1024, 2048) + >>> self = STDCNet(stdc_type, in_channels, + ... channels, bottleneck_type).eval() + >>> outputs = self.forward(inputs) + >>> for i in range(len(outputs)): + ... print(f'outputs[{i}].shape = {outputs[i].shape}') + outputs[0].shape = torch.Size([1, 256, 128, 256]) + outputs[1].shape = torch.Size([1, 512, 64, 128]) + outputs[2].shape = torch.Size([1, 1024, 32, 64]) + """ + + arch_settings = { + 'STDCNet1': [(2, 1), (2, 1), (2, 1)], + 'STDCNet2': [(2, 1, 1, 1), (2, 1, 1, 1, 1), (2, 1, 1)] + } + + def __init__(self, + stdc_type, + in_channels, + channels, + bottleneck_type, + norm_cfg, + act_cfg, + num_convs=4, + with_final_conv=False, + pretrained=None, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + assert stdc_type in self.arch_settings, \ + f'invalid structure {stdc_type} for STDCNet.' + assert bottleneck_type in ['add', 'cat'],\ + f'bottleneck_type must be `add` or `cat`, got {bottleneck_type}' + + assert len(channels) == 5,\ + f'invalid channels length {len(channels)} for STDCNet.' + + self.in_channels = in_channels + self.channels = channels + self.stage_strides = self.arch_settings[stdc_type] + self.prtrained = pretrained + self.num_convs = num_convs + self.with_final_conv = with_final_conv + + self.stages = ModuleList([ + ConvModule( + self.in_channels, + self.channels[0], + kernel_size=3, + stride=2, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg), + ConvModule( + self.channels[0], + self.channels[1], + kernel_size=3, + stride=2, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + ]) + # `self.num_shallow_features` is the number of shallow modules in + # `STDCNet`, which is noted as `Stage1` and `Stage2` in original paper. + # They are both not used for following modules like Attention + # Refinement Module and Feature Fusion Module. + # Thus they would be cut from `outs`. Please refer to Figure 4 + # of original paper for more details. + self.num_shallow_features = len(self.stages) + + for strides in self.stage_strides: + idx = len(self.stages) - 1 + self.stages.append( + self._make_stage(self.channels[idx], self.channels[idx + 1], + strides, norm_cfg, act_cfg, bottleneck_type)) + # After appending, `self.stages` is a ModuleList including several + # shallow modules and STDCModules. + # (len(self.stages) == + # self.num_shallow_features + len(self.stage_strides)) + if self.with_final_conv: + self.final_conv = ConvModule( + self.channels[-1], + max(1024, self.channels[-1]), + 1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def _make_stage(self, in_channels, out_channels, strides, norm_cfg, + act_cfg, bottleneck_type): + layers = [] + for i, stride in enumerate(strides): + layers.append( + STDCModule( + in_channels if i == 0 else out_channels, + out_channels, + stride, + norm_cfg, + act_cfg, + num_convs=self.num_convs, + fusion_type=bottleneck_type)) + return Sequential(*layers) + + def forward(self, x): + outs = [] + for stage in self.stages: + x = stage(x) + outs.append(x) + if self.with_final_conv: + outs[-1] = self.final_conv(outs[-1]) + outs = outs[self.num_shallow_features:] + return tuple(outs) + + +@MODELS.register_module() +class STDCContextPathNet(BaseModule): + """STDCNet with Context Path. The `outs` below is a list of three feature + maps from deep to shallow, whose height and width is from small to big, + respectively. The biggest feature map of `outs` is outputted for + `STDCHead`, where Detail Loss would be calculated by Detail Ground-truth. + The other two feature maps are used for Attention Refinement Module, + respectively. Besides, the biggest feature map of `outs` and the last + output of Attention Refinement Module are concatenated for Feature Fusion + Module. Then, this fusion feature map `feat_fuse` would be outputted for + `decode_head`. More details please refer to Figure 4 of original paper. + + Args: + backbone_cfg (dict): Config dict for stdc backbone. + last_in_channels (tuple(int)), The number of channels of last + two feature maps from stdc backbone. Default: (1024, 512). + out_channels (int): The channels of output feature maps. + Default: 128. + ffm_cfg (dict): Config dict for Feature Fusion Module. Default: + `dict(in_channels=512, out_channels=256, scale_factor=4)`. + upsample_mode (str): Algorithm used for upsampling: + ``'nearest'`` | ``'linear'`` | ``'bilinear'`` | ``'bicubic'`` | + ``'trilinear'``. Default: ``'nearest'``. + align_corners (str): align_corners argument of F.interpolate. It + must be `None` if upsample_mode is ``'nearest'``. Default: None. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + + Return: + outputs (tuple): The tuple of list of output feature map for + auxiliary heads and decoder head. + """ + + def __init__(self, + backbone_cfg, + last_in_channels=(1024, 512), + out_channels=128, + ffm_cfg=dict( + in_channels=512, out_channels=256, scale_factor=4), + upsample_mode='nearest', + align_corners=None, + norm_cfg=dict(type='BN'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.backbone = MODELS.build(backbone_cfg) + self.arms = ModuleList() + self.convs = ModuleList() + for channels in last_in_channels: + self.arms.append(AttentionRefinementModule(channels, out_channels)) + self.convs.append( + ConvModule( + out_channels, + out_channels, + 3, + padding=1, + norm_cfg=norm_cfg)) + self.conv_avg = ConvModule( + last_in_channels[0], out_channels, 1, norm_cfg=norm_cfg) + + self.ffm = FeatureFusionModule(**ffm_cfg) + + self.upsample_mode = upsample_mode + self.align_corners = align_corners + + def forward(self, x): + outs = list(self.backbone(x)) + avg = F.adaptive_avg_pool2d(outs[-1], 1) + avg_feat = self.conv_avg(avg) + + feature_up = resize( + avg_feat, + size=outs[-1].shape[2:], + mode=self.upsample_mode, + align_corners=self.align_corners) + arms_out = [] + for i in range(len(self.arms)): + x_arm = self.arms[i](outs[len(outs) - 1 - i]) + feature_up + feature_up = resize( + x_arm, + size=outs[len(outs) - 1 - i - 1].shape[2:], + mode=self.upsample_mode, + align_corners=self.align_corners) + feature_up = self.convs[i](feature_up) + arms_out.append(feature_up) + + feat_fuse = self.ffm(outs[0], arms_out[1]) + + # The `outputs` has four feature maps. + # `outs[0]` is outputted for `STDCHead` auxiliary head. + # Two feature maps of `arms_out` are outputted for auxiliary head. + # `feat_fuse` is outputted for decoder head. + outputs = [outs[0]] + list(arms_out) + [feat_fuse] + return tuple(outputs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/swin.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/swin.py new file mode 100644 index 0000000..67b28a9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/swin.py @@ -0,0 +1,757 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from collections import OrderedDict +from copy import deepcopy + +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.utils.checkpoint as cp +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN, build_dropout +from mmengine.logging import print_log +from mmengine.model import BaseModule, ModuleList +from mmengine.model.weight_init import (constant_init, trunc_normal_, + trunc_normal_init) +from mmengine.runner import CheckpointLoader +from mmengine.utils import to_2tuple + +from mmseg.registry import MODELS +from ..utils.embed import PatchEmbed, PatchMerging + + +class WindowMSA(BaseModule): + """Window based multi-head self-attention (W-MSA) module with relative + position bias. + + Args: + embed_dims (int): Number of input channels. + num_heads (int): Number of attention heads. + window_size (tuple[int]): The height and width of the window. + qkv_bias (bool, optional): If True, add a learnable bias to q, k, v. + Default: True. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + attn_drop_rate (float, optional): Dropout ratio of attention weight. + Default: 0.0 + proj_drop_rate (float, optional): Dropout ratio of output. Default: 0. + init_cfg (dict | None, optional): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + window_size, + qkv_bias=True, + qk_scale=None, + attn_drop_rate=0., + proj_drop_rate=0., + init_cfg=None): + + super().__init__(init_cfg=init_cfg) + self.embed_dims = embed_dims + self.window_size = window_size # Wh, Ww + self.num_heads = num_heads + head_embed_dims = embed_dims // num_heads + self.scale = qk_scale or head_embed_dims**-0.5 + + # define a parameter table of relative position bias + self.relative_position_bias_table = nn.Parameter( + torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), + num_heads)) # 2*Wh-1 * 2*Ww-1, nH + + # About 2x faster than original impl + Wh, Ww = self.window_size + rel_index_coords = self.double_step_seq(2 * Ww - 1, Wh, 1, Ww) + rel_position_index = rel_index_coords + rel_index_coords.T + rel_position_index = rel_position_index.flip(1).contiguous() + self.register_buffer('relative_position_index', rel_position_index) + + self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop_rate) + self.proj = nn.Linear(embed_dims, embed_dims) + self.proj_drop = nn.Dropout(proj_drop_rate) + + self.softmax = nn.Softmax(dim=-1) + + def init_weights(self): + trunc_normal_(self.relative_position_bias_table, std=0.02) + + def forward(self, x, mask=None): + """ + Args: + + x (tensor): input features with shape of (num_windows*B, N, C) + mask (tensor | None, Optional): mask with shape of (num_windows, + Wh*Ww, Wh*Ww), value should be between (-inf, 0]. + """ + B, N, C = x.shape + qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, + C // self.num_heads).permute(2, 0, 3, 1, 4) + # make torchscript happy (cannot use tensor as tuple) + q, k, v = qkv[0], qkv[1], qkv[2] + + q = q * self.scale + attn = (q @ k.transpose(-2, -1)) + + relative_position_bias = self.relative_position_bias_table[ + self.relative_position_index.view(-1)].view( + self.window_size[0] * self.window_size[1], + self.window_size[0] * self.window_size[1], + -1) # Wh*Ww,Wh*Ww,nH + relative_position_bias = relative_position_bias.permute( + 2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww + attn = attn + relative_position_bias.unsqueeze(0) + + if mask is not None: + nW = mask.shape[0] + attn = attn.view(B // nW, nW, self.num_heads, N, + N) + mask.unsqueeze(1).unsqueeze(0) + attn = attn.view(-1, self.num_heads, N, N) + attn = self.softmax(attn) + + attn = self.attn_drop(attn) + + x = (attn @ v).transpose(1, 2).reshape(B, N, C) + x = self.proj(x) + x = self.proj_drop(x) + return x + + @staticmethod + def double_step_seq(step1, len1, step2, len2): + seq1 = torch.arange(0, step1 * len1, step1) + seq2 = torch.arange(0, step2 * len2, step2) + return (seq1[:, None] + seq2[None, :]).reshape(1, -1) + + +class ShiftWindowMSA(BaseModule): + """Shifted Window Multihead Self-Attention Module. + + Args: + embed_dims (int): Number of input channels. + num_heads (int): Number of attention heads. + window_size (int): The height and width of the window. + shift_size (int, optional): The shift step of each window towards + right-bottom. If zero, act as regular window-msa. Defaults to 0. + qkv_bias (bool, optional): If True, add a learnable bias to q, k, v. + Default: True + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Defaults: None. + attn_drop_rate (float, optional): Dropout ratio of attention weight. + Defaults: 0. + proj_drop_rate (float, optional): Dropout ratio of output. + Defaults: 0. + dropout_layer (dict, optional): The dropout_layer used before output. + Defaults: dict(type='DropPath', drop_prob=0.). + init_cfg (dict, optional): The extra config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + window_size, + shift_size=0, + qkv_bias=True, + qk_scale=None, + attn_drop_rate=0, + proj_drop_rate=0, + dropout_layer=dict(type='DropPath', drop_prob=0.), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.window_size = window_size + self.shift_size = shift_size + assert 0 <= self.shift_size < self.window_size + + self.w_msa = WindowMSA( + embed_dims=embed_dims, + num_heads=num_heads, + window_size=to_2tuple(window_size), + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop_rate=attn_drop_rate, + proj_drop_rate=proj_drop_rate, + init_cfg=None) + + self.drop = build_dropout(dropout_layer) + + def forward(self, query, hw_shape): + B, L, C = query.shape + H, W = hw_shape + assert L == H * W, 'input feature has wrong size' + query = query.view(B, H, W, C) + + # pad feature maps to multiples of window size + pad_r = (self.window_size - W % self.window_size) % self.window_size + pad_b = (self.window_size - H % self.window_size) % self.window_size + query = F.pad(query, (0, 0, 0, pad_r, 0, pad_b)) + H_pad, W_pad = query.shape[1], query.shape[2] + + # cyclic shift + if self.shift_size > 0: + shifted_query = torch.roll( + query, + shifts=(-self.shift_size, -self.shift_size), + dims=(1, 2)) + + # calculate attention mask for SW-MSA + img_mask = torch.zeros((1, H_pad, W_pad, 1), device=query.device) + h_slices = (slice(0, -self.window_size), + slice(-self.window_size, + -self.shift_size), slice(-self.shift_size, None)) + w_slices = (slice(0, -self.window_size), + slice(-self.window_size, + -self.shift_size), slice(-self.shift_size, None)) + cnt = 0 + for h in h_slices: + for w in w_slices: + img_mask[:, h, w, :] = cnt + cnt += 1 + + # nW, window_size, window_size, 1 + mask_windows = self.window_partition(img_mask) + mask_windows = mask_windows.view( + -1, self.window_size * self.window_size) + attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) + attn_mask = attn_mask.masked_fill(attn_mask != 0, + float(-100.0)).masked_fill( + attn_mask == 0, float(0.0)) + else: + shifted_query = query + attn_mask = None + + # nW*B, window_size, window_size, C + query_windows = self.window_partition(shifted_query) + # nW*B, window_size*window_size, C + query_windows = query_windows.view(-1, self.window_size**2, C) + + # W-MSA/SW-MSA (nW*B, window_size*window_size, C) + attn_windows = self.w_msa(query_windows, mask=attn_mask) + + # merge windows + attn_windows = attn_windows.view(-1, self.window_size, + self.window_size, C) + + # B H' W' C + shifted_x = self.window_reverse(attn_windows, H_pad, W_pad) + # reverse cyclic shift + if self.shift_size > 0: + x = torch.roll( + shifted_x, + shifts=(self.shift_size, self.shift_size), + dims=(1, 2)) + else: + x = shifted_x + + if pad_r > 0 or pad_b: + x = x[:, :H, :W, :].contiguous() + + x = x.view(B, H * W, C) + + x = self.drop(x) + return x + + def window_reverse(self, windows, H, W): + """ + Args: + windows: (num_windows*B, window_size, window_size, C) + H (int): Height of image + W (int): Width of image + Returns: + x: (B, H, W, C) + """ + window_size = self.window_size + B = int(windows.shape[0] / (H * W / window_size / window_size)) + x = windows.view(B, H // window_size, W // window_size, window_size, + window_size, -1) + x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1) + return x + + def window_partition(self, x): + """ + Args: + x: (B, H, W, C) + Returns: + windows: (num_windows*B, window_size, window_size, C) + """ + B, H, W, C = x.shape + window_size = self.window_size + x = x.view(B, H // window_size, window_size, W // window_size, + window_size, C) + windows = x.permute(0, 1, 3, 2, 4, 5).contiguous() + windows = windows.view(-1, window_size, window_size, C) + return windows + + +class SwinBlock(BaseModule): + """" + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + window_size (int, optional): The local window scale. Default: 7. + shift (bool, optional): whether to shift window or not. Default False. + qkv_bias (bool, optional): enable bias for qkv if True. Default: True. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + drop_rate (float, optional): Dropout rate. Default: 0. + attn_drop_rate (float, optional): Attention dropout rate. Default: 0. + drop_path_rate (float, optional): Stochastic depth rate. Default: 0. + act_cfg (dict, optional): The config dict of activation function. + Default: dict(type='GELU'). + norm_cfg (dict, optional): The config dict of normalization. + Default: dict(type='LN'). + with_cp (bool, optional): Use checkpoint or not. Using checkpoint + will save some memory while slowing down the training speed. + Default: False. + init_cfg (dict | list | None, optional): The init config. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + window_size=7, + shift=False, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + with_cp=False, + init_cfg=None): + + super().__init__(init_cfg=init_cfg) + + self.with_cp = with_cp + + self.norm1 = build_norm_layer(norm_cfg, embed_dims)[1] + self.attn = ShiftWindowMSA( + embed_dims=embed_dims, + num_heads=num_heads, + window_size=window_size, + shift_size=window_size // 2 if shift else 0, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop_rate=attn_drop_rate, + proj_drop_rate=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + init_cfg=None) + + self.norm2 = build_norm_layer(norm_cfg, embed_dims)[1] + self.ffn = FFN( + embed_dims=embed_dims, + feedforward_channels=feedforward_channels, + num_fcs=2, + ffn_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + act_cfg=act_cfg, + add_identity=True, + init_cfg=None) + + def forward(self, x, hw_shape): + + def _inner_forward(x): + identity = x + x = self.norm1(x) + x = self.attn(x, hw_shape) + + x = x + identity + + identity = x + x = self.norm2(x) + x = self.ffn(x, identity=identity) + + return x + + if self.with_cp and x.requires_grad: + x = cp.checkpoint(_inner_forward, x) + else: + x = _inner_forward(x) + + return x + + +class SwinBlockSequence(BaseModule): + """Implements one stage in Swin Transformer. + + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + depth (int): The number of blocks in this stage. + window_size (int, optional): The local window scale. Default: 7. + qkv_bias (bool, optional): enable bias for qkv if True. Default: True. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + drop_rate (float, optional): Dropout rate. Default: 0. + attn_drop_rate (float, optional): Attention dropout rate. Default: 0. + drop_path_rate (float | list[float], optional): Stochastic depth + rate. Default: 0. + downsample (BaseModule | None, optional): The downsample operation + module. Default: None. + act_cfg (dict, optional): The config dict of activation function. + Default: dict(type='GELU'). + norm_cfg (dict, optional): The config dict of normalization. + Default: dict(type='LN'). + with_cp (bool, optional): Use checkpoint or not. Using checkpoint + will save some memory while slowing down the training speed. + Default: False. + init_cfg (dict | list | None, optional): The init config. + Default: None. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + depth, + window_size=7, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + downsample=None, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + with_cp=False, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + if isinstance(drop_path_rate, list): + drop_path_rates = drop_path_rate + assert len(drop_path_rates) == depth + else: + drop_path_rates = [deepcopy(drop_path_rate) for _ in range(depth)] + + self.blocks = ModuleList() + for i in range(depth): + block = SwinBlock( + embed_dims=embed_dims, + num_heads=num_heads, + feedforward_channels=feedforward_channels, + window_size=window_size, + shift=False if i % 2 == 0 else True, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop_rate=drop_rate, + attn_drop_rate=attn_drop_rate, + drop_path_rate=drop_path_rates[i], + act_cfg=act_cfg, + norm_cfg=norm_cfg, + with_cp=with_cp, + init_cfg=None) + self.blocks.append(block) + + self.downsample = downsample + + def forward(self, x, hw_shape): + for block in self.blocks: + x = block(x, hw_shape) + + if self.downsample: + x_down, down_hw_shape = self.downsample(x, hw_shape) + return x_down, down_hw_shape, x, hw_shape + else: + return x, hw_shape, x, hw_shape + + +@MODELS.register_module() +class SwinTransformer(BaseModule): + """Swin Transformer backbone. + + This backbone is the implementation of `Swin Transformer: + Hierarchical Vision Transformer using Shifted + Windows `_. + Inspiration from https://github.com/microsoft/Swin-Transformer. + + Args: + pretrain_img_size (int | tuple[int]): The size of input image when + pretrain. Defaults: 224. + in_channels (int): The num of input channels. + Defaults: 3. + embed_dims (int): The feature dimension. Default: 96. + patch_size (int | tuple[int]): Patch size. Default: 4. + window_size (int): Window size. Default: 7. + mlp_ratio (int | float): Ratio of mlp hidden dim to embedding dim. + Default: 4. + depths (tuple[int]): Depths of each Swin Transformer stage. + Default: (2, 2, 6, 2). + num_heads (tuple[int]): Parallel attention heads of each Swin + Transformer stage. Default: (3, 6, 12, 24). + strides (tuple[int]): The patch merging or patch embedding stride of + each Swin Transformer stage. (In swin, we set kernel size equal to + stride.) Default: (4, 2, 2, 2). + out_indices (tuple[int]): Output from which stages. + Default: (0, 1, 2, 3). + qkv_bias (bool, optional): If True, add a learnable bias to query, key, + value. Default: True + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + patch_norm (bool): If add a norm layer for patch embed and patch + merging. Default: True. + drop_rate (float): Dropout rate. Defaults: 0. + attn_drop_rate (float): Attention dropout rate. Default: 0. + drop_path_rate (float): Stochastic depth rate. Defaults: 0.1. + use_abs_pos_embed (bool): If True, add absolute position embedding to + the patch embedding. Defaults: False. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='LN'). + norm_cfg (dict): Config dict for normalization layer at + output of backone. Defaults: dict(type='LN'). + with_cp (bool, optional): Use checkpoint or not. Using checkpoint + will save some memory while slowing down the training speed. + Default: False. + pretrained (str, optional): model pretrained path. Default: None. + frozen_stages (int): Stages to be frozen (stop grad and set eval mode). + -1 means not freezing any parameters. + init_cfg (dict, optional): The Config for initialization. + Defaults to None. + """ + + def __init__(self, + pretrain_img_size=224, + in_channels=3, + embed_dims=96, + patch_size=4, + window_size=7, + mlp_ratio=4, + depths=(2, 2, 6, 2), + num_heads=(3, 6, 12, 24), + strides=(4, 2, 2, 2), + out_indices=(0, 1, 2, 3), + qkv_bias=True, + qk_scale=None, + patch_norm=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.1, + use_abs_pos_embed=False, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + with_cp=False, + pretrained=None, + frozen_stages=-1, + init_cfg=None): + self.frozen_stages = frozen_stages + + if isinstance(pretrain_img_size, int): + pretrain_img_size = to_2tuple(pretrain_img_size) + elif isinstance(pretrain_img_size, tuple): + if len(pretrain_img_size) == 1: + pretrain_img_size = to_2tuple(pretrain_img_size[0]) + assert len(pretrain_img_size) == 2, \ + f'The size of image should have length 1 or 2, ' \ + f'but got {len(pretrain_img_size)}' + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be specified at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + init_cfg = init_cfg + else: + raise TypeError('pretrained must be a str or None') + + super().__init__(init_cfg=init_cfg) + + num_layers = len(depths) + self.out_indices = out_indices + self.use_abs_pos_embed = use_abs_pos_embed + + assert strides[0] == patch_size, 'Use non-overlapping patch embed.' + + self.patch_embed = PatchEmbed( + in_channels=in_channels, + embed_dims=embed_dims, + conv_type='Conv2d', + kernel_size=patch_size, + stride=strides[0], + padding='corner', + norm_cfg=norm_cfg if patch_norm else None, + init_cfg=None) + + if self.use_abs_pos_embed: + patch_row = pretrain_img_size[0] // patch_size + patch_col = pretrain_img_size[1] // patch_size + num_patches = patch_row * patch_col + self.absolute_pos_embed = nn.Parameter( + torch.zeros((1, num_patches, embed_dims))) + + self.drop_after_pos = nn.Dropout(p=drop_rate) + + # set stochastic depth decay rule + total_depth = sum(depths) + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, total_depth) + ] + + self.stages = ModuleList() + in_channels = embed_dims + for i in range(num_layers): + if i < num_layers - 1: + downsample = PatchMerging( + in_channels=in_channels, + out_channels=2 * in_channels, + stride=strides[i + 1], + norm_cfg=norm_cfg if patch_norm else None, + init_cfg=None) + else: + downsample = None + + stage = SwinBlockSequence( + embed_dims=in_channels, + num_heads=num_heads[i], + feedforward_channels=int(mlp_ratio * in_channels), + depth=depths[i], + window_size=window_size, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop_rate=drop_rate, + attn_drop_rate=attn_drop_rate, + drop_path_rate=dpr[sum(depths[:i]):sum(depths[:i + 1])], + downsample=downsample, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + with_cp=with_cp, + init_cfg=None) + self.stages.append(stage) + if downsample: + in_channels = downsample.out_channels + + self.num_features = [int(embed_dims * 2**i) for i in range(num_layers)] + # Add a norm layer for each output + for i in out_indices: + layer = build_norm_layer(norm_cfg, self.num_features[i])[1] + layer_name = f'norm{i}' + self.add_module(layer_name, layer) + + def train(self, mode=True): + """Convert the model into training mode while keep layers freezed.""" + super().train(mode) + self._freeze_stages() + + def _freeze_stages(self): + if self.frozen_stages >= 0: + self.patch_embed.eval() + for param in self.patch_embed.parameters(): + param.requires_grad = False + if self.use_abs_pos_embed: + self.absolute_pos_embed.requires_grad = False + self.drop_after_pos.eval() + + for i in range(1, self.frozen_stages + 1): + + if (i - 1) in self.out_indices: + norm_layer = getattr(self, f'norm{i-1}') + norm_layer.eval() + for param in norm_layer.parameters(): + param.requires_grad = False + + m = self.stages[i - 1] + m.eval() + for param in m.parameters(): + param.requires_grad = False + + def init_weights(self): + if self.init_cfg is None: + print_log(f'No pre-trained weights for ' + f'{self.__class__.__name__}, ' + f'training start from scratch') + if self.use_abs_pos_embed: + trunc_normal_(self.absolute_pos_embed, std=0.02) + for m in self.modules(): + if isinstance(m, nn.Linear): + trunc_normal_init(m, std=.02, bias=0.) + elif isinstance(m, nn.LayerNorm): + constant_init(m, val=1.0, bias=0.) + else: + assert 'checkpoint' in self.init_cfg, f'Only support ' \ + f'specify `Pretrained` in ' \ + f'`init_cfg` in ' \ + f'{self.__class__.__name__} ' + ckpt = CheckpointLoader.load_checkpoint( + self.init_cfg['checkpoint'], logger=None, map_location='cpu') + if 'state_dict' in ckpt: + _state_dict = ckpt['state_dict'] + elif 'model' in ckpt: + _state_dict = ckpt['model'] + else: + _state_dict = ckpt + + state_dict = OrderedDict() + for k, v in _state_dict.items(): + if k.startswith('backbone.'): + state_dict[k[9:]] = v + else: + state_dict[k] = v + + # strip prefix of state_dict + if list(state_dict.keys())[0].startswith('module.'): + state_dict = {k[7:]: v for k, v in state_dict.items()} + + # reshape absolute position embedding + if state_dict.get('absolute_pos_embed') is not None: + absolute_pos_embed = state_dict['absolute_pos_embed'] + N1, L, C1 = absolute_pos_embed.size() + N2, C2, H, W = self.absolute_pos_embed.size() + if N1 != N2 or C1 != C2 or L != H * W: + print_log('Error in loading absolute_pos_embed, pass') + else: + state_dict['absolute_pos_embed'] = absolute_pos_embed.view( + N2, H, W, C2).permute(0, 3, 1, 2).contiguous() + + # interpolate position bias table if needed + relative_position_bias_table_keys = [ + k for k in state_dict.keys() + if 'relative_position_bias_table' in k + ] + for table_key in relative_position_bias_table_keys: + table_pretrained = state_dict[table_key] + if table_key in self.state_dict(): + table_current = self.state_dict()[table_key] + L1, nH1 = table_pretrained.size() + L2, nH2 = table_current.size() + if nH1 != nH2: + print_log(f'Error in loading {table_key}, pass') + elif L1 != L2: + S1 = int(L1**0.5) + S2 = int(L2**0.5) + table_pretrained_resized = F.interpolate( + table_pretrained.permute(1, 0).reshape( + 1, nH1, S1, S1), + size=(S2, S2), + mode='bicubic') + state_dict[table_key] = table_pretrained_resized.view( + nH2, L2).permute(1, 0).contiguous() + + # load state_dict + self.load_state_dict(state_dict, strict=False) + + def forward(self, x): + x, hw_shape = self.patch_embed(x) + + if self.use_abs_pos_embed: + x = x + self.absolute_pos_embed + x = self.drop_after_pos(x) + + outs = [] + for i, stage in enumerate(self.stages): + x, hw_shape, out, out_hw_shape = stage(x, hw_shape) + if i in self.out_indices: + norm_layer = getattr(self, f'norm{i}') + out = norm_layer(out) + out = out.view(-1, *out_hw_shape, + self.num_features[i]).permute(0, 3, 1, + 2).contiguous() + outs.append(out) + + return outs diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/timm_backbone.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/timm_backbone.py new file mode 100644 index 0000000..1eef302 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/timm_backbone.py @@ -0,0 +1,63 @@ +# Copyright (c) OpenMMLab. All rights reserved. +try: + import timm +except ImportError: + timm = None + +from mmengine.model import BaseModule +from mmengine.registry import MODELS as MMENGINE_MODELS + +from mmseg.registry import MODELS + + +@MODELS.register_module() +class TIMMBackbone(BaseModule): + """Wrapper to use backbones from timm library. More details can be found in + `timm `_ . + + Args: + model_name (str): Name of timm model to instantiate. + pretrained (bool): Load pretrained weights if True. + checkpoint_path (str): Path of checkpoint to load after + model is initialized. + in_channels (int): Number of input image channels. Default: 3. + init_cfg (dict, optional): Initialization config dict + **kwargs: Other timm & model specific arguments. + """ + + def __init__( + self, + model_name, + features_only=True, + pretrained=True, + checkpoint_path='', + in_channels=3, + init_cfg=None, + **kwargs, + ): + if timm is None: + raise RuntimeError('timm is not installed') + super().__init__(init_cfg) + if 'norm_layer' in kwargs: + kwargs['norm_layer'] = MMENGINE_MODELS.get(kwargs['norm_layer']) + self.timm_model = timm.create_model( + model_name=model_name, + features_only=features_only, + pretrained=pretrained, + in_chans=in_channels, + checkpoint_path=checkpoint_path, + **kwargs, + ) + + # Make unused parameters None + self.timm_model.global_pool = None + self.timm_model.fc = None + self.timm_model.classifier = None + + # Hack to use pretrained weights from timm + if pretrained or checkpoint_path: + self._is_init = True + + def forward(self, x): + features = self.timm_model(x) + return features diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/twins.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/twins.py new file mode 100644 index 0000000..b6a6eea --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/twins.py @@ -0,0 +1,588 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import warnings + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.drop import build_dropout +from mmcv.cnn.bricks.transformer import FFN +from mmengine.model import BaseModule, ModuleList +from mmengine.model.weight_init import (constant_init, normal_init, + trunc_normal_init) +from torch.nn.modules.batchnorm import _BatchNorm + +from mmseg.models.backbones.mit import EfficientMultiheadAttention +from mmseg.registry import MODELS +from ..utils.embed import PatchEmbed + + +class GlobalSubsampledAttention(EfficientMultiheadAttention): + """Global Sub-sampled Attention (Spatial Reduction Attention) + + This module is modified from EfficientMultiheadAttention, + which is a module from mmseg.models.backbones.mit.py. + Specifically, there is no difference between + `GlobalSubsampledAttention` and `EfficientMultiheadAttention`, + `GlobalSubsampledAttention` is built as a brand new class + because it is renamed as `Global sub-sampled attention (GSA)` + in paper. + + + Args: + embed_dims (int): The embedding dimension. + num_heads (int): Parallel attention heads. + attn_drop (float): A Dropout layer on attn_output_weights. + Default: 0.0. + proj_drop (float): A Dropout layer after `nn.MultiheadAttention`. + Default: 0.0. + dropout_layer (obj:`ConfigDict`): The dropout_layer used + when adding the shortcut. Default: None. + batch_first (bool): Key, Query and Value are shape of + (batch, n, embed_dims) + or (n, batch, embed_dims). Default: False. + qkv_bias (bool): enable bias for qkv if True. Default: True. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + sr_ratio (int): The ratio of spatial reduction of GSA of PCPVT. + Default: 1. + init_cfg (dict, optional): The Config for initialization. + Defaults to None. + """ + + def __init__(self, + embed_dims, + num_heads, + attn_drop=0., + proj_drop=0., + dropout_layer=None, + batch_first=True, + qkv_bias=True, + norm_cfg=dict(type='LN'), + sr_ratio=1, + init_cfg=None): + super().__init__( + embed_dims, + num_heads, + attn_drop=attn_drop, + proj_drop=proj_drop, + dropout_layer=dropout_layer, + batch_first=batch_first, + qkv_bias=qkv_bias, + norm_cfg=norm_cfg, + sr_ratio=sr_ratio, + init_cfg=init_cfg) + + +class GSAEncoderLayer(BaseModule): + """Implements one encoder layer with GSA. + + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + drop_rate (float): Probability of an element to be zeroed + after the feed forward layer. Default: 0.0. + attn_drop_rate (float): The drop out rate for attention layer. + Default: 0.0. + drop_path_rate (float): Stochastic depth rate. Default 0.0. + num_fcs (int): The number of fully-connected layers for FFNs. + Default: 2. + qkv_bias (bool): Enable bias for qkv if True. Default: True + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + sr_ratio (float): Kernel_size of conv in Attention modules. Default: 1. + init_cfg (dict, optional): The Config for initialization. + Defaults to None. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + num_fcs=2, + qkv_bias=True, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + sr_ratio=1., + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.norm1 = build_norm_layer(norm_cfg, embed_dims, postfix=1)[1] + self.attn = GlobalSubsampledAttention( + embed_dims=embed_dims, + num_heads=num_heads, + attn_drop=attn_drop_rate, + proj_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + qkv_bias=qkv_bias, + norm_cfg=norm_cfg, + sr_ratio=sr_ratio) + + self.norm2 = build_norm_layer(norm_cfg, embed_dims, postfix=2)[1] + self.ffn = FFN( + embed_dims=embed_dims, + feedforward_channels=feedforward_channels, + num_fcs=num_fcs, + ffn_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + act_cfg=act_cfg, + add_identity=False) + + self.drop_path = build_dropout( + dict(type='DropPath', drop_prob=drop_path_rate) + ) if drop_path_rate > 0. else nn.Identity() + + def forward(self, x, hw_shape): + x = x + self.drop_path(self.attn(self.norm1(x), hw_shape, identity=0.)) + x = x + self.drop_path(self.ffn(self.norm2(x))) + return x + + +class LocallyGroupedSelfAttention(BaseModule): + """Locally-grouped Self Attention (LSA) module. + + Args: + embed_dims (int): Number of input channels. + num_heads (int): Number of attention heads. Default: 8 + qkv_bias (bool, optional): If True, add a learnable bias to q, k, v. + Default: False. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + attn_drop_rate (float, optional): Dropout ratio of attention weight. + Default: 0.0 + proj_drop_rate (float, optional): Dropout ratio of output. Default: 0. + window_size(int): Window size of LSA. Default: 1. + init_cfg (dict, optional): The Config for initialization. + Defaults to None. + """ + + def __init__(self, + embed_dims, + num_heads=8, + qkv_bias=False, + qk_scale=None, + attn_drop_rate=0., + proj_drop_rate=0., + window_size=1, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + assert embed_dims % num_heads == 0, f'dim {embed_dims} should be ' \ + f'divided by num_heads ' \ + f'{num_heads}.' + self.embed_dims = embed_dims + self.num_heads = num_heads + head_dim = embed_dims // num_heads + self.scale = qk_scale or head_dim**-0.5 + + self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop_rate) + self.proj = nn.Linear(embed_dims, embed_dims) + self.proj_drop = nn.Dropout(proj_drop_rate) + self.window_size = window_size + + def forward(self, x, hw_shape): + b, n, c = x.shape + h, w = hw_shape + x = x.view(b, h, w, c) + + # pad feature maps to multiples of Local-groups + pad_l = pad_t = 0 + pad_r = (self.window_size - w % self.window_size) % self.window_size + pad_b = (self.window_size - h % self.window_size) % self.window_size + x = F.pad(x, (0, 0, pad_l, pad_r, pad_t, pad_b)) + + # calculate attention mask for LSA + Hp, Wp = x.shape[1:-1] + _h, _w = Hp // self.window_size, Wp // self.window_size + mask = torch.zeros((1, Hp, Wp), device=x.device) + mask[:, -pad_b:, :].fill_(1) + mask[:, :, -pad_r:].fill_(1) + + # [B, _h, _w, window_size, window_size, C] + x = x.reshape(b, _h, self.window_size, _w, self.window_size, + c).transpose(2, 3) + mask = mask.reshape(1, _h, self.window_size, _w, + self.window_size).transpose(2, 3).reshape( + 1, _h * _w, + self.window_size * self.window_size) + # [1, _h*_w, window_size*window_size, window_size*window_size] + attn_mask = mask.unsqueeze(2) - mask.unsqueeze(3) + attn_mask = attn_mask.masked_fill(attn_mask != 0, + float(-1000.0)).masked_fill( + attn_mask == 0, float(0.0)) + + # [3, B, _w*_h, nhead, window_size*window_size, dim] + qkv = self.qkv(x).reshape(b, _h * _w, + self.window_size * self.window_size, 3, + self.num_heads, c // self.num_heads).permute( + 3, 0, 1, 4, 2, 5) + q, k, v = qkv[0], qkv[1], qkv[2] + # [B, _h*_w, n_head, window_size*window_size, window_size*window_size] + attn = (q @ k.transpose(-2, -1)) * self.scale + attn = attn + attn_mask.unsqueeze(2) + attn = attn.softmax(dim=-1) + attn = self.attn_drop(attn) + attn = (attn @ v).transpose(2, 3).reshape(b, _h, _w, self.window_size, + self.window_size, c) + x = attn.transpose(2, 3).reshape(b, _h * self.window_size, + _w * self.window_size, c) + if pad_r > 0 or pad_b > 0: + x = x[:, :h, :w, :].contiguous() + + x = x.reshape(b, n, c) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class LSAEncoderLayer(BaseModule): + """Implements one encoder layer in Twins-SVT. + + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + drop_rate (float): Probability of an element to be zeroed + after the feed forward layer. Default: 0.0. + attn_drop_rate (float, optional): Dropout ratio of attention weight. + Default: 0.0 + drop_path_rate (float): Stochastic depth rate. Default 0.0. + num_fcs (int): The number of fully-connected layers for FFNs. + Default: 2. + qkv_bias (bool): Enable bias for qkv if True. Default: True + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + window_size (int): Window size of LSA. Default: 1. + init_cfg (dict, optional): The Config for initialization. + Defaults to None. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + num_fcs=2, + qkv_bias=True, + qk_scale=None, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + window_size=1, + init_cfg=None): + + super().__init__(init_cfg=init_cfg) + + self.norm1 = build_norm_layer(norm_cfg, embed_dims, postfix=1)[1] + self.attn = LocallyGroupedSelfAttention(embed_dims, num_heads, + qkv_bias, qk_scale, + attn_drop_rate, drop_rate, + window_size) + + self.norm2 = build_norm_layer(norm_cfg, embed_dims, postfix=2)[1] + self.ffn = FFN( + embed_dims=embed_dims, + feedforward_channels=feedforward_channels, + num_fcs=num_fcs, + ffn_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + act_cfg=act_cfg, + add_identity=False) + + self.drop_path = build_dropout( + dict(type='DropPath', drop_prob=drop_path_rate) + ) if drop_path_rate > 0. else nn.Identity() + + def forward(self, x, hw_shape): + x = x + self.drop_path(self.attn(self.norm1(x), hw_shape)) + x = x + self.drop_path(self.ffn(self.norm2(x))) + return x + + +class ConditionalPositionEncoding(BaseModule): + """The Conditional Position Encoding (CPE) module. + + The CPE is the implementation of 'Conditional Positional Encodings + for Vision Transformers '_. + + Args: + in_channels (int): Number of input channels. + embed_dims (int): The feature dimension. Default: 768. + stride (int): Stride of conv layer. Default: 1. + """ + + def __init__(self, in_channels, embed_dims=768, stride=1, init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.proj = nn.Conv2d( + in_channels, + embed_dims, + kernel_size=3, + stride=stride, + padding=1, + bias=True, + groups=embed_dims) + self.stride = stride + + def forward(self, x, hw_shape): + b, n, c = x.shape + h, w = hw_shape + feat_token = x + cnn_feat = feat_token.transpose(1, 2).view(b, c, h, w) + if self.stride == 1: + x = self.proj(cnn_feat) + cnn_feat + else: + x = self.proj(cnn_feat) + x = x.flatten(2).transpose(1, 2) + return x + + +@MODELS.register_module() +class PCPVT(BaseModule): + """The backbone of Twins-PCPVT. + + This backbone is the implementation of `Twins: Revisiting the Design + of Spatial Attention in Vision Transformers + `_. + + Args: + in_channels (int): Number of input channels. Default: 3. + embed_dims (list): Embedding dimension. Default: [64, 128, 256, 512]. + patch_sizes (list): The patch sizes. Default: [4, 2, 2, 2]. + strides (list): The strides. Default: [4, 2, 2, 2]. + num_heads (int): Number of attention heads. Default: [1, 2, 4, 8]. + mlp_ratios (int): Ratio of mlp hidden dim to embedding dim. + Default: [4, 4, 4, 4]. + out_indices (tuple[int]): Output from which stages. + Default: (0, 1, 2, 3). + qkv_bias (bool): Enable bias for qkv if True. Default: False. + drop_rate (float): Probability of an element to be zeroed. + Default 0. + attn_drop_rate (float): The drop out rate for attention layer. + Default 0.0 + drop_path_rate (float): Stochastic depth rate. Default 0.0 + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN') + depths (list): Depths of each stage. Default [3, 4, 6, 3] + sr_ratios (list): Kernel_size of conv in each Attn module in + Transformer encoder layer. Default: [8, 4, 2, 1]. + norm_after_stage(bool): Add extra norm. Default False. + init_cfg (dict, optional): The Config for initialization. + Defaults to None. + """ + + def __init__(self, + in_channels=3, + embed_dims=[64, 128, 256, 512], + patch_sizes=[4, 2, 2, 2], + strides=[4, 2, 2, 2], + num_heads=[1, 2, 4, 8], + mlp_ratios=[4, 4, 4, 4], + out_indices=(0, 1, 2, 3), + qkv_bias=False, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + norm_cfg=dict(type='LN'), + depths=[3, 4, 6, 3], + sr_ratios=[8, 4, 2, 1], + norm_after_stage=False, + pretrained=None, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be set at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is not None: + raise TypeError('pretrained must be a str or None') + self.depths = depths + + # patch_embed + self.patch_embeds = ModuleList() + self.position_encoding_drops = ModuleList() + self.layers = ModuleList() + + for i in range(len(depths)): + self.patch_embeds.append( + PatchEmbed( + in_channels=in_channels if i == 0 else embed_dims[i - 1], + embed_dims=embed_dims[i], + conv_type='Conv2d', + kernel_size=patch_sizes[i], + stride=strides[i], + padding='corner', + norm_cfg=norm_cfg)) + + self.position_encoding_drops.append(nn.Dropout(p=drop_rate)) + + self.position_encodings = ModuleList([ + ConditionalPositionEncoding(embed_dim, embed_dim) + for embed_dim in embed_dims + ]) + + # transformer encoder + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, sum(depths)) + ] # stochastic depth decay rule + cur = 0 + + for k in range(len(depths)): + _block = ModuleList([ + GSAEncoderLayer( + embed_dims=embed_dims[k], + num_heads=num_heads[k], + feedforward_channels=mlp_ratios[k] * embed_dims[k], + attn_drop_rate=attn_drop_rate, + drop_rate=drop_rate, + drop_path_rate=dpr[cur + i], + num_fcs=2, + qkv_bias=qkv_bias, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + sr_ratio=sr_ratios[k]) for i in range(depths[k]) + ]) + self.layers.append(_block) + cur += depths[k] + + self.norm_name, norm = build_norm_layer( + norm_cfg, embed_dims[-1], postfix=1) + + self.out_indices = out_indices + self.norm_after_stage = norm_after_stage + if self.norm_after_stage: + self.norm_list = ModuleList() + for dim in embed_dims: + self.norm_list.append(build_norm_layer(norm_cfg, dim)[1]) + + def init_weights(self): + if self.init_cfg is not None: + super().init_weights() + else: + for m in self.modules(): + if isinstance(m, nn.Linear): + trunc_normal_init(m, std=.02, bias=0.) + elif isinstance(m, (_BatchNorm, nn.GroupNorm, nn.LayerNorm)): + constant_init(m, val=1.0, bias=0.) + elif isinstance(m, nn.Conv2d): + fan_out = m.kernel_size[0] * m.kernel_size[ + 1] * m.out_channels + fan_out //= m.groups + normal_init( + m, mean=0, std=math.sqrt(2.0 / fan_out), bias=0) + + def forward(self, x): + outputs = list() + + b = x.shape[0] + + for i in range(len(self.depths)): + x, hw_shape = self.patch_embeds[i](x) + h, w = hw_shape + x = self.position_encoding_drops[i](x) + for j, blk in enumerate(self.layers[i]): + x = blk(x, hw_shape) + if j == 0: + x = self.position_encodings[i](x, hw_shape) + if self.norm_after_stage: + x = self.norm_list[i](x) + x = x.reshape(b, h, w, -1).permute(0, 3, 1, 2).contiguous() + + if i in self.out_indices: + outputs.append(x) + + return tuple(outputs) + + +@MODELS.register_module() +class SVT(PCPVT): + """The backbone of Twins-SVT. + + This backbone is the implementation of `Twins: Revisiting the Design + of Spatial Attention in Vision Transformers + `_. + + Args: + in_channels (int): Number of input channels. Default: 3. + embed_dims (list): Embedding dimension. Default: [64, 128, 256, 512]. + patch_sizes (list): The patch sizes. Default: [4, 2, 2, 2]. + strides (list): The strides. Default: [4, 2, 2, 2]. + num_heads (int): Number of attention heads. Default: [1, 2, 4]. + mlp_ratios (int): Ratio of mlp hidden dim to embedding dim. + Default: [4, 4, 4]. + out_indices (tuple[int]): Output from which stages. + Default: (0, 1, 2, 3). + qkv_bias (bool): Enable bias for qkv if True. Default: False. + drop_rate (float): Dropout rate. Default 0. + attn_drop_rate (float): Dropout ratio of attention weight. + Default 0.0 + drop_path_rate (float): Stochastic depth rate. Default 0.2. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN') + depths (list): Depths of each stage. Default [4, 4, 4]. + sr_ratios (list): Kernel_size of conv in each Attn module in + Transformer encoder layer. Default: [4, 2, 1]. + windiow_sizes (list): Window size of LSA. Default: [7, 7, 7], + input_features_slice(bool): Input features need slice. Default: False. + norm_after_stage(bool): Add extra norm. Default False. + strides (list): Strides in patch-Embedding modules. Default: (2, 2, 2) + init_cfg (dict, optional): The Config for initialization. + Defaults to None. + """ + + def __init__(self, + in_channels=3, + embed_dims=[64, 128, 256], + patch_sizes=[4, 2, 2, 2], + strides=[4, 2, 2, 2], + num_heads=[1, 2, 4], + mlp_ratios=[4, 4, 4], + out_indices=(0, 1, 2, 3), + qkv_bias=False, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.2, + norm_cfg=dict(type='LN'), + depths=[4, 4, 4], + sr_ratios=[4, 2, 1], + windiow_sizes=[7, 7, 7], + norm_after_stage=True, + pretrained=None, + init_cfg=None): + super().__init__(in_channels, embed_dims, patch_sizes, strides, + num_heads, mlp_ratios, out_indices, qkv_bias, + drop_rate, attn_drop_rate, drop_path_rate, norm_cfg, + depths, sr_ratios, norm_after_stage, pretrained, + init_cfg) + # transformer encoder + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, sum(depths)) + ] # stochastic depth decay rule + + for k in range(len(depths)): + for i in range(depths[k]): + if i % 2 == 0: + self.layers[k][i] = \ + LSAEncoderLayer( + embed_dims=embed_dims[k], + num_heads=num_heads[k], + feedforward_channels=mlp_ratios[k] * embed_dims[k], + drop_rate=drop_rate, + attn_drop_rate=attn_drop_rate, + drop_path_rate=dpr[sum(depths[:k])+i], + qkv_bias=qkv_bias, + window_size=windiow_sizes[k]) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/unet.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/unet.py new file mode 100644 index 0000000..545921d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/unet.py @@ -0,0 +1,436 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import ConvModule, build_activation_layer, build_norm_layer +from mmengine.model import BaseModule +from mmengine.utils.dl_utils.parrots_wrapper import _BatchNorm + +from mmseg.registry import MODELS +from ..utils import UpConvBlock, Upsample + + +class BasicConvBlock(nn.Module): + """Basic convolutional block for UNet. + + This module consists of several plain convolutional layers. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + num_convs (int): Number of convolutional layers. Default: 2. + stride (int): Whether use stride convolution to downsample + the input feature map. If stride=2, it only uses stride convolution + in the first convolutional layer to downsample the input feature + map. Options are 1 or 2. Default: 1. + dilation (int): Whether use dilated convolution to expand the + receptive field. Set dilation rate of each convolutional layer and + the dilation rate of the first convolutional layer is always 1. + Default: 1. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + conv_cfg (dict | None): Config dict for convolution layer. + Default: None. + norm_cfg (dict | None): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict | None): Config dict for activation layer in ConvModule. + Default: dict(type='ReLU'). + dcn (bool): Use deformable convolution in convolutional layer or not. + Default: None. + plugins (dict): plugins for convolutional layers. Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + num_convs=2, + stride=1, + dilation=1, + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + dcn=None, + plugins=None): + super().__init__() + assert dcn is None, 'Not implemented yet.' + assert plugins is None, 'Not implemented yet.' + + self.with_cp = with_cp + convs = [] + for i in range(num_convs): + convs.append( + ConvModule( + in_channels=in_channels if i == 0 else out_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride if i == 0 else 1, + dilation=1 if i == 0 else dilation, + padding=1 if i == 0 else dilation, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + self.convs = nn.Sequential(*convs) + + def forward(self, x): + """Forward function.""" + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(self.convs, x) + else: + out = self.convs(x) + return out + + +@MODELS.register_module() +class DeconvModule(nn.Module): + """Deconvolution upsample module in decoder for UNet (2X upsample). + + This module uses deconvolution to upsample feature map in the decoder + of UNet. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + norm_cfg (dict | None): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict | None): Config dict for activation layer in ConvModule. + Default: dict(type='ReLU'). + kernel_size (int): Kernel size of the convolutional layer. Default: 4. + """ + + def __init__(self, + in_channels, + out_channels, + with_cp=False, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + *, + kernel_size=4, + scale_factor=2): + super().__init__() + + assert (kernel_size - scale_factor >= 0) and\ + (kernel_size - scale_factor) % 2 == 0,\ + f'kernel_size should be greater than or equal to scale_factor '\ + f'and (kernel_size - scale_factor) should be even numbers, '\ + f'while the kernel size is {kernel_size} and scale_factor is '\ + f'{scale_factor}.' + + stride = scale_factor + padding = (kernel_size - scale_factor) // 2 + self.with_cp = with_cp + deconv = nn.ConvTranspose2d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding) + + norm_name, norm = build_norm_layer(norm_cfg, out_channels) + activate = build_activation_layer(act_cfg) + self.deconv_upsamping = nn.Sequential(deconv, norm, activate) + + def forward(self, x): + """Forward function.""" + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(self.deconv_upsamping, x) + else: + out = self.deconv_upsamping(x) + return out + + +@MODELS.register_module() +class InterpConv(nn.Module): + """Interpolation upsample module in decoder for UNet. + + This module uses interpolation to upsample feature map in the decoder + of UNet. It consists of one interpolation upsample layer and one + convolutional layer. It can be one interpolation upsample layer followed + by one convolutional layer (conv_first=False) or one convolutional layer + followed by one interpolation upsample layer (conv_first=True). + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + norm_cfg (dict | None): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict | None): Config dict for activation layer in ConvModule. + Default: dict(type='ReLU'). + conv_cfg (dict | None): Config dict for convolution layer. + Default: None. + conv_first (bool): Whether convolutional layer or interpolation + upsample layer first. Default: False. It means interpolation + upsample layer followed by one convolutional layer. + kernel_size (int): Kernel size of the convolutional layer. Default: 1. + stride (int): Stride of the convolutional layer. Default: 1. + padding (int): Padding of the convolutional layer. Default: 1. + upsample_cfg (dict): Interpolation config of the upsample layer. + Default: dict( + scale_factor=2, mode='bilinear', align_corners=False). + """ + + def __init__(self, + in_channels, + out_channels, + with_cp=False, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + *, + conv_cfg=None, + conv_first=False, + kernel_size=1, + stride=1, + padding=0, + upsample_cfg=dict( + scale_factor=2, mode='bilinear', align_corners=False)): + super().__init__() + + self.with_cp = with_cp + conv = ConvModule( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + upsample = Upsample(**upsample_cfg) + if conv_first: + self.interp_upsample = nn.Sequential(conv, upsample) + else: + self.interp_upsample = nn.Sequential(upsample, conv) + + def forward(self, x): + """Forward function.""" + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(self.interp_upsample, x) + else: + out = self.interp_upsample(x) + return out + + +@MODELS.register_module() +class UNet(BaseModule): + """UNet backbone. + + This backbone is the implementation of `U-Net: Convolutional Networks + for Biomedical Image Segmentation `_. + + Args: + in_channels (int): Number of input image channels. Default" 3. + base_channels (int): Number of base channels of each stage. + The output channels of the first stage. Default: 64. + num_stages (int): Number of stages in encoder, normally 5. Default: 5. + strides (Sequence[int 1 | 2]): Strides of each stage in encoder. + len(strides) is equal to num_stages. Normally the stride of the + first stage in encoder is 1. If strides[i]=2, it uses stride + convolution to downsample in the correspondence encoder stage. + Default: (1, 1, 1, 1, 1). + enc_num_convs (Sequence[int]): Number of convolutional layers in the + convolution block of the correspondence encoder stage. + Default: (2, 2, 2, 2, 2). + dec_num_convs (Sequence[int]): Number of convolutional layers in the + convolution block of the correspondence decoder stage. + Default: (2, 2, 2, 2). + downsamples (Sequence[int]): Whether use MaxPool to downsample the + feature map after the first stage of encoder + (stages: [1, num_stages)). If the correspondence encoder stage use + stride convolution (strides[i]=2), it will never use MaxPool to + downsample, even downsamples[i-1]=True. + Default: (True, True, True, True). + enc_dilations (Sequence[int]): Dilation rate of each stage in encoder. + Default: (1, 1, 1, 1, 1). + dec_dilations (Sequence[int]): Dilation rate of each stage in decoder. + Default: (1, 1, 1, 1). + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + conv_cfg (dict | None): Config dict for convolution layer. + Default: None. + norm_cfg (dict | None): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict | None): Config dict for activation layer in ConvModule. + Default: dict(type='ReLU'). + upsample_cfg (dict): The upsample config of the upsample module in + decoder. Default: dict(type='InterpConv'). + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + dcn (bool): Use deformable convolution in convolutional layer or not. + Default: None. + plugins (dict): plugins for convolutional layers. Default: None. + pretrained (str, optional): model pretrained path. Default: None + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None + + Notice: + The input image size should be divisible by the whole downsample rate + of the encoder. More detail of the whole downsample rate can be found + in UNet._check_input_divisible. + """ + + def __init__(self, + in_channels=3, + base_channels=64, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1), + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + upsample_cfg=dict(type='InterpConv'), + norm_eval=False, + dcn=None, + plugins=None, + pretrained=None, + init_cfg=None): + super().__init__(init_cfg) + + self.pretrained = pretrained + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be setting at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is a deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is None: + if init_cfg is None: + self.init_cfg = [ + dict(type='Kaiming', layer='Conv2d'), + dict( + type='Constant', + val=1, + layer=['_BatchNorm', 'GroupNorm']) + ] + else: + raise TypeError('pretrained must be a str or None') + + assert dcn is None, 'Not implemented yet.' + assert plugins is None, 'Not implemented yet.' + assert len(strides) == num_stages, \ + 'The length of strides should be equal to num_stages, '\ + f'while the strides is {strides}, the length of '\ + f'strides is {len(strides)}, and the num_stages is '\ + f'{num_stages}.' + assert len(enc_num_convs) == num_stages, \ + 'The length of enc_num_convs should be equal to num_stages, '\ + f'while the enc_num_convs is {enc_num_convs}, the length of '\ + f'enc_num_convs is {len(enc_num_convs)}, and the num_stages is '\ + f'{num_stages}.' + assert len(dec_num_convs) == (num_stages-1), \ + 'The length of dec_num_convs should be equal to (num_stages-1), '\ + f'while the dec_num_convs is {dec_num_convs}, the length of '\ + f'dec_num_convs is {len(dec_num_convs)}, and the num_stages is '\ + f'{num_stages}.' + assert len(downsamples) == (num_stages-1), \ + 'The length of downsamples should be equal to (num_stages-1), '\ + f'while the downsamples is {downsamples}, the length of '\ + f'downsamples is {len(downsamples)}, and the num_stages is '\ + f'{num_stages}.' + assert len(enc_dilations) == num_stages, \ + 'The length of enc_dilations should be equal to num_stages, '\ + f'while the enc_dilations is {enc_dilations}, the length of '\ + f'enc_dilations is {len(enc_dilations)}, and the num_stages is '\ + f'{num_stages}.' + assert len(dec_dilations) == (num_stages-1), \ + 'The length of dec_dilations should be equal to (num_stages-1), '\ + f'while the dec_dilations is {dec_dilations}, the length of '\ + f'dec_dilations is {len(dec_dilations)}, and the num_stages is '\ + f'{num_stages}.' + self.num_stages = num_stages + self.strides = strides + self.downsamples = downsamples + self.norm_eval = norm_eval + self.base_channels = base_channels + + self.encoder = nn.ModuleList() + self.decoder = nn.ModuleList() + + for i in range(num_stages): + enc_conv_block = [] + if i != 0: + if strides[i] == 1 and downsamples[i - 1]: + enc_conv_block.append(nn.MaxPool2d(kernel_size=2)) + upsample = (strides[i] != 1 or downsamples[i - 1]) + self.decoder.append( + UpConvBlock( + conv_block=BasicConvBlock, + in_channels=base_channels * 2**i, + skip_channels=base_channels * 2**(i - 1), + out_channels=base_channels * 2**(i - 1), + num_convs=dec_num_convs[i - 1], + stride=1, + dilation=dec_dilations[i - 1], + with_cp=with_cp, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + upsample_cfg=upsample_cfg if upsample else None, + dcn=None, + plugins=None)) + + enc_conv_block.append( + BasicConvBlock( + in_channels=in_channels, + out_channels=base_channels * 2**i, + num_convs=enc_num_convs[i], + stride=strides[i], + dilation=enc_dilations[i], + with_cp=with_cp, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + dcn=None, + plugins=None)) + self.encoder.append(nn.Sequential(*enc_conv_block)) + in_channels = base_channels * 2**i + + def forward(self, x): + self._check_input_divisible(x) + enc_outs = [] + for enc in self.encoder: + x = enc(x) + enc_outs.append(x) + dec_outs = [x] + for i in reversed(range(len(self.decoder))): + x = self.decoder[i](enc_outs[i], x) + dec_outs.append(x) + + return dec_outs + + def train(self, mode=True): + """Convert the model into training mode while keep normalization layer + freezed.""" + super().train(mode) + if mode and self.norm_eval: + for m in self.modules(): + # trick: eval have effect on BatchNorm only + if isinstance(m, _BatchNorm): + m.eval() + + def _check_input_divisible(self, x): + h, w = x.shape[-2:] + whole_downsample_rate = 1 + for i in range(1, self.num_stages): + if self.strides[i] == 2 or self.downsamples[i - 1]: + whole_downsample_rate *= 2 + assert (h % whole_downsample_rate == 0) \ + and (w % whole_downsample_rate == 0),\ + f'The input image size {(h, w)} should be divisible by the whole '\ + f'downsample rate {whole_downsample_rate}, when num_stages is '\ + f'{self.num_stages}, strides is {self.strides}, and downsamples '\ + f'is {self.downsamples}.' diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/vit.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/vit.py new file mode 100644 index 0000000..dd0f688 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/vit.py @@ -0,0 +1,501 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import warnings + +import torch +import torch.nn as nn +import torch.utils.checkpoint as cp +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN, MultiheadAttention +from mmengine.logging import print_log +from mmengine.model import BaseModule, ModuleList +from mmengine.model.weight_init import (constant_init, kaiming_init, + trunc_normal_) +from mmengine.runner.checkpoint import CheckpointLoader, load_state_dict +from torch.nn.modules.batchnorm import _BatchNorm +from torch.nn.modules.utils import _pair as to_2tuple + +from mmseg.registry import MODELS +from ..utils import PatchEmbed, resize + + +class TransformerEncoderLayer(BaseModule): + """Implements one encoder layer in Vision Transformer. + + Args: + embed_dims (int): The feature dimension. + num_heads (int): Parallel attention heads. + feedforward_channels (int): The hidden dimension for FFNs. + drop_rate (float): Probability of an element to be zeroed + after the feed forward layer. Default: 0.0. + attn_drop_rate (float): The drop out rate for attention layer. + Default: 0.0. + drop_path_rate (float): stochastic depth rate. Default 0.0. + num_fcs (int): The number of fully-connected layers for FFNs. + Default: 2. + qkv_bias (bool): enable bias for qkv if True. Default: True + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + batch_first (bool): Key, Query and Value are shape of + (batch, n, embed_dim) + or (n, batch, embed_dim). Default: True. + with_cp (bool): Use checkpoint or not. Using checkpoint will save + some memory while slowing down the training speed. Default: False. + """ + + def __init__(self, + embed_dims, + num_heads, + feedforward_channels, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + num_fcs=2, + qkv_bias=True, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + batch_first=True, + attn_cfg=dict(), + ffn_cfg=dict(), + with_cp=False): + super().__init__() + + self.norm1_name, norm1 = build_norm_layer( + norm_cfg, embed_dims, postfix=1) + self.add_module(self.norm1_name, norm1) + + attn_cfg.update( + dict( + embed_dims=embed_dims, + num_heads=num_heads, + attn_drop=attn_drop_rate, + proj_drop=drop_rate, + batch_first=batch_first, + bias=qkv_bias)) + + self.build_attn(attn_cfg) + + self.norm2_name, norm2 = build_norm_layer( + norm_cfg, embed_dims, postfix=2) + self.add_module(self.norm2_name, norm2) + + ffn_cfg.update( + dict( + embed_dims=embed_dims, + feedforward_channels=feedforward_channels, + num_fcs=num_fcs, + ffn_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate) + if drop_path_rate > 0 else None, + act_cfg=act_cfg)) + self.build_ffn(ffn_cfg) + self.with_cp = with_cp + + def build_attn(self, attn_cfg): + self.attn = MultiheadAttention(**attn_cfg) + + def build_ffn(self, ffn_cfg): + self.ffn = FFN(**ffn_cfg) + + @property + def norm1(self): + return getattr(self, self.norm1_name) + + @property + def norm2(self): + return getattr(self, self.norm2_name) + + def forward(self, x): + + def _inner_forward(x): + x = self.attn(self.norm1(x), identity=x) + x = self.ffn(self.norm2(x), identity=x) + return x + + if self.with_cp and x.requires_grad: + x = cp.checkpoint(_inner_forward, x) + else: + x = _inner_forward(x) + return x + + +@MODELS.register_module() +class VisionTransformer(BaseModule): + """Vision Transformer. + + This backbone is the implementation of `An Image is Worth 16x16 Words: + Transformers for Image Recognition at + Scale `_. + + Args: + img_size (int | tuple): Input image size. Default: 224. + patch_size (int): The patch size. Default: 16. + patch_pad (str | int | None): The padding method in patch embedding. + Default: 'corner'. + in_channels (int): Number of input channels. Default: 3. + embed_dims (int): embedding dimension. Default: 768. + num_layers (int): depth of transformer. Default: 12. + num_heads (int): number of attention heads. Default: 12. + mlp_ratio (int): ratio of mlp hidden dim to embedding dim. + Default: 4. + out_origin (bool): Whether to output the original input embedding. + Default: False + out_indices (list | tuple | int): Output from which stages. + Default: -1. + qkv_bias (bool): enable bias for qkv if True. Default: True. + drop_rate (float): Probability of an element to be zeroed. + Default 0.0 + attn_drop_rate (float): The drop out rate for attention layer. + Default 0.0 + drop_path_rate (float): stochastic depth rate. Default 0.0 + with_cls_token (bool): Whether concatenating class token into image + tokens as transformer input. Default: True. + output_cls_token (bool): Whether output the cls_token. If set True, + `with_cls_token` must be True. Default: False. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN') + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + patch_bias (dict): Whether use bias in convolution of PatchEmbed Block. + Default: True. + patch_norm (bool): Whether to add a norm in PatchEmbed Block. + Default: False. + pre_norm (bool): Whether to add a norm before Transformer Layers. + Default: False. + final_norm (bool): Whether to add a additional layer to normalize + final feature map. Default: False. + interpolate_mode (str): Select the interpolate mode for position + embeding vector resize. Default: bicubic. + num_fcs (int): The number of fully-connected layers for FFNs. + Default: 2. + norm_eval (bool): Whether to set norm layers to eval mode, namely, + freeze running stats (mean and var). Note: Effect on Batch Norm + and its variants only. Default: False. + with_cp (bool): Use checkpoint or not. Using checkpoint will save + some memory while slowing down the training speed. Default: False. + frozen_exclude (List): List of parameters that are not to be frozen. + Default: ["all"], "all" means there are no frozen parameters. + pretrained (str, optional): model pretrained path. Default: None. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + img_size=224, + patch_size=16, + patch_pad='corner', + in_channels=3, + embed_dims=768, + num_layers=12, + num_heads=12, + mlp_ratio=4, + out_origin=False, + out_indices=-1, + qkv_bias=True, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + with_cls_token=True, + output_cls_token=False, + norm_cfg=dict(type='LN'), + act_cfg=dict(type='GELU'), + patch_norm=False, + patch_bias=False, + pre_norm=False, + final_norm=False, + interpolate_mode='bicubic', + num_fcs=2, + norm_eval=False, + with_cp=False, + frozen_exclude=['all'], + pretrained=None, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + if isinstance(img_size, int): + img_size = to_2tuple(img_size) + elif isinstance(img_size, tuple): + if len(img_size) == 1: + img_size = to_2tuple(img_size[0]) + assert len(img_size) == 2, \ + f'The size of image should have length 1 or 2, ' \ + f'but got {len(img_size)}' + + if output_cls_token: + assert with_cls_token is True, f'with_cls_token must be True if' \ + f'set output_cls_token to True, but got {with_cls_token}' + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be set at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is not None: + raise TypeError('pretrained must be a str or None') + + self.img_size = img_size + self.patch_size = patch_size + self.interpolate_mode = interpolate_mode + self.norm_eval = norm_eval + self.with_cp = with_cp + self.pretrained = pretrained + self.out_origin = out_origin + self.frozen_exclude = frozen_exclude + + self.patch_embed = PatchEmbed( + in_channels=in_channels, + embed_dims=embed_dims, + conv_type='Conv2d', + kernel_size=patch_size, + stride=patch_size, + padding=patch_pad, + bias=patch_bias, + norm_cfg=norm_cfg if patch_norm else None, + init_cfg=None, + ) + + num_patches = (img_size[0] // patch_size) * \ + (img_size[1] // patch_size) + + self.with_cls_token = with_cls_token + self.output_cls_token = output_cls_token + self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dims)) + self.pos_embed = nn.Parameter( + torch.zeros(1, num_patches + 1, embed_dims)) + self.drop_after_pos = nn.Dropout(p=drop_rate) + self.pre_norm = pre_norm + + if self.pre_norm: + self.pre_ln_name, pre_ln = build_norm_layer( + norm_cfg, embed_dims, postfix='_pre') + self.add_module(self.pre_ln_name, pre_ln) + + if isinstance(out_indices, int): + if out_indices == -1: + out_indices = num_layers - 1 + self.out_indices = [out_indices] + elif isinstance(out_indices, list) or isinstance(out_indices, tuple): + self.out_indices = out_indices + else: + raise TypeError('out_indices must be type of int, list or tuple') + + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, num_layers) + ] # stochastic depth decay rule + + self.layers = ModuleList() + for i in range(num_layers): + self.layers.append( + TransformerEncoderLayer( + embed_dims=embed_dims, + num_heads=num_heads, + feedforward_channels=mlp_ratio * embed_dims, + attn_drop_rate=attn_drop_rate, + drop_rate=drop_rate, + drop_path_rate=dpr[i], + num_fcs=num_fcs, + qkv_bias=qkv_bias, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + with_cp=with_cp, + batch_first=True)) + + self.final_norm = final_norm + if final_norm: + self.norm1_name, norm1 = build_norm_layer( + norm_cfg, embed_dims, postfix=1) + self.add_module(self.norm1_name, norm1) + + self._freeze() + + @property + def pre_ln(self): + return getattr(self, self.pre_ln_name) + + @property + def norm1(self): + return getattr(self, self.norm1_name) + + def init_weights(self): + if isinstance(self.init_cfg, dict) and \ + self.init_cfg.get('type') in ['Pretrained', 'Pretrained_Part']: + checkpoint = CheckpointLoader.load_checkpoint( + self.init_cfg['checkpoint'], logger=None, map_location='cpu') + + if self.init_cfg.get('type') == 'Pretrained': + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + else: + state_dict = checkpoint + + elif self.init_cfg.get('type') == 'Pretrained_Part': + state_dict = checkpoint.copy() + para_prefix = 'image_encoder' + prefix_len = len(para_prefix) + 1 + for k, v in checkpoint.items(): + state_dict.pop(k) + if para_prefix in k: + state_dict[k[prefix_len:]] = v + + if 'pos_embed' in state_dict.keys(): + if self.pos_embed.shape != state_dict['pos_embed'].shape: + print_log(msg=f'Resize the pos_embed shape from ' + f'{state_dict["pos_embed"].shape} to ' + f'{self.pos_embed.shape}') + h, w = self.img_size + pos_size = int( + math.sqrt(state_dict['pos_embed'].shape[1] - 1)) + state_dict['pos_embed'] = self.resize_pos_embed( + state_dict['pos_embed'], + (h // self.patch_size, w // self.patch_size), + (pos_size, pos_size), self.interpolate_mode) + + load_state_dict(self, state_dict, strict=False, logger=None) + elif self.init_cfg is not None: + super().init_weights() + else: + # We only implement the 'jax_impl' initialization implemented at + # https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py#L353 # noqa: E501 + trunc_normal_(self.pos_embed, std=.02) + trunc_normal_(self.cls_token, std=.02) + for n, m in self.named_modules(): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=.02) + if m.bias is not None: + if 'ffn' in n: + nn.init.normal_(m.bias, mean=0., std=1e-6) + else: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.Conv2d): + kaiming_init(m, mode='fan_in', bias=0.) + elif isinstance(m, (_BatchNorm, nn.GroupNorm, nn.LayerNorm)): + constant_init(m, val=1.0, bias=0.) + + def _freeze(self): + if 'all' in self.frozen_exclude: + return + for name, param in self.named_parameters(): + if not any([exclude in name for exclude in self.frozen_exclude]): + param.requires_grad = False + + def _pos_embeding(self, patched_img, hw_shape, pos_embed): + """Positioning embeding method. + + Resize the pos_embed, if the input image size doesn't match + the training size. + Args: + patched_img (torch.Tensor): The patched image, it should be + shape of [B, L1, C]. + hw_shape (tuple): The downsampled image resolution. + pos_embed (torch.Tensor): The pos_embed weighs, it should be + shape of [B, L2, c]. + Return: + torch.Tensor: The pos encoded image feature. + """ + assert patched_img.ndim == 3 and pos_embed.ndim == 3, \ + 'the shapes of patched_img and pos_embed must be [B, L, C]' + x_len, pos_len = patched_img.shape[1], pos_embed.shape[1] + if x_len != pos_len: + if pos_len == (self.img_size[0] // self.patch_size) * ( + self.img_size[1] // self.patch_size) + 1: + pos_h = self.img_size[0] // self.patch_size + pos_w = self.img_size[1] // self.patch_size + else: + raise ValueError( + 'Unexpected shape of pos_embed, got {}.'.format( + pos_embed.shape)) + pos_embed = self.resize_pos_embed(pos_embed, hw_shape, + (pos_h, pos_w), + self.interpolate_mode) + return self.drop_after_pos(patched_img + pos_embed) + + @staticmethod + def resize_pos_embed(pos_embed, input_shpae, pos_shape, mode): + """Resize pos_embed weights. + + Resize pos_embed using bicubic interpolate method. + Args: + pos_embed (torch.Tensor): Position embedding weights. + input_shpae (tuple): Tuple for (downsampled input image height, + downsampled input image width). + pos_shape (tuple): The resolution of downsampled origin training + image. + mode (str): Algorithm used for upsampling: + ``'nearest'`` | ``'linear'`` | ``'bilinear'`` | ``'bicubic'`` | + ``'trilinear'``. Default: ``'nearest'`` + Return: + torch.Tensor: The resized pos_embed of shape [B, L_new, C] + """ + assert pos_embed.ndim == 3, 'shape of pos_embed must be [B, L, C]' + pos_h, pos_w = pos_shape + cls_token_weight = pos_embed[:, 0] + pos_embed_weight = pos_embed[:, (-1 * pos_h * pos_w):] + pos_embed_weight = pos_embed_weight.reshape( + 1, pos_h, pos_w, pos_embed.shape[2]).permute(0, 3, 1, 2) + pos_embed_weight = resize( + pos_embed_weight, size=input_shpae, align_corners=False, mode=mode) + cls_token_weight = cls_token_weight.unsqueeze(1) + pos_embed_weight = torch.flatten(pos_embed_weight, 2).transpose(1, 2) + pos_embed = torch.cat((cls_token_weight, pos_embed_weight), dim=1) + return pos_embed + + def forward(self, inputs): + B = inputs.shape[0] + + x, hw_shape = self.patch_embed(inputs) + + # stole cls_tokens impl from Phil Wang, thanks + cls_tokens = self.cls_token.expand(B, -1, -1) + x = torch.cat((cls_tokens, x), dim=1) + x = self._pos_embeding(x, hw_shape, self.pos_embed) + + if not self.with_cls_token: + # Remove class token for transformer encoder input + x = x[:, 1:] + + if self.pre_norm: + x = self.pre_ln(x) + + outs = [] + if self.out_origin: + if self.with_cls_token: + # Remove class token and reshape token for decoder head + out = x[:, 1:] + else: + out = x + B, _, C = out.shape + out = out.reshape(B, hw_shape[0], hw_shape[1], + C).permute(0, 3, 1, 2).contiguous() + if self.output_cls_token: + out = [out, x[:, 0]] + outs.append(out) + + for i, layer in enumerate(self.layers): + x = layer(x) + if i == len(self.layers) - 1: + if self.final_norm: + x = self.norm1(x) + if i in self.out_indices: + if self.with_cls_token: + # Remove class token and reshape token for decoder head + out = x[:, 1:] + else: + out = x + B, _, C = out.shape + out = out.reshape(B, hw_shape[0], hw_shape[1], + C).permute(0, 3, 1, 2).contiguous() + if self.output_cls_token: + out = [out, x[:, 0]] + outs.append(out) + + return tuple(outs) + + def train(self, mode=True): + super().train(mode) + if mode and self.norm_eval: + for m in self.modules(): + if isinstance(m, nn.LayerNorm): + m.eval() diff --git a/Seg_All_In_One_MMSeg/mmseg/models/backbones/vpd.py b/Seg_All_In_One_MMSeg/mmseg/models/backbones/vpd.py new file mode 100644 index 0000000..e0536d3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/backbones/vpd.py @@ -0,0 +1,395 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# ------------------------------------------------------------------------------ +# Adapted from https://github.com/wl-zhao/VPD/blob/main/vpd/models.py +# Original licence: MIT License +# ------------------------------------------------------------------------------ + +import math +from typing import List, Optional, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.model import BaseModule +from mmengine.runner import CheckpointLoader, load_checkpoint + +from mmseg.registry import MODELS +from mmseg.utils import ConfigType, OptConfigType + +try: + from ldm.modules.diffusionmodules.util import timestep_embedding + from ldm.util import instantiate_from_config + has_ldm = True +except ImportError: + has_ldm = False + + +def register_attention_control(model, controller): + """Registers a control function to manage attention within a model. + + Args: + model: The model to which attention is to be registered. + controller: The control function responsible for managing attention. + """ + + def ca_forward(self, place_in_unet): + """Custom forward method for attention. + + Args: + self: Reference to the current object. + place_in_unet: The location in UNet (down/mid/up). + + Returns: + The modified forward method. + """ + + def forward(x, context=None, mask=None): + h = self.heads + is_cross = context is not None + context = context or x # if context is None, use x + + q, k, v = self.to_q(x), self.to_k(context), self.to_v(context) + q, k, v = ( + tensor.view(tensor.shape[0] * h, tensor.shape[1], + tensor.shape[2] // h) for tensor in [q, k, v]) + + sim = torch.matmul(q, k.transpose(-2, -1)) * self.scale + + if mask is not None: + mask = mask.flatten(1).unsqueeze(1).repeat(h, 1, 1) + max_neg_value = -torch.finfo(sim.dtype).max + sim.masked_fill_(~mask, max_neg_value) + + attn = sim.softmax(dim=-1) + attn_mean = attn.view(h, attn.shape[0] // h, + *attn.shape[1:]).mean(0) + controller(attn_mean, is_cross, place_in_unet) + + out = torch.matmul(attn, v) + out = out.view(out.shape[0] // h, out.shape[1], out.shape[2] * h) + return self.to_out(out) + + return forward + + def register_recr(net_, count, place_in_unet): + """Recursive function to register the custom forward method to all + CrossAttention layers. + + Args: + net_: The network layer currently being processed. + count: The current count of layers processed. + place_in_unet: The location in UNet (down/mid/up). + + Returns: + The updated count of layers processed. + """ + if net_.__class__.__name__ == 'CrossAttention': + net_.forward = ca_forward(net_, place_in_unet) + return count + 1 + if hasattr(net_, 'children'): + return sum( + register_recr(child, 0, place_in_unet) + for child in net_.children()) + return count + + cross_att_count = sum( + register_recr(net[1], 0, place) for net, place in [ + (child, 'down') if 'input_blocks' in name else ( + child, 'up') if 'output_blocks' in name else + (child, + 'mid') if 'middle_block' in name else (None, None) # Default case + for name, child in model.diffusion_model.named_children() + ] if net is not None) + + controller.num_att_layers = cross_att_count + + +class AttentionStore: + """A class for storing attention information in the UNet model. + + Attributes: + base_size (int): Base size for storing attention information. + max_size (int): Maximum size for storing attention information. + """ + + def __init__(self, base_size=64, max_size=None): + """Initialize AttentionStore with default or custom sizes.""" + self.reset() + self.base_size = base_size + self.max_size = max_size or (base_size // 2) + self.num_att_layers = -1 + + @staticmethod + def get_empty_store(): + """Returns an empty store for holding attention values.""" + return { + key: [] + for key in [ + 'down_cross', 'mid_cross', 'up_cross', 'down_self', 'mid_self', + 'up_self' + ] + } + + def reset(self): + """Resets the step and attention stores to their initial states.""" + self.cur_step = 0 + self.cur_att_layer = 0 + self.step_store = self.get_empty_store() + self.attention_store = {} + + def forward(self, attn, is_cross: bool, place_in_unet: str): + """Processes a single forward step, storing the attention. + + Args: + attn: The attention tensor. + is_cross (bool): Whether it's cross attention. + place_in_unet (str): The location in UNet (down/mid/up). + + Returns: + The unmodified attention tensor. + """ + key = f"{place_in_unet}_{'cross' if is_cross else 'self'}" + if attn.shape[1] <= (self.max_size)**2: + self.step_store[key].append(attn) + return attn + + def between_steps(self): + """Processes and stores attention information between steps.""" + if not self.attention_store: + self.attention_store = self.step_store + else: + for key in self.attention_store: + self.attention_store[key] = [ + stored + step for stored, step in zip( + self.attention_store[key], self.step_store[key]) + ] + self.step_store = self.get_empty_store() + + def get_average_attention(self): + """Calculates and returns the average attention across all steps.""" + return { + key: [item for item in self.step_store[key]] + for key in self.step_store + } + + def __call__(self, attn, is_cross: bool, place_in_unet: str): + """Allows the class instance to be callable.""" + return self.forward(attn, is_cross, place_in_unet) + + @property + def num_uncond_att_layers(self): + """Returns the number of unconditional attention layers (default is + 0).""" + return 0 + + def step_callback(self, x_t): + """A placeholder for a step callback. + + Returns the input unchanged. + """ + return x_t + + +class UNetWrapper(nn.Module): + """A wrapper for UNet with optional attention mechanisms. + + Args: + unet (nn.Module): The UNet model to wrap + use_attn (bool): Whether to use attention. Defaults to True + base_size (int): Base size for the attention store. Defaults to 512 + max_attn_size (int, optional): Maximum size for the attention store. + Defaults to None + attn_selector (str): The types of attention to use. + Defaults to 'up_cross+down_cross' + """ + + def __init__(self, + unet, + use_attn=True, + base_size=512, + max_attn_size=None, + attn_selector='up_cross+down_cross'): + super().__init__() + + assert has_ldm, 'To use UNetWrapper, please install required ' \ + 'packages via `pip install -r requirements/optional.txt`.' + + self.unet = unet + self.attention_store = AttentionStore( + base_size=base_size // 8, max_size=max_attn_size) + self.attn_selector = attn_selector.split('+') + self.use_attn = use_attn + self.init_sizes(base_size) + if self.use_attn: + register_attention_control(unet, self.attention_store) + + def init_sizes(self, base_size): + """Initialize sizes based on the base size.""" + self.size16 = base_size // 32 + self.size32 = base_size // 16 + self.size64 = base_size // 8 + + def forward(self, x, timesteps=None, context=None, y=None, **kwargs): + """Forward pass through the model.""" + diffusion_model = self.unet.diffusion_model + if self.use_attn: + self.attention_store.reset() + hs, emb, out_list = self._unet_forward(x, timesteps, context, y, + diffusion_model) + if self.use_attn: + self._append_attn_to_output(out_list) + return out_list[::-1] + + def _unet_forward(self, x, timesteps, context, y, diffusion_model): + hs = [] + t_emb = timestep_embedding( + timesteps, diffusion_model.model_channels, repeat_only=False) + emb = diffusion_model.time_embed(t_emb) + h = x.type(diffusion_model.dtype) + for module in diffusion_model.input_blocks: + h = module(h, emb, context) + hs.append(h) + h = diffusion_model.middle_block(h, emb, context) + out_list = [] + for i_out, module in enumerate(diffusion_model.output_blocks): + h = torch.cat([h, hs.pop()], dim=1) + h = module(h, emb, context) + if i_out in [1, 4, 7]: + out_list.append(h) + h = h.type(x.dtype) + out_list.append(h) + return hs, emb, out_list + + def _append_attn_to_output(self, out_list): + avg_attn = self.attention_store.get_average_attention() + attns = {self.size16: [], self.size32: [], self.size64: []} + for k in self.attn_selector: + for up_attn in avg_attn[k]: + size = int(math.sqrt(up_attn.shape[1])) + up_attn = up_attn.transpose(-1, -2).reshape( + *up_attn.shape[:2], size, -1) + attns[size].append(up_attn) + attn16 = torch.stack(attns[self.size16]).mean(0) + attn32 = torch.stack(attns[self.size32]).mean(0) + attn64 = torch.stack(attns[self.size64]).mean(0) if len( + attns[self.size64]) > 0 else None + out_list[1] = torch.cat([out_list[1], attn16], dim=1) + out_list[2] = torch.cat([out_list[2], attn32], dim=1) + if attn64 is not None: + out_list[3] = torch.cat([out_list[3], attn64], dim=1) + + +class TextAdapter(nn.Module): + """A PyTorch Module that serves as a text adapter. + + This module takes text embeddings and adjusts them based on a scaling + factor gamma. + """ + + def __init__(self, text_dim=768): + super().__init__() + self.fc = nn.Sequential( + nn.Linear(text_dim, text_dim), nn.GELU(), + nn.Linear(text_dim, text_dim)) + + def forward(self, texts, gamma): + texts_after = self.fc(texts) + texts = texts + gamma * texts_after + return texts + + +@MODELS.register_module() +class VPD(BaseModule): + """VPD (Visual Perception Diffusion) model. + + .. _`VPD`: https://arxiv.org/abs/2303.02153 + + Args: + diffusion_cfg (dict): Configuration for diffusion model. + class_embed_path (str): Path for class embeddings. + unet_cfg (dict, optional): Configuration for U-Net. + gamma (float, optional): Gamma for text adaptation. Defaults to 1e-4. + class_embed_select (bool, optional): If True, enables class embedding + selection. Defaults to False. + pad_shape (Optional[Union[int, List[int]]], optional): Padding shape. + Defaults to None. + pad_val (Union[int, List[int]], optional): Padding value. + Defaults to 0. + init_cfg (dict, optional): Configuration for network initialization. + """ + + def __init__(self, + diffusion_cfg: ConfigType, + class_embed_path: str, + unet_cfg: OptConfigType = dict(), + gamma: float = 1e-4, + class_embed_select=False, + pad_shape: Optional[Union[int, List[int]]] = None, + pad_val: Union[int, List[int]] = 0, + init_cfg: OptConfigType = None): + + super().__init__(init_cfg=init_cfg) + + assert has_ldm, 'To use VPD model, please install required packages' \ + ' via `pip install -r requirements/optional.txt`.' + + if pad_shape is not None: + if not isinstance(pad_shape, (list, tuple)): + pad_shape = (pad_shape, pad_shape) + + self.pad_shape = pad_shape + self.pad_val = pad_val + + # diffusion model + diffusion_checkpoint = diffusion_cfg.pop('checkpoint', None) + sd_model = instantiate_from_config(diffusion_cfg) + if diffusion_checkpoint is not None: + load_checkpoint(sd_model, diffusion_checkpoint, strict=False) + + self.encoder_vq = sd_model.first_stage_model + self.unet = UNetWrapper(sd_model.model, **unet_cfg) + + # class embeddings & text adapter + class_embeddings = CheckpointLoader.load_checkpoint(class_embed_path) + text_dim = class_embeddings.size(-1) + self.text_adapter = TextAdapter(text_dim=text_dim) + self.class_embed_select = class_embed_select + if class_embed_select: + class_embeddings = torch.cat( + (class_embeddings, class_embeddings.mean(dim=0, + keepdims=True)), + dim=0) + self.register_buffer('class_embeddings', class_embeddings) + self.gamma = nn.Parameter(torch.ones(text_dim) * gamma) + + def forward(self, x): + """Extract features from images.""" + + # calculate cross-attn map + if self.class_embed_select: + if isinstance(x, (tuple, list)): + x, class_ids = x[:2] + class_ids = class_ids.tolist() + else: + class_ids = [-1] * x.size(0) + class_embeddings = self.class_embeddings[class_ids] + c_crossattn = self.text_adapter(class_embeddings, self.gamma) + c_crossattn = c_crossattn.unsqueeze(1) + else: + class_embeddings = self.class_embeddings + c_crossattn = self.text_adapter(class_embeddings, self.gamma) + c_crossattn = c_crossattn.unsqueeze(0).repeat(x.size(0), 1, 1) + + # pad to required input shape for pretrained diffusion model + if self.pad_shape is not None: + pad_width = max(0, self.pad_shape[1] - x.shape[-1]) + pad_height = max(0, self.pad_shape[0] - x.shape[-2]) + x = F.pad(x, (0, pad_width, 0, pad_height), value=self.pad_val) + + # forward the denoising model + with torch.no_grad(): + latents = self.encoder_vq.encode(x).mode().detach() + t = torch.ones((x.shape[0], ), device=x.device).long() + outs = self.unet(latents, t, context=c_crossattn) + + return outs diff --git a/Seg_All_In_One_MMSeg/mmseg/models/builder.py b/Seg_All_In_One_MMSeg/mmseg/models/builder.py new file mode 100644 index 0000000..081c646 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/builder.py @@ -0,0 +1,52 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +from mmseg.registry import MODELS + +BACKBONES = MODELS +NECKS = MODELS +HEADS = MODELS +LOSSES = MODELS +SEGMENTORS = MODELS + + +def build_backbone(cfg): + """Build backbone.""" + warnings.warn('``build_backbone`` would be deprecated soon, please use ' + '``mmseg.registry.MODELS.build()`` ') + return BACKBONES.build(cfg) + + +def build_neck(cfg): + """Build neck.""" + warnings.warn('``build_neck`` would be deprecated soon, please use ' + '``mmseg.registry.MODELS.build()`` ') + return NECKS.build(cfg) + + +def build_head(cfg): + """Build head.""" + warnings.warn('``build_head`` would be deprecated soon, please use ' + '``mmseg.registry.MODELS.build()`` ') + return HEADS.build(cfg) + + +def build_loss(cfg): + """Build loss.""" + warnings.warn('``build_loss`` would be deprecated soon, please use ' + '``mmseg.registry.MODELS.build()`` ') + return LOSSES.build(cfg) + + +def build_segmentor(cfg, train_cfg=None, test_cfg=None): + """Build segmentor.""" + if train_cfg is not None or test_cfg is not None: + warnings.warn( + 'train_cfg and test_cfg is deprecated, ' + 'please specify them in model', UserWarning) + assert cfg.get('train_cfg') is None or train_cfg is None, \ + 'train_cfg specified in both outer field and model field ' + assert cfg.get('test_cfg') is None or test_cfg is None, \ + 'test_cfg specified in both outer field and model field ' + return SEGMENTORS.build( + cfg, default_args=dict(train_cfg=train_cfg, test_cfg=test_cfg)) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/data_preprocessor.py b/Seg_All_In_One_MMSeg/mmseg/models/data_preprocessor.py new file mode 100644 index 0000000..8d32bc6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/data_preprocessor.py @@ -0,0 +1,151 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from numbers import Number +from typing import Any, Dict, List, Optional, Sequence + +import torch +from mmengine.model import BaseDataPreprocessor + +from mmseg.registry import MODELS +from mmseg.utils import stack_batch + + +@MODELS.register_module() +class SegDataPreProcessor(BaseDataPreprocessor): + """Image pre-processor for segmentation tasks. + + Comparing with the :class:`mmengine.ImgDataPreprocessor`, + + 1. It won't do normalization if ``mean`` is not specified. + 2. It does normalization and color space conversion after stacking batch. + 3. It supports batch augmentations like mixup and cutmix. + + + It provides the data pre-processing as follows + + - Collate and move data to the target device. + - Pad inputs to the input size with defined ``pad_val``, and pad seg map + with defined ``seg_pad_val``. + - Stack inputs to batch_inputs. + - Convert inputs from bgr to rgb if the shape of input is (3, H, W). + - Normalize image with defined std and mean. + - Do batch augmentations like Mixup and Cutmix during training. + + Args: + mean (Sequence[Number], optional): The pixel mean of R, G, B channels. + Defaults to None. + std (Sequence[Number], optional): The pixel standard deviation of + R, G, B channels. Defaults to None. + size (tuple, optional): Fixed padding size. + size_divisor (int, optional): The divisor of padded size. + pad_val (float, optional): Padding value. Default: 0. + seg_pad_val (float, optional): Padding value of segmentation map. + Default: 255. + padding_mode (str): Type of padding. Default: constant. + - constant: pads with a constant value, this value is specified + with pad_val. + bgr_to_rgb (bool): whether to convert image from BGR to RGB. + Defaults to False. + rgb_to_bgr (bool): whether to convert image from RGB to RGB. + Defaults to False. + batch_augments (list[dict], optional): Batch-level augmentations + test_cfg (dict, optional): The padding size config in testing, if not + specify, will use `size` and `size_divisor` params as default. + Defaults to None, only supports keys `size` or `size_divisor`. + """ + + def __init__( + self, + mean: Sequence[Number] = None, + std: Sequence[Number] = None, + size: Optional[tuple] = None, + size_divisor: Optional[int] = None, + pad_val: Number = 0, + seg_pad_val: Number = 255, + bgr_to_rgb: bool = False, + rgb_to_bgr: bool = False, + batch_augments: Optional[List[dict]] = None, + test_cfg: dict = None, + ): + super().__init__() + self.size = size + self.size_divisor = size_divisor + self.pad_val = pad_val + self.seg_pad_val = seg_pad_val + + assert not (bgr_to_rgb and rgb_to_bgr), ( + '`bgr2rgb` and `rgb2bgr` cannot be set to True at the same time') + self.channel_conversion = rgb_to_bgr or bgr_to_rgb + + if mean is not None: + assert std is not None, 'To enable the normalization in ' \ + 'preprocessing, please specify both ' \ + '`mean` and `std`.' + # Enable the normalization in preprocessing. + self._enable_normalize = True + self.register_buffer('mean', + torch.tensor(mean).view(-1, 1, 1), False) + self.register_buffer('std', + torch.tensor(std).view(-1, 1, 1), False) + else: + self._enable_normalize = False + + # TODO: support batch augmentations. + self.batch_augments = batch_augments + + # Support different padding methods in testing + self.test_cfg = test_cfg + + def forward(self, data: dict, training: bool = False) -> Dict[str, Any]: + """Perform normalization、padding and bgr2rgb conversion based on + ``BaseDataPreprocessor``. + + Args: + data (dict): data sampled from dataloader. + training (bool): Whether to enable training time augmentation. + + Returns: + Dict: Data in the same format as the model input. + """ + data = self.cast_data(data) # type: ignore + inputs = data['inputs'] + data_samples = data.get('data_samples', None) + # TODO: whether normalize should be after stack_batch + if self.channel_conversion and inputs[0].size(0) == 3: + inputs = [_input[[2, 1, 0], ...] for _input in inputs] + + inputs = [_input.float() for _input in inputs] + if self._enable_normalize: + inputs = [(_input - self.mean) / self.std for _input in inputs] + + if training: + assert data_samples is not None, ('During training, ', + '`data_samples` must be define.') + inputs, data_samples = stack_batch( + inputs=inputs, + data_samples=data_samples, + size=self.size, + size_divisor=self.size_divisor, + pad_val=self.pad_val, + seg_pad_val=self.seg_pad_val) + + if self.batch_augments is not None: + inputs, data_samples = self.batch_augments( + inputs, data_samples) + else: + img_size = inputs[0].shape[1:] + assert all(input_.shape[1:] == img_size for input_ in inputs), \ + 'The image size in a batch should be the same.' + # pad images when testing + if self.test_cfg: + inputs, padded_samples = stack_batch( + inputs=inputs, + size=self.test_cfg.get('size', None), + size_divisor=self.test_cfg.get('size_divisor', None), + pad_val=self.pad_val, + seg_pad_val=self.seg_pad_val) + for data_sample, pad_info in zip(data_samples, padded_samples): + data_sample.set_metainfo({**pad_info}) + else: + inputs = torch.stack(inputs, dim=0) + + return dict(inputs=inputs, data_samples=data_samples) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/__init__.py new file mode 100644 index 0000000..4229763 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/__init__.py @@ -0,0 +1,48 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .ann_head import ANNHead +from .apc_head import APCHead +from .aspp_head import ASPPHead +from .cc_head import CCHead +from .da_head import DAHead +from .ddr_head import DDRHead +from .dm_head import DMHead +from .dnl_head import DNLHead +from .dpt_head import DPTHead +from .ema_head import EMAHead +from .enc_head import EncHead +from .fcn_head import FCNHead +from .fpn_head import FPNHead +from .gc_head import GCHead +from .ham_head import LightHamHead +from .isa_head import ISAHead +from .knet_head import IterativeDecodeHead, KernelUpdateHead, KernelUpdator +from .lraspp_head import LRASPPHead +from .mask2former_head import Mask2FormerHead +from .maskformer_head import MaskFormerHead +from .nl_head import NLHead +from .ocr_head import OCRHead +from .pid_head import PIDHead +from .point_head import PointHead +from .psa_head import PSAHead +from .psp_head import PSPHead +from .san_head import SideAdapterCLIPHead +from .segformer_head import SegformerHead +from .segmenter_mask_head import SegmenterMaskTransformerHead +from .sep_aspp_head import DepthwiseSeparableASPPHead +from .sep_fcn_head import DepthwiseSeparableFCNHead +from .setr_mla_head import SETRMLAHead +from .setr_up_head import SETRUPHead +from .stdc_head import STDCHead +from .uper_head import UPerHead +from .vpd_depth_head import VPDDepthHead + +__all__ = [ + 'FCNHead', 'PSPHead', 'ASPPHead', 'PSAHead', 'NLHead', 'GCHead', 'CCHead', + 'UPerHead', 'DepthwiseSeparableASPPHead', 'ANNHead', 'DAHead', 'OCRHead', + 'EncHead', 'DepthwiseSeparableFCNHead', 'FPNHead', 'EMAHead', 'DNLHead', + 'PointHead', 'APCHead', 'DMHead', 'LRASPPHead', 'SETRUPHead', + 'SETRMLAHead', 'DPTHead', 'SETRMLAHead', 'SegmenterMaskTransformerHead', + 'SegformerHead', 'ISAHead', 'STDCHead', 'IterativeDecodeHead', + 'KernelUpdateHead', 'KernelUpdator', 'MaskFormerHead', 'Mask2FormerHead', + 'LightHamHead', 'PIDHead', 'DDRHead', 'VPDDepthHead', 'SideAdapterCLIPHead' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ann_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ann_head.py new file mode 100644 index 0000000..2b40ef5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ann_head.py @@ -0,0 +1,245 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import SelfAttentionBlock as _SelfAttentionBlock +from .decode_head import BaseDecodeHead + + +class PPMConcat(nn.ModuleList): + """Pyramid Pooling Module that only concat the features of each layer. + + Args: + pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid + Module. + """ + + def __init__(self, pool_scales=(1, 3, 6, 8)): + super().__init__( + [nn.AdaptiveAvgPool2d(pool_scale) for pool_scale in pool_scales]) + + def forward(self, feats): + """Forward function.""" + ppm_outs = [] + for ppm in self: + ppm_out = ppm(feats) + ppm_outs.append(ppm_out.view(*feats.shape[:2], -1)) + concat_outs = torch.cat(ppm_outs, dim=2) + return concat_outs + + +class SelfAttentionBlock(_SelfAttentionBlock): + """Make a ANN used SelfAttentionBlock. + + Args: + low_in_channels (int): Input channels of lower level feature, + which is the key feature for self-attention. + high_in_channels (int): Input channels of higher level feature, + which is the query feature for self-attention. + channels (int): Output channels of key/query transform. + out_channels (int): Output channels. + share_key_query (bool): Whether share projection weight between key + and query projection. + query_scale (int): The scale of query feature map. + key_pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid + Module of key feature. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict|None): Config of activation layers. + """ + + def __init__(self, low_in_channels, high_in_channels, channels, + out_channels, share_key_query, query_scale, key_pool_scales, + conv_cfg, norm_cfg, act_cfg): + key_psp = PPMConcat(key_pool_scales) + if query_scale > 1: + query_downsample = nn.MaxPool2d(kernel_size=query_scale) + else: + query_downsample = None + super().__init__( + key_in_channels=low_in_channels, + query_in_channels=high_in_channels, + channels=channels, + out_channels=out_channels, + share_key_query=share_key_query, + query_downsample=query_downsample, + key_downsample=key_psp, + key_query_num_convs=1, + key_query_norm=True, + value_out_num_convs=1, + value_out_norm=False, + matmul_norm=True, + with_out=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + +class AFNB(nn.Module): + """Asymmetric Fusion Non-local Block(AFNB) + + Args: + low_in_channels (int): Input channels of lower level feature, + which is the key feature for self-attention. + high_in_channels (int): Input channels of higher level feature, + which is the query feature for self-attention. + channels (int): Output channels of key/query transform. + out_channels (int): Output channels. + and query projection. + query_scales (tuple[int]): The scales of query feature map. + Default: (1,) + key_pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid + Module of key feature. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict|None): Config of activation layers. + """ + + def __init__(self, low_in_channels, high_in_channels, channels, + out_channels, query_scales, key_pool_scales, conv_cfg, + norm_cfg, act_cfg): + super().__init__() + self.stages = nn.ModuleList() + for query_scale in query_scales: + self.stages.append( + SelfAttentionBlock( + low_in_channels=low_in_channels, + high_in_channels=high_in_channels, + channels=channels, + out_channels=out_channels, + share_key_query=False, + query_scale=query_scale, + key_pool_scales=key_pool_scales, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.bottleneck = ConvModule( + out_channels + high_in_channels, + out_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + def forward(self, low_feats, high_feats): + """Forward function.""" + priors = [stage(high_feats, low_feats) for stage in self.stages] + context = torch.stack(priors, dim=0).sum(dim=0) + output = self.bottleneck(torch.cat([context, high_feats], 1)) + return output + + +class APNB(nn.Module): + """Asymmetric Pyramid Non-local Block (APNB) + + Args: + in_channels (int): Input channels of key/query feature, + which is the key feature for self-attention. + channels (int): Output channels of key/query transform. + out_channels (int): Output channels. + query_scales (tuple[int]): The scales of query feature map. + Default: (1,) + key_pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid + Module of key feature. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict|None): Config of activation layers. + """ + + def __init__(self, in_channels, channels, out_channels, query_scales, + key_pool_scales, conv_cfg, norm_cfg, act_cfg): + super().__init__() + self.stages = nn.ModuleList() + for query_scale in query_scales: + self.stages.append( + SelfAttentionBlock( + low_in_channels=in_channels, + high_in_channels=in_channels, + channels=channels, + out_channels=out_channels, + share_key_query=True, + query_scale=query_scale, + key_pool_scales=key_pool_scales, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.bottleneck = ConvModule( + 2 * in_channels, + out_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, feats): + """Forward function.""" + priors = [stage(feats, feats) for stage in self.stages] + context = torch.stack(priors, dim=0).sum(dim=0) + output = self.bottleneck(torch.cat([context, feats], 1)) + return output + + +@MODELS.register_module() +class ANNHead(BaseDecodeHead): + """Asymmetric Non-local Neural Networks for Semantic Segmentation. + + This head is the implementation of `ANNNet + `_. + + Args: + project_channels (int): Projection channels for Nonlocal. + query_scales (tuple[int]): The scales of query feature map. + Default: (1,) + key_pool_scales (tuple[int]): The pooling scales of key feature map. + Default: (1, 3, 6, 8). + """ + + def __init__(self, + project_channels, + query_scales=(1, ), + key_pool_scales=(1, 3, 6, 8), + **kwargs): + super().__init__(input_transform='multiple_select', **kwargs) + assert len(self.in_channels) == 2 + low_in_channels, high_in_channels = self.in_channels + self.project_channels = project_channels + self.fusion = AFNB( + low_in_channels=low_in_channels, + high_in_channels=high_in_channels, + out_channels=high_in_channels, + channels=project_channels, + query_scales=query_scales, + key_pool_scales=key_pool_scales, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.bottleneck = ConvModule( + high_in_channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.context = APNB( + in_channels=self.channels, + out_channels=self.channels, + channels=project_channels, + query_scales=query_scales, + key_pool_scales=key_pool_scales, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, inputs): + """Forward function.""" + low_feats, high_feats = self._transform_inputs(inputs) + output = self.fusion(low_feats, high_feats) + output = self.dropout(output) + output = self.bottleneck(output) + output = self.context(output) + output = self.cls_seg(output) + + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/apc_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/apc_head.py new file mode 100644 index 0000000..728f396 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/apc_head.py @@ -0,0 +1,159 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import resize +from .decode_head import BaseDecodeHead + + +class ACM(nn.Module): + """Adaptive Context Module used in APCNet. + + Args: + pool_scale (int): Pooling scale used in Adaptive Context + Module to extract region features. + fusion (bool): Add one conv to fuse residual feature. + in_channels (int): Input channels. + channels (int): Channels after modules, before conv_seg. + conv_cfg (dict | None): Config of conv layers. + norm_cfg (dict | None): Config of norm layers. + act_cfg (dict): Config of activation layers. + """ + + def __init__(self, pool_scale, fusion, in_channels, channels, conv_cfg, + norm_cfg, act_cfg): + super().__init__() + self.pool_scale = pool_scale + self.fusion = fusion + self.in_channels = in_channels + self.channels = channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.pooled_redu_conv = ConvModule( + self.in_channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.input_redu_conv = ConvModule( + self.in_channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.global_info = ConvModule( + self.channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.gla = nn.Conv2d(self.channels, self.pool_scale**2, 1, 1, 0) + + self.residual_conv = ConvModule( + self.channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + if self.fusion: + self.fusion_conv = ConvModule( + self.channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, x): + """Forward function.""" + pooled_x = F.adaptive_avg_pool2d(x, self.pool_scale) + # [batch_size, channels, h, w] + x = self.input_redu_conv(x) + # [batch_size, channels, pool_scale, pool_scale] + pooled_x = self.pooled_redu_conv(pooled_x) + batch_size = x.size(0) + # [batch_size, pool_scale * pool_scale, channels] + pooled_x = pooled_x.view(batch_size, self.channels, + -1).permute(0, 2, 1).contiguous() + # [batch_size, h * w, pool_scale * pool_scale] + affinity_matrix = self.gla(x + resize( + self.global_info(F.adaptive_avg_pool2d(x, 1)), size=x.shape[2:]) + ).permute(0, 2, 3, 1).reshape( + batch_size, -1, self.pool_scale**2) + affinity_matrix = F.sigmoid(affinity_matrix) + # [batch_size, h * w, channels] + z_out = torch.matmul(affinity_matrix, pooled_x) + # [batch_size, channels, h * w] + z_out = z_out.permute(0, 2, 1).contiguous() + # [batch_size, channels, h, w] + z_out = z_out.view(batch_size, self.channels, x.size(2), x.size(3)) + z_out = self.residual_conv(z_out) + z_out = F.relu(z_out + x) + if self.fusion: + z_out = self.fusion_conv(z_out) + + return z_out + + +@MODELS.register_module() +class APCHead(BaseDecodeHead): + """Adaptive Pyramid Context Network for Semantic Segmentation. + + This head is the implementation of + `APCNet `_. + + Args: + pool_scales (tuple[int]): Pooling scales used in Adaptive Context + Module. Default: (1, 2, 3, 6). + fusion (bool): Add one conv to fuse residual feature. + """ + + def __init__(self, pool_scales=(1, 2, 3, 6), fusion=True, **kwargs): + super().__init__(**kwargs) + assert isinstance(pool_scales, (list, tuple)) + self.pool_scales = pool_scales + self.fusion = fusion + acm_modules = [] + for pool_scale in self.pool_scales: + acm_modules.append( + ACM(pool_scale, + self.fusion, + self.in_channels, + self.channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + self.acm_modules = nn.ModuleList(acm_modules) + self.bottleneck = ConvModule( + self.in_channels + len(pool_scales) * self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + acm_outs = [x] + for acm_module in self.acm_modules: + acm_outs.append(acm_module(x)) + acm_outs = torch.cat(acm_outs, dim=1) + output = self.bottleneck(acm_outs) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/aspp_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/aspp_head.py new file mode 100644 index 0000000..6d7185d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/aspp_head.py @@ -0,0 +1,122 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import resize +from .decode_head import BaseDecodeHead + + +class ASPPModule(nn.ModuleList): + """Atrous Spatial Pyramid Pooling (ASPP) Module. + + Args: + dilations (tuple[int]): Dilation rate of each layer. + in_channels (int): Input channels. + channels (int): Channels after modules, before conv_seg. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict): Config of activation layers. + """ + + def __init__(self, dilations, in_channels, channels, conv_cfg, norm_cfg, + act_cfg): + super().__init__() + self.dilations = dilations + self.in_channels = in_channels + self.channels = channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + for dilation in dilations: + self.append( + ConvModule( + self.in_channels, + self.channels, + 1 if dilation == 1 else 3, + dilation=dilation, + padding=0 if dilation == 1 else dilation, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + + def forward(self, x): + """Forward function.""" + aspp_outs = [] + for aspp_module in self: + aspp_outs.append(aspp_module(x)) + + return aspp_outs + + +@MODELS.register_module() +class ASPPHead(BaseDecodeHead): + """Rethinking Atrous Convolution for Semantic Image Segmentation. + + This head is the implementation of `DeepLabV3 + `_. + + Args: + dilations (tuple[int]): Dilation rates for ASPP module. + Default: (1, 6, 12, 18). + """ + + def __init__(self, dilations=(1, 6, 12, 18), **kwargs): + super().__init__(**kwargs) + assert isinstance(dilations, (list, tuple)) + self.dilations = dilations + self.image_pool = nn.Sequential( + nn.AdaptiveAvgPool2d(1), + ConvModule( + self.in_channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + self.aspp_modules = ASPPModule( + dilations, + self.in_channels, + self.channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.bottleneck = ConvModule( + (len(dilations) + 1) * self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def _forward_feature(self, inputs): + """Forward function for feature maps before classifying each pixel with + ``self.cls_seg`` fc. + + Args: + inputs (list[Tensor]): List of multi-level img features. + + Returns: + feats (Tensor): A tensor of shape (batch_size, self.channels, + H, W) which is feature map for last layer of decoder head. + """ + x = self._transform_inputs(inputs) + aspp_outs = [ + resize( + self.image_pool(x), + size=x.size()[2:], + mode='bilinear', + align_corners=self.align_corners) + ] + aspp_outs.extend(self.aspp_modules(x)) + aspp_outs = torch.cat(aspp_outs, dim=1) + feats = self.bottleneck(aspp_outs) + return feats + + def forward(self, inputs): + """Forward function.""" + output = self._forward_feature(inputs) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/cascade_decode_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/cascade_decode_head.py new file mode 100644 index 0000000..fe2bcb9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/cascade_decode_head.py @@ -0,0 +1,62 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import List + +from torch import Tensor + +from mmseg.utils import ConfigType +from .decode_head import BaseDecodeHead + + +class BaseCascadeDecodeHead(BaseDecodeHead, metaclass=ABCMeta): + """Base class for cascade decode head used in + :class:`CascadeEncoderDecoder.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @abstractmethod + def forward(self, inputs, prev_output): + """Placeholder of forward function.""" + pass + + def loss(self, inputs: List[Tensor], prev_output: Tensor, + batch_data_samples: List[dict], train_cfg: ConfigType) -> Tensor: + """Forward function for training. + + Args: + inputs (List[Tensor]): List of multi-level img features. + prev_output (Tensor): The output of previous decode head. + batch_data_samples (List[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `metainfo` and `gt_sem_seg`. + train_cfg (dict): The training config. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + seg_logits = self.forward(inputs, prev_output) + losses = self.loss_by_feat(seg_logits, batch_data_samples) + + return losses + + def predict(self, inputs: List[Tensor], prev_output: Tensor, + batch_img_metas: List[dict], tese_cfg: ConfigType): + """Forward function for testing. + + Args: + inputs (List[Tensor]): List of multi-level img features. + prev_output (Tensor): The output of previous decode head. + batch_img_metas (dict): List Image info where each dict may also + contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', and 'pad_shape'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + test_cfg (dict): The testing config. + + Returns: + Tensor: Output segmentation map. + """ + seg_logits = self.forward(inputs, prev_output) + + return self.predict_by_feat(seg_logits, batch_img_metas) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/cc_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/cc_head.py new file mode 100644 index 0000000..e9075a2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/cc_head.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.registry import MODELS +from .fcn_head import FCNHead + +try: + from mmcv.ops import CrissCrossAttention +except ModuleNotFoundError: + CrissCrossAttention = None + + +@MODELS.register_module() +class CCHead(FCNHead): + """CCNet: Criss-Cross Attention for Semantic Segmentation. + + This head is the implementation of `CCNet + `_. + + Args: + recurrence (int): Number of recurrence of Criss Cross Attention + module. Default: 2. + """ + + def __init__(self, recurrence=2, **kwargs): + if CrissCrossAttention is None: + raise RuntimeError('Please install mmcv-full for ' + 'CrissCrossAttention ops') + super().__init__(num_convs=2, **kwargs) + self.recurrence = recurrence + self.cca = CrissCrossAttention(self.channels) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + output = self.convs[0](x) + for _ in range(self.recurrence): + output = self.cca(output) + output = self.convs[1](output) + if self.concat_input: + output = self.conv_cat(torch.cat([x, output], dim=1)) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/da_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/da_head.py new file mode 100644 index 0000000..d872143 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/da_head.py @@ -0,0 +1,184 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +import torch.nn.functional as F +from mmcv.cnn import ConvModule, Scale +from torch import Tensor, nn + +from mmseg.registry import MODELS +from mmseg.utils import SampleList, add_prefix +from ..utils import SelfAttentionBlock as _SelfAttentionBlock +from .decode_head import BaseDecodeHead + + +class PAM(_SelfAttentionBlock): + """Position Attention Module (PAM) + + Args: + in_channels (int): Input channels of key/query feature. + channels (int): Output channels of key/query transform. + """ + + def __init__(self, in_channels, channels): + super().__init__( + key_in_channels=in_channels, + query_in_channels=in_channels, + channels=channels, + out_channels=in_channels, + share_key_query=False, + query_downsample=None, + key_downsample=None, + key_query_num_convs=1, + key_query_norm=False, + value_out_num_convs=1, + value_out_norm=False, + matmul_norm=False, + with_out=False, + conv_cfg=None, + norm_cfg=None, + act_cfg=None) + + self.gamma = Scale(0) + + def forward(self, x): + """Forward function.""" + out = super().forward(x, x) + + out = self.gamma(out) + x + return out + + +class CAM(nn.Module): + """Channel Attention Module (CAM)""" + + def __init__(self): + super().__init__() + self.gamma = Scale(0) + + def forward(self, x): + """Forward function.""" + batch_size, channels, height, width = x.size() + proj_query = x.view(batch_size, channels, -1) + proj_key = x.view(batch_size, channels, -1).permute(0, 2, 1) + energy = torch.bmm(proj_query, proj_key) + energy_new = torch.max( + energy, -1, keepdim=True)[0].expand_as(energy) - energy + attention = F.softmax(energy_new, dim=-1) + proj_value = x.view(batch_size, channels, -1) + + out = torch.bmm(attention, proj_value) + out = out.view(batch_size, channels, height, width) + + out = self.gamma(out) + x + return out + + +@MODELS.register_module() +class DAHead(BaseDecodeHead): + """Dual Attention Network for Scene Segmentation. + + This head is the implementation of `DANet + `_. + + Args: + pam_channels (int): The channels of Position Attention Module(PAM). + """ + + def __init__(self, pam_channels, **kwargs): + super().__init__(**kwargs) + self.pam_channels = pam_channels + self.pam_in_conv = ConvModule( + self.in_channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.pam = PAM(self.channels, pam_channels) + self.pam_out_conv = ConvModule( + self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.pam_conv_seg = nn.Conv2d( + self.channels, self.num_classes, kernel_size=1) + + self.cam_in_conv = ConvModule( + self.in_channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.cam = CAM() + self.cam_out_conv = ConvModule( + self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.cam_conv_seg = nn.Conv2d( + self.channels, self.num_classes, kernel_size=1) + + def pam_cls_seg(self, feat): + """PAM feature classification.""" + if self.dropout is not None: + feat = self.dropout(feat) + output = self.pam_conv_seg(feat) + return output + + def cam_cls_seg(self, feat): + """CAM feature classification.""" + if self.dropout is not None: + feat = self.dropout(feat) + output = self.cam_conv_seg(feat) + return output + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + pam_feat = self.pam_in_conv(x) + pam_feat = self.pam(pam_feat) + pam_feat = self.pam_out_conv(pam_feat) + pam_out = self.pam_cls_seg(pam_feat) + + cam_feat = self.cam_in_conv(x) + cam_feat = self.cam(cam_feat) + cam_feat = self.cam_out_conv(cam_feat) + cam_out = self.cam_cls_seg(cam_feat) + + feat_sum = pam_feat + cam_feat + pam_cam_out = self.cls_seg(feat_sum) + + return pam_cam_out, pam_out, cam_out + + def predict(self, inputs, batch_img_metas: List[dict], test_cfg, + **kwargs) -> List[Tensor]: + """Forward function for testing, only ``pam_cam`` is used.""" + seg_logits = self.forward(inputs)[0] + return self.predict_by_feat(seg_logits, batch_img_metas, **kwargs) + + def loss_by_feat(self, seg_logit: Tuple[Tensor], + batch_data_samples: SampleList, **kwargs) -> dict: + """Compute ``pam_cam``, ``pam``, ``cam`` loss.""" + pam_cam_seg_logit, pam_seg_logit, cam_seg_logit = seg_logit + loss = dict() + loss.update( + add_prefix( + super().loss_by_feat(pam_cam_seg_logit, batch_data_samples), + 'pam_cam')) + loss.update( + add_prefix(super().loss_by_feat(pam_seg_logit, batch_data_samples), + 'pam')) + loss.update( + add_prefix(super().loss_by_feat(cam_seg_logit, batch_data_samples), + 'cam')) + return loss diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ddr_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ddr_head.py new file mode 100644 index 0000000..ba26d65 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ddr_head.py @@ -0,0 +1,116 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Tuple, Union + +import torch.nn as nn +from mmcv.cnn import ConvModule, build_activation_layer, build_norm_layer +from torch import Tensor + +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.models.losses import accuracy +from mmseg.models.utils import resize +from mmseg.registry import MODELS +from mmseg.utils import OptConfigType, SampleList + + +@MODELS.register_module() +class DDRHead(BaseDecodeHead): + """Decode head for DDRNet. + + Args: + in_channels (int): Number of input channels. + channels (int): Number of output channels. + num_classes (int): Number of classes. + norm_cfg (dict, optional): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict, optional): Config dict for activation layer. + Default: dict(type='ReLU', inplace=True). + """ + + def __init__(self, + in_channels: int, + channels: int, + num_classes: int, + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + **kwargs): + super().__init__( + in_channels, + channels, + num_classes=num_classes, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **kwargs) + + self.head = self._make_base_head(self.in_channels, self.channels) + self.aux_head = self._make_base_head(self.in_channels // 2, + self.channels) + self.aux_cls_seg = nn.Conv2d( + self.channels, self.out_channels, kernel_size=1) + + def init_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_( + m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + + def forward( + self, + inputs: Union[Tensor, + Tuple[Tensor]]) -> Union[Tensor, Tuple[Tensor]]: + if self.training: + c3_feat, c5_feat = inputs + x_c = self.head(c5_feat) + x_c = self.cls_seg(x_c) + x_s = self.aux_head(c3_feat) + x_s = self.aux_cls_seg(x_s) + + return x_c, x_s + else: + x_c = self.head(inputs) + x_c = self.cls_seg(x_c) + return x_c + + def _make_base_head(self, in_channels: int, + channels: int) -> nn.Sequential: + layers = [ + ConvModule( + in_channels, + channels, + kernel_size=3, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + order=('norm', 'act', 'conv')), + build_norm_layer(self.norm_cfg, channels)[1], + build_activation_layer(self.act_cfg), + ] + + return nn.Sequential(*layers) + + def loss_by_feat(self, seg_logits: Tuple[Tensor], + batch_data_samples: SampleList) -> dict: + loss = dict() + context_logit, spatial_logit = seg_logits + seg_label = self._stack_batch_gt(batch_data_samples) + + context_logit = resize( + context_logit, + size=seg_label.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + spatial_logit = resize( + spatial_logit, + size=seg_label.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + seg_label = seg_label.squeeze(1) + + loss['loss_context'] = self.loss_decode[0](context_logit, seg_label) + loss['loss_spatial'] = self.loss_decode[1](spatial_logit, seg_label) + loss['acc_seg'] = accuracy( + context_logit, seg_label, ignore_index=self.ignore_index) + + return loss diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/decode_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/decode_head.py new file mode 100644 index 0000000..fd53afe --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/decode_head.py @@ -0,0 +1,366 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings +from abc import ABCMeta, abstractmethod +from typing import List, Tuple + +import torch +import torch.nn as nn +from mmengine.model import BaseModule +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.structures import build_pixel_sampler +from mmseg.utils import ConfigType, SampleList +from ..losses import accuracy +from ..utils import resize + + +class BaseDecodeHead(BaseModule, metaclass=ABCMeta): + """Base class for BaseDecodeHead. + + 1. The ``init_weights`` method is used to initialize decode_head's + model parameters. After segmentor initialization, ``init_weights`` + is triggered when ``segmentor.init_weights()`` is called externally. + + 2. The ``loss`` method is used to calculate the loss of decode_head, + which includes two steps: (1) the decode_head model performs forward + propagation to obtain the feature maps (2) The ``loss_by_feat`` method + is called based on the feature maps to calculate the loss. + + .. code:: text + + loss(): forward() -> loss_by_feat() + + 3. The ``predict`` method is used to predict segmentation results, + which includes two steps: (1) the decode_head model performs forward + propagation to obtain the feature maps (2) The ``predict_by_feat`` method + is called based on the feature maps to predict segmentation results + including post-processing. + + .. code:: text + + predict(): forward() -> predict_by_feat() + + Args: + in_channels (int|Sequence[int]): Input channels. + channels (int): Channels after modules, before conv_seg. + num_classes (int): Number of classes. + out_channels (int): Output channels of conv_seg. Default: None. + threshold (float): Threshold for binary segmentation in the case of + `num_classes==1`. Default: None. + dropout_ratio (float): Ratio of dropout layer. Default: 0.1. + conv_cfg (dict|None): Config of conv layers. Default: None. + norm_cfg (dict|None): Config of norm layers. Default: None. + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU') + in_index (int|Sequence[int]): Input feature index. Default: -1 + input_transform (str|None): Transformation type of input features. + Options: 'resize_concat', 'multiple_select', None. + 'resize_concat': Multiple feature maps will be resize to the + same size as first one and than concat together. + Usually used in FCN head of HRNet. + 'multiple_select': Multiple feature maps will be bundle into + a list and passed into decode head. + None: Only one select feature map is allowed. + Default: None. + loss_decode (dict | Sequence[dict]): Config of decode loss. + The `loss_name` is property of corresponding loss function which + could be shown in training log. If you want this loss + item to be included into the backward graph, `loss_` must be the + prefix of the name. Defaults to 'loss_ce'. + e.g. dict(type='CrossEntropyLoss'), + [dict(type='CrossEntropyLoss', loss_name='loss_ce'), + dict(type='DiceLoss', loss_name='loss_dice')] + Default: dict(type='CrossEntropyLoss'). + ignore_index (int | None): The label index to be ignored. When using + masked BCE loss, ignore_index should be set to None. Default: 255. + sampler (dict|None): The config of segmentation map sampler. + Default: None. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + init_cfg (dict or list[dict], optional): Initialization config dict. + """ + + def __init__(self, + in_channels, + channels, + *, + num_classes, + out_channels=None, + threshold=None, + dropout_ratio=0.1, + conv_cfg=None, + norm_cfg=None, + act_cfg=dict(type='ReLU'), + in_index=-1, + input_transform=None, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0), + ignore_index=255, + sampler=None, + align_corners=False, + init_cfg=dict( + type='Normal', std=0.01, override=dict(name='conv_seg'))): + super().__init__(init_cfg) + self._init_inputs(in_channels, in_index, input_transform) + self.channels = channels + self.dropout_ratio = dropout_ratio + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.in_index = in_index + + self.ignore_index = ignore_index + self.align_corners = align_corners + + if out_channels is None: + if num_classes == 2: + warnings.warn('For binary segmentation, we suggest using' + '`out_channels = 1` to define the output' + 'channels of segmentor, and use `threshold`' + 'to convert `seg_logits` into a prediction' + 'applying a threshold') + out_channels = num_classes + + if out_channels != num_classes and out_channels != 1: + raise ValueError( + 'out_channels should be equal to num_classes,' + 'except binary segmentation set out_channels == 1 and' + f'num_classes == 2, but got out_channels={out_channels}' + f'and num_classes={num_classes}') + + if out_channels == 1 and threshold is None: + threshold = 0.3 + warnings.warn('threshold is not defined for binary, and defaults' + 'to 0.3') + self.num_classes = num_classes + self.out_channels = out_channels + self.threshold = threshold + + if isinstance(loss_decode, dict): + self.loss_decode = MODELS.build(loss_decode) + elif isinstance(loss_decode, (list, tuple)): + self.loss_decode = nn.ModuleList() + for loss in loss_decode: + self.loss_decode.append(MODELS.build(loss)) + else: + raise TypeError(f'loss_decode must be a dict or sequence of dict,\ + but got {type(loss_decode)}') + + if sampler is not None: + self.sampler = build_pixel_sampler(sampler, context=self) + else: + self.sampler = None + + self.conv_seg = nn.Conv2d(channels, self.out_channels, kernel_size=1) + if dropout_ratio > 0: + self.dropout = nn.Dropout2d(dropout_ratio) + else: + self.dropout = None + + def extra_repr(self): + """Extra repr.""" + s = f'input_transform={self.input_transform}, ' \ + f'ignore_index={self.ignore_index}, ' \ + f'align_corners={self.align_corners}' + return s + + def _init_inputs(self, in_channels, in_index, input_transform): + """Check and initialize input transforms. + + The in_channels, in_index and input_transform must match. + Specifically, when input_transform is None, only single feature map + will be selected. So in_channels and in_index must be of type int. + When input_transform + + Args: + in_channels (int|Sequence[int]): Input channels. + in_index (int|Sequence[int]): Input feature index. + input_transform (str|None): Transformation type of input features. + Options: 'resize_concat', 'multiple_select', None. + 'resize_concat': Multiple feature maps will be resize to the + same size as first one and than concat together. + Usually used in FCN head of HRNet. + 'multiple_select': Multiple feature maps will be bundle into + a list and passed into decode head. + None: Only one select feature map is allowed. + """ + + if input_transform is not None: + assert input_transform in ['resize_concat', 'multiple_select'] + self.input_transform = input_transform + self.in_index = in_index + if input_transform is not None: + assert isinstance(in_channels, (list, tuple)) + assert isinstance(in_index, (list, tuple)) + assert len(in_channels) == len(in_index) + if input_transform == 'resize_concat': + self.in_channels = sum(in_channels) + else: + self.in_channels = in_channels + else: + assert isinstance(in_channels, int) + assert isinstance(in_index, int) + self.in_channels = in_channels + + def _transform_inputs(self, inputs): + """Transform inputs for decoder. + + Args: + inputs (list[Tensor]): List of multi-level img features. + + Returns: + Tensor: The transformed inputs + """ + + if self.input_transform == 'resize_concat': + inputs = [inputs[i] for i in self.in_index] + upsampled_inputs = [ + resize( + input=x, + size=inputs[0].shape[2:], + mode='bilinear', + align_corners=self.align_corners) for x in inputs + ] + inputs = torch.cat(upsampled_inputs, dim=1) + elif self.input_transform == 'multiple_select': + inputs = [inputs[i] for i in self.in_index] + else: + inputs = inputs[self.in_index] + + return inputs + + @abstractmethod + def forward(self, inputs): + """Placeholder of forward function.""" + pass + + def cls_seg(self, feat): + """Classify each pixel.""" + if self.dropout is not None: + feat = self.dropout(feat) + output = self.conv_seg(feat) + return output + + def loss(self, inputs: Tuple[Tensor], batch_data_samples: SampleList, + train_cfg: ConfigType) -> dict: + """Forward function for training. + + Args: + inputs (Tuple[Tensor]): List of multi-level img features. + batch_data_samples (list[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `img_metas` or `gt_semantic_seg`. + train_cfg (dict): The training config. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + seg_logits = self.forward(inputs) + losses = self.loss_by_feat(seg_logits, batch_data_samples) + return losses + + def predict(self, inputs: Tuple[Tensor], batch_img_metas: List[dict], + test_cfg: ConfigType) -> Tensor: + """Forward function for prediction. + + Args: + inputs (Tuple[Tensor]): List of multi-level img features. + batch_img_metas (dict): List Image info where each dict may also + contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', and 'pad_shape'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + test_cfg (dict): The testing config. + + Returns: + Tensor: Outputs segmentation logits map. + """ + seg_logits = self.forward(inputs) + + return self.predict_by_feat(seg_logits, batch_img_metas) + + def _stack_batch_gt(self, batch_data_samples: SampleList) -> Tensor: + gt_semantic_segs = [ + data_sample.gt_sem_seg.data for data_sample in batch_data_samples + ] + return torch.stack(gt_semantic_segs, dim=0) + + def loss_by_feat(self, seg_logits: Tensor, + batch_data_samples: SampleList) -> dict: + """Compute segmentation loss. + + Args: + seg_logits (Tensor): The output from decode head forward function. + batch_data_samples (List[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `metainfo` and `gt_sem_seg`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + + seg_label = self._stack_batch_gt(batch_data_samples) + loss = dict() + seg_logits = resize( + input=seg_logits, + size=seg_label.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + if self.sampler is not None: + seg_weight = self.sampler.sample(seg_logits, seg_label) + else: + seg_weight = None + seg_label = seg_label.squeeze(1) + + if not isinstance(self.loss_decode, nn.ModuleList): + losses_decode = [self.loss_decode] + else: + losses_decode = self.loss_decode + for loss_decode in losses_decode: + if loss_decode.loss_name not in loss: + loss[loss_decode.loss_name] = loss_decode( + seg_logits, + seg_label, + weight=seg_weight, + ignore_index=self.ignore_index) + else: + loss[loss_decode.loss_name] += loss_decode( + seg_logits, + seg_label, + weight=seg_weight, + ignore_index=self.ignore_index) + + loss['acc_seg'] = accuracy( + seg_logits, seg_label, ignore_index=self.ignore_index) + return loss + + def predict_by_feat(self, seg_logits: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Transform a batch of output seg_logits to the input shape. + + Args: + seg_logits (Tensor): The output from decode head forward function. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + + Returns: + Tensor: Outputs segmentation logits map. + """ + + if isinstance(batch_img_metas[0]['img_shape'], torch.Size): + # slide inference + size = batch_img_metas[0]['img_shape'] + elif 'pad_shape' in batch_img_metas[0]: + size = batch_img_metas[0]['pad_shape'][:2] + else: + size = batch_img_metas[0]['img_shape'] + + seg_logits = resize( + input=seg_logits, + size=size, + mode='bilinear', + align_corners=self.align_corners) + return seg_logits diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dm_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dm_head.py new file mode 100644 index 0000000..7694abd --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dm_head.py @@ -0,0 +1,141 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, build_activation_layer, build_norm_layer + +from mmseg.registry import MODELS +from .decode_head import BaseDecodeHead + + +class DCM(nn.Module): + """Dynamic Convolutional Module used in DMNet. + + Args: + filter_size (int): The filter size of generated convolution kernel + used in Dynamic Convolutional Module. + fusion (bool): Add one conv to fuse DCM output feature. + in_channels (int): Input channels. + channels (int): Channels after modules, before conv_seg. + conv_cfg (dict | None): Config of conv layers. + norm_cfg (dict | None): Config of norm layers. + act_cfg (dict): Config of activation layers. + """ + + def __init__(self, filter_size, fusion, in_channels, channels, conv_cfg, + norm_cfg, act_cfg): + super().__init__() + self.filter_size = filter_size + self.fusion = fusion + self.in_channels = in_channels + self.channels = channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.filter_gen_conv = nn.Conv2d(self.in_channels, self.channels, 1, 1, + 0) + + self.input_redu_conv = ConvModule( + self.in_channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + if self.norm_cfg is not None: + self.norm = build_norm_layer(self.norm_cfg, self.channels)[1] + else: + self.norm = None + self.activate = build_activation_layer(self.act_cfg) + + if self.fusion: + self.fusion_conv = ConvModule( + self.channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, x): + """Forward function.""" + generated_filter = self.filter_gen_conv( + F.adaptive_avg_pool2d(x, self.filter_size)) + x = self.input_redu_conv(x) + b, c, h, w = x.shape + # [1, b * c, h, w], c = self.channels + x = x.view(1, b * c, h, w) + # [b * c, 1, filter_size, filter_size] + generated_filter = generated_filter.view(b * c, 1, self.filter_size, + self.filter_size) + pad = (self.filter_size - 1) // 2 + if (self.filter_size - 1) % 2 == 0: + p2d = (pad, pad, pad, pad) + else: + p2d = (pad + 1, pad, pad + 1, pad) + x = F.pad(input=x, pad=p2d, mode='constant', value=0) + # [1, b * c, h, w] + output = F.conv2d(input=x, weight=generated_filter, groups=b * c) + # [b, c, h, w] + output = output.view(b, c, h, w) + if self.norm is not None: + output = self.norm(output) + output = self.activate(output) + + if self.fusion: + output = self.fusion_conv(output) + + return output + + +@MODELS.register_module() +class DMHead(BaseDecodeHead): + """Dynamic Multi-scale Filters for Semantic Segmentation. + + This head is the implementation of + `DMNet `_. + + Args: + filter_sizes (tuple[int]): The size of generated convolutional filters + used in Dynamic Convolutional Module. Default: (1, 3, 5, 7). + fusion (bool): Add one conv to fuse DCM output feature. + """ + + def __init__(self, filter_sizes=(1, 3, 5, 7), fusion=False, **kwargs): + super().__init__(**kwargs) + assert isinstance(filter_sizes, (list, tuple)) + self.filter_sizes = filter_sizes + self.fusion = fusion + dcm_modules = [] + for filter_size in self.filter_sizes: + dcm_modules.append( + DCM(filter_size, + self.fusion, + self.in_channels, + self.channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + self.dcm_modules = nn.ModuleList(dcm_modules) + self.bottleneck = ConvModule( + self.in_channels + len(filter_sizes) * self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + dcm_outs = [x] + for dcm_module in self.dcm_modules: + dcm_outs.append(dcm_module(x)) + dcm_outs = torch.cat(dcm_outs, dim=1) + output = self.bottleneck(dcm_outs) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dnl_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dnl_head.py new file mode 100644 index 0000000..248c118 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dnl_head.py @@ -0,0 +1,137 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.cnn import NonLocal2d +from torch import nn + +from mmseg.registry import MODELS +from .fcn_head import FCNHead + + +class DisentangledNonLocal2d(NonLocal2d): + """Disentangled Non-Local Blocks. + + Args: + temperature (float): Temperature to adjust attention. Default: 0.05 + """ + + def __init__(self, *arg, temperature, **kwargs): + super().__init__(*arg, **kwargs) + self.temperature = temperature + self.conv_mask = nn.Conv2d(self.in_channels, 1, kernel_size=1) + + def embedded_gaussian(self, theta_x, phi_x): + """Embedded gaussian with temperature.""" + + # NonLocal2d pairwise_weight: [N, HxW, HxW] + pairwise_weight = torch.matmul(theta_x, phi_x) + if self.use_scale: + # theta_x.shape[-1] is `self.inter_channels` + pairwise_weight /= torch.tensor( + theta_x.shape[-1], + dtype=torch.float, + device=pairwise_weight.device)**torch.tensor( + 0.5, device=pairwise_weight.device) + pairwise_weight /= torch.tensor( + self.temperature, device=pairwise_weight.device) + pairwise_weight = pairwise_weight.softmax(dim=-1) + return pairwise_weight + + def forward(self, x): + # x: [N, C, H, W] + n = x.size(0) + + # g_x: [N, HxW, C] + g_x = self.g(x).view(n, self.inter_channels, -1) + g_x = g_x.permute(0, 2, 1) + + # theta_x: [N, HxW, C], phi_x: [N, C, HxW] + if self.mode == 'gaussian': + theta_x = x.view(n, self.in_channels, -1) + theta_x = theta_x.permute(0, 2, 1) + if self.sub_sample: + phi_x = self.phi(x).view(n, self.in_channels, -1) + else: + phi_x = x.view(n, self.in_channels, -1) + elif self.mode == 'concatenation': + theta_x = self.theta(x).view(n, self.inter_channels, -1, 1) + phi_x = self.phi(x).view(n, self.inter_channels, 1, -1) + else: + theta_x = self.theta(x).view(n, self.inter_channels, -1) + theta_x = theta_x.permute(0, 2, 1) + phi_x = self.phi(x).view(n, self.inter_channels, -1) + + # subtract mean + theta_x -= theta_x.mean(dim=-2, keepdim=True) + phi_x -= phi_x.mean(dim=-1, keepdim=True) + + pairwise_func = getattr(self, self.mode) + # pairwise_weight: [N, HxW, HxW] + pairwise_weight = pairwise_func(theta_x, phi_x) + + # y: [N, HxW, C] + y = torch.matmul(pairwise_weight, g_x) + # y: [N, C, H, W] + y = y.permute(0, 2, 1).contiguous().reshape(n, self.inter_channels, + *x.size()[2:]) + + # unary_mask: [N, 1, HxW] + unary_mask = self.conv_mask(x) + unary_mask = unary_mask.view(n, 1, -1) + unary_mask = unary_mask.softmax(dim=-1) + # unary_x: [N, 1, C] + unary_x = torch.matmul(unary_mask, g_x) + # unary_x: [N, C, 1, 1] + unary_x = unary_x.permute(0, 2, 1).contiguous().reshape( + n, self.inter_channels, 1, 1) + + output = x + self.conv_out(y + unary_x) + + return output + + +@MODELS.register_module() +class DNLHead(FCNHead): + """Disentangled Non-Local Neural Networks. + + This head is the implementation of `DNLNet + `_. + + Args: + reduction (int): Reduction factor of projection transform. Default: 2. + use_scale (bool): Whether to scale pairwise_weight by + sqrt(1/inter_channels). Default: False. + mode (str): The nonlocal mode. Options are 'embedded_gaussian', + 'dot_product'. Default: 'embedded_gaussian.'. + temperature (float): Temperature to adjust attention. Default: 0.05 + """ + + def __init__(self, + reduction=2, + use_scale=True, + mode='embedded_gaussian', + temperature=0.05, + **kwargs): + super().__init__(num_convs=2, **kwargs) + self.reduction = reduction + self.use_scale = use_scale + self.mode = mode + self.temperature = temperature + self.dnl_block = DisentangledNonLocal2d( + in_channels=self.channels, + reduction=self.reduction, + use_scale=self.use_scale, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + mode=self.mode, + temperature=self.temperature) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + output = self.convs[0](x) + output = self.dnl_block(output) + output = self.convs[1](output) + if self.concat_input: + output = self.conv_cat(torch.cat([x, output], dim=1)) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dpt_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dpt_head.py new file mode 100644 index 0000000..d2cfd89 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/dpt_head.py @@ -0,0 +1,294 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, Linear, build_activation_layer +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..utils import resize +from .decode_head import BaseDecodeHead + + +class ReassembleBlocks(BaseModule): + """ViTPostProcessBlock, process cls_token in ViT backbone output and + rearrange the feature vector to feature map. + + Args: + in_channels (int): ViT feature channels. Default: 768. + out_channels (List): output channels of each stage. + Default: [96, 192, 384, 768]. + readout_type (str): Type of readout operation. Default: 'ignore'. + patch_size (int): The patch size. Default: 16. + init_cfg (dict, optional): Initialization config dict. Default: None. + """ + + def __init__(self, + in_channels=768, + out_channels=[96, 192, 384, 768], + readout_type='ignore', + patch_size=16, + init_cfg=None): + super().__init__(init_cfg) + + assert readout_type in ['ignore', 'add', 'project'] + self.readout_type = readout_type + self.patch_size = patch_size + + self.projects = nn.ModuleList([ + ConvModule( + in_channels=in_channels, + out_channels=out_channel, + kernel_size=1, + act_cfg=None, + ) for out_channel in out_channels + ]) + + self.resize_layers = nn.ModuleList([ + nn.ConvTranspose2d( + in_channels=out_channels[0], + out_channels=out_channels[0], + kernel_size=4, + stride=4, + padding=0), + nn.ConvTranspose2d( + in_channels=out_channels[1], + out_channels=out_channels[1], + kernel_size=2, + stride=2, + padding=0), + nn.Identity(), + nn.Conv2d( + in_channels=out_channels[3], + out_channels=out_channels[3], + kernel_size=3, + stride=2, + padding=1) + ]) + if self.readout_type == 'project': + self.readout_projects = nn.ModuleList() + for _ in range(len(self.projects)): + self.readout_projects.append( + nn.Sequential( + Linear(2 * in_channels, in_channels), + build_activation_layer(dict(type='GELU')))) + + def forward(self, inputs): + assert isinstance(inputs, list) + out = [] + for i, x in enumerate(inputs): + assert len(x) == 2 + x, cls_token = x[0], x[1] + feature_shape = x.shape + if self.readout_type == 'project': + x = x.flatten(2).permute((0, 2, 1)) + readout = cls_token.unsqueeze(1).expand_as(x) + x = self.readout_projects[i](torch.cat((x, readout), -1)) + x = x.permute(0, 2, 1).reshape(feature_shape) + elif self.readout_type == 'add': + x = x.flatten(2) + cls_token.unsqueeze(-1) + x = x.reshape(feature_shape) + else: + pass + x = self.projects[i](x) + x = self.resize_layers[i](x) + out.append(x) + return out + + +class PreActResidualConvUnit(BaseModule): + """ResidualConvUnit, pre-activate residual unit. + + Args: + in_channels (int): number of channels in the input feature map. + act_cfg (dict): dictionary to construct and config activation layer. + norm_cfg (dict): dictionary to construct and config norm layer. + stride (int): stride of the first block. Default: 1 + dilation (int): dilation rate for convs layers. Default: 1. + init_cfg (dict, optional): Initialization config dict. Default: None. + """ + + def __init__(self, + in_channels, + act_cfg, + norm_cfg, + stride=1, + dilation=1, + init_cfg=None): + super().__init__(init_cfg) + + self.conv1 = ConvModule( + in_channels, + in_channels, + 3, + stride=stride, + padding=dilation, + dilation=dilation, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + bias=False, + order=('act', 'conv', 'norm')) + + self.conv2 = ConvModule( + in_channels, + in_channels, + 3, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + bias=False, + order=('act', 'conv', 'norm')) + + def forward(self, inputs): + inputs_ = inputs.clone() + x = self.conv1(inputs) + x = self.conv2(x) + return x + inputs_ + + +class FeatureFusionBlock(BaseModule): + """FeatureFusionBlock, merge feature map from different stages. + + Args: + in_channels (int): Input channels. + act_cfg (dict): The activation config for ResidualConvUnit. + norm_cfg (dict): Config dict for normalization layer. + expand (bool): Whether expand the channels in post process block. + Default: False. + align_corners (bool): align_corner setting for bilinear upsample. + Default: True. + init_cfg (dict, optional): Initialization config dict. Default: None. + """ + + def __init__(self, + in_channels, + act_cfg, + norm_cfg, + expand=False, + align_corners=True, + init_cfg=None): + super().__init__(init_cfg) + + self.in_channels = in_channels + self.expand = expand + self.align_corners = align_corners + + self.out_channels = in_channels + if self.expand: + self.out_channels = in_channels // 2 + + self.project = ConvModule( + self.in_channels, + self.out_channels, + kernel_size=1, + act_cfg=None, + bias=True) + + self.res_conv_unit1 = PreActResidualConvUnit( + in_channels=self.in_channels, act_cfg=act_cfg, norm_cfg=norm_cfg) + self.res_conv_unit2 = PreActResidualConvUnit( + in_channels=self.in_channels, act_cfg=act_cfg, norm_cfg=norm_cfg) + + def forward(self, *inputs): + x = inputs[0] + if len(inputs) == 2: + if x.shape != inputs[1].shape: + res = resize( + inputs[1], + size=(x.shape[2], x.shape[3]), + mode='bilinear', + align_corners=False) + else: + res = inputs[1] + x = x + self.res_conv_unit1(res) + x = self.res_conv_unit2(x) + x = resize( + x, + scale_factor=2, + mode='bilinear', + align_corners=self.align_corners) + x = self.project(x) + return x + + +@MODELS.register_module() +class DPTHead(BaseDecodeHead): + """Vision Transformers for Dense Prediction. + + This head is implemented of `DPT `_. + + Args: + embed_dims (int): The embed dimension of the ViT backbone. + Default: 768. + post_process_channels (List): Out channels of post process conv + layers. Default: [96, 192, 384, 768]. + readout_type (str): Type of readout operation. Default: 'ignore'. + patch_size (int): The patch size. Default: 16. + expand_channels (bool): Whether expand the channels in post process + block. Default: False. + act_cfg (dict): The activation config for residual conv unit. + Default dict(type='ReLU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + """ + + def __init__(self, + embed_dims=768, + post_process_channels=[96, 192, 384, 768], + readout_type='ignore', + patch_size=16, + expand_channels=False, + act_cfg=dict(type='ReLU'), + norm_cfg=dict(type='BN'), + **kwargs): + super().__init__(**kwargs) + + self.in_channels = self.in_channels + self.expand_channels = expand_channels + self.reassemble_blocks = ReassembleBlocks(embed_dims, + post_process_channels, + readout_type, patch_size) + + self.post_process_channels = [ + channel * math.pow(2, i) if expand_channels else channel + for i, channel in enumerate(post_process_channels) + ] + self.convs = nn.ModuleList() + for channel in self.post_process_channels: + self.convs.append( + ConvModule( + channel, + self.channels, + kernel_size=3, + padding=1, + act_cfg=None, + bias=False)) + self.fusion_blocks = nn.ModuleList() + for _ in range(len(self.convs)): + self.fusion_blocks.append( + FeatureFusionBlock(self.channels, act_cfg, norm_cfg)) + self.fusion_blocks[0].res_conv_unit1 = None + self.project = ConvModule( + self.channels, + self.channels, + kernel_size=3, + padding=1, + norm_cfg=norm_cfg) + self.num_fusion_blocks = len(self.fusion_blocks) + self.num_reassemble_blocks = len(self.reassemble_blocks.resize_layers) + self.num_post_process_channels = len(self.post_process_channels) + assert self.num_fusion_blocks == self.num_reassemble_blocks + assert self.num_reassemble_blocks == self.num_post_process_channels + + def forward(self, inputs): + assert len(inputs) == self.num_reassemble_blocks + x = self._transform_inputs(inputs) + x = self.reassemble_blocks(x) + x = [self.convs[i](feature) for i, feature in enumerate(x)] + out = self.fusion_blocks[0](x[-1]) + for i in range(1, len(self.fusion_blocks)): + out = self.fusion_blocks[i](out, x[-(i + 1)]) + out = self.project(out) + out = self.cls_seg(out) + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ema_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ema_head.py new file mode 100644 index 0000000..ab8dbb0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ema_head.py @@ -0,0 +1,169 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.distributed as dist +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from .decode_head import BaseDecodeHead + + +def reduce_mean(tensor): + """Reduce mean when distributed training.""" + if not (dist.is_available() and dist.is_initialized()): + return tensor + tensor = tensor.clone() + dist.all_reduce(tensor.div_(dist.get_world_size()), op=dist.ReduceOp.SUM) + return tensor + + +class EMAModule(nn.Module): + """Expectation Maximization Attention Module used in EMANet. + + Args: + channels (int): Channels of the whole module. + num_bases (int): Number of bases. + num_stages (int): Number of the EM iterations. + """ + + def __init__(self, channels, num_bases, num_stages, momentum): + super().__init__() + assert num_stages >= 1, 'num_stages must be at least 1!' + self.num_bases = num_bases + self.num_stages = num_stages + self.momentum = momentum + + bases = torch.zeros(1, channels, self.num_bases) + bases.normal_(0, math.sqrt(2. / self.num_bases)) + # [1, channels, num_bases] + bases = F.normalize(bases, dim=1, p=2) + self.register_buffer('bases', bases) + + def forward(self, feats): + """Forward function.""" + batch_size, channels, height, width = feats.size() + # [batch_size, channels, height*width] + feats = feats.view(batch_size, channels, height * width) + # [batch_size, channels, num_bases] + bases = self.bases.repeat(batch_size, 1, 1) + + with torch.no_grad(): + for i in range(self.num_stages): + # [batch_size, height*width, num_bases] + attention = torch.einsum('bcn,bck->bnk', feats, bases) + attention = F.softmax(attention, dim=2) + # l1 norm + attention_normed = F.normalize(attention, dim=1, p=1) + # [batch_size, channels, num_bases] + bases = torch.einsum('bcn,bnk->bck', feats, attention_normed) + # l2 norm + bases = F.normalize(bases, dim=1, p=2) + + feats_recon = torch.einsum('bck,bnk->bcn', bases, attention) + feats_recon = feats_recon.view(batch_size, channels, height, width) + + if self.training: + bases = bases.mean(dim=0, keepdim=True) + bases = reduce_mean(bases) + # l2 norm + bases = F.normalize(bases, dim=1, p=2) + self.bases = (1 - + self.momentum) * self.bases + self.momentum * bases + + return feats_recon + + +@MODELS.register_module() +class EMAHead(BaseDecodeHead): + """Expectation Maximization Attention Networks for Semantic Segmentation. + + This head is the implementation of `EMANet + `_. + + Args: + ema_channels (int): EMA module channels + num_bases (int): Number of bases. + num_stages (int): Number of the EM iterations. + concat_input (bool): Whether concat the input and output of convs + before classification layer. Default: True + momentum (float): Momentum to update the base. Default: 0.1. + """ + + def __init__(self, + ema_channels, + num_bases, + num_stages, + concat_input=True, + momentum=0.1, + **kwargs): + super().__init__(**kwargs) + self.ema_channels = ema_channels + self.num_bases = num_bases + self.num_stages = num_stages + self.concat_input = concat_input + self.momentum = momentum + self.ema_module = EMAModule(self.ema_channels, self.num_bases, + self.num_stages, self.momentum) + + self.ema_in_conv = ConvModule( + self.in_channels, + self.ema_channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + # project (0, inf) -> (-inf, inf) + self.ema_mid_conv = ConvModule( + self.ema_channels, + self.ema_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=None, + act_cfg=None) + for param in self.ema_mid_conv.parameters(): + param.requires_grad = False + + self.ema_out_conv = ConvModule( + self.ema_channels, + self.ema_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=None) + self.bottleneck = ConvModule( + self.ema_channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + if self.concat_input: + self.conv_cat = ConvModule( + self.in_channels + self.channels, + self.channels, + kernel_size=3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + feats = self.ema_in_conv(x) + identity = feats + feats = self.ema_mid_conv(feats) + recon = self.ema_module(feats) + recon = F.relu(recon, inplace=True) + recon = self.ema_out_conv(recon) + output = F.relu(identity + recon, inplace=True) + output = self.bottleneck(output) + if self.concat_input: + output = self.conv_cat(torch.cat([x, output], dim=1)) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/enc_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/enc_head.py new file mode 100644 index 0000000..2bba73b --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/enc_head.py @@ -0,0 +1,196 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, build_norm_layer +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.utils import ConfigType, SampleList +from ..utils import Encoding, resize +from .decode_head import BaseDecodeHead + + +class EncModule(nn.Module): + """Encoding Module used in EncNet. + + Args: + in_channels (int): Input channels. + num_codes (int): Number of code words. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict): Config of activation layers. + """ + + def __init__(self, in_channels, num_codes, conv_cfg, norm_cfg, act_cfg): + super().__init__() + self.encoding_project = ConvModule( + in_channels, + in_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + # TODO: resolve this hack + # change to 1d + if norm_cfg is not None: + encoding_norm_cfg = norm_cfg.copy() + if encoding_norm_cfg['type'] in ['BN', 'IN']: + encoding_norm_cfg['type'] += '1d' + else: + encoding_norm_cfg['type'] = encoding_norm_cfg['type'].replace( + '2d', '1d') + else: + # fallback to BN1d + encoding_norm_cfg = dict(type='BN1d') + self.encoding = nn.Sequential( + Encoding(channels=in_channels, num_codes=num_codes), + build_norm_layer(encoding_norm_cfg, num_codes)[1], + nn.ReLU(inplace=True)) + self.fc = nn.Sequential( + nn.Linear(in_channels, in_channels), nn.Sigmoid()) + + def forward(self, x): + """Forward function.""" + encoding_projection = self.encoding_project(x) + encoding_feat = self.encoding(encoding_projection).mean(dim=1) + batch_size, channels, _, _ = x.size() + gamma = self.fc(encoding_feat) + y = gamma.view(batch_size, channels, 1, 1) + output = F.relu_(x + x * y) + return encoding_feat, output + + +@MODELS.register_module() +class EncHead(BaseDecodeHead): + """Context Encoding for Semantic Segmentation. + + This head is the implementation of `EncNet + `_. + + Args: + num_codes (int): Number of code words. Default: 32. + use_se_loss (bool): Whether use Semantic Encoding Loss (SE-loss) to + regularize the training. Default: True. + add_lateral (bool): Whether use lateral connection to fuse features. + Default: False. + loss_se_decode (dict): Config of decode loss. + Default: dict(type='CrossEntropyLoss', use_sigmoid=True). + """ + + def __init__(self, + num_codes=32, + use_se_loss=True, + add_lateral=False, + loss_se_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_weight=0.2), + **kwargs): + super().__init__(input_transform='multiple_select', **kwargs) + self.use_se_loss = use_se_loss + self.add_lateral = add_lateral + self.num_codes = num_codes + self.bottleneck = ConvModule( + self.in_channels[-1], + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + if add_lateral: + self.lateral_convs = nn.ModuleList() + for in_channels in self.in_channels[:-1]: # skip the last one + self.lateral_convs.append( + ConvModule( + in_channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + self.fusion = ConvModule( + len(self.in_channels) * self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.enc_module = EncModule( + self.channels, + num_codes=num_codes, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + if self.use_se_loss: + self.loss_se_decode = MODELS.build(loss_se_decode) + self.se_layer = nn.Linear(self.channels, self.num_classes) + + def forward(self, inputs): + """Forward function.""" + inputs = self._transform_inputs(inputs) + feat = self.bottleneck(inputs[-1]) + if self.add_lateral: + laterals = [ + resize( + lateral_conv(inputs[i]), + size=feat.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + feat = self.fusion(torch.cat([feat, *laterals], 1)) + encode_feat, output = self.enc_module(feat) + output = self.cls_seg(output) + if self.use_se_loss: + se_output = self.se_layer(encode_feat) + return output, se_output + else: + return output + + def predict(self, inputs: Tuple[Tensor], batch_img_metas: List[dict], + test_cfg: ConfigType): + """Forward function for testing, ignore se_loss.""" + if self.use_se_loss: + seg_logits = self.forward(inputs)[0] + else: + seg_logits = self.forward(inputs) + return self.predict_by_feat(seg_logits, batch_img_metas) + + @staticmethod + def _convert_to_onehot_labels(seg_label, num_classes): + """Convert segmentation label to onehot. + + Args: + seg_label (Tensor): Segmentation label of shape (N, H, W). + num_classes (int): Number of classes. + + Returns: + Tensor: Onehot labels of shape (N, num_classes). + """ + + batch_size = seg_label.size(0) + onehot_labels = seg_label.new_zeros((batch_size, num_classes)) + for i in range(batch_size): + hist = seg_label[i].float().histc( + bins=num_classes, min=0, max=num_classes - 1) + onehot_labels[i] = hist > 0 + return onehot_labels + + def loss_by_feat(self, seg_logit: Tuple[Tensor], + batch_data_samples: SampleList, **kwargs) -> dict: + """Compute segmentation and semantic encoding loss.""" + seg_logit, se_seg_logit = seg_logit + loss = dict() + loss.update(super().loss_by_feat(seg_logit, batch_data_samples)) + + seg_label = self._stack_batch_gt(batch_data_samples) + se_loss = self.loss_se_decode( + se_seg_logit, + self._convert_to_onehot_labels(seg_label, self.num_classes)) + loss['loss_se'] = se_loss + return loss diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/fcn_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/fcn_head.py new file mode 100644 index 0000000..3418018 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/fcn_head.py @@ -0,0 +1,96 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from .decode_head import BaseDecodeHead + + +@MODELS.register_module() +class FCNHead(BaseDecodeHead): + """Fully Convolution Networks for Semantic Segmentation. + + This head is implemented of `FCNNet `_. + + Args: + num_convs (int): Number of convs in the head. Default: 2. + kernel_size (int): The kernel size for convs in the head. Default: 3. + concat_input (bool): Whether concat the input and output of convs + before classification layer. + dilation (int): The dilation rate for convs in the head. Default: 1. + """ + + def __init__(self, + num_convs=2, + kernel_size=3, + concat_input=True, + dilation=1, + **kwargs): + assert num_convs >= 0 and dilation > 0 and isinstance(dilation, int) + self.num_convs = num_convs + self.concat_input = concat_input + self.kernel_size = kernel_size + super().__init__(**kwargs) + if num_convs == 0: + assert self.in_channels == self.channels + + conv_padding = (kernel_size // 2) * dilation + convs = [] + convs.append( + ConvModule( + self.in_channels, + self.channels, + kernel_size=kernel_size, + padding=conv_padding, + dilation=dilation, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + for i in range(num_convs - 1): + convs.append( + ConvModule( + self.channels, + self.channels, + kernel_size=kernel_size, + padding=conv_padding, + dilation=dilation, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + if num_convs == 0: + self.convs = nn.Identity() + else: + self.convs = nn.Sequential(*convs) + if self.concat_input: + self.conv_cat = ConvModule( + self.in_channels + self.channels, + self.channels, + kernel_size=kernel_size, + padding=kernel_size // 2, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def _forward_feature(self, inputs): + """Forward function for feature maps before classifying each pixel with + ``self.cls_seg`` fc. + + Args: + inputs (list[Tensor]): List of multi-level img features. + + Returns: + feats (Tensor): A tensor of shape (batch_size, self.channels, + H, W) which is feature map for last layer of decoder head. + """ + x = self._transform_inputs(inputs) + feats = self.convs(x) + if self.concat_input: + feats = self.conv_cat(torch.cat([x, feats], dim=1)) + return feats + + def forward(self, inputs): + """Forward function.""" + output = self._forward_feature(inputs) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/fpn_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/fpn_head.py new file mode 100644 index 0000000..25f481f --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/fpn_head.py @@ -0,0 +1,68 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import numpy as np +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import Upsample, resize +from .decode_head import BaseDecodeHead + + +@MODELS.register_module() +class FPNHead(BaseDecodeHead): + """Panoptic Feature Pyramid Networks. + + This head is the implementation of `Semantic FPN + `_. + + Args: + feature_strides (tuple[int]): The strides for input feature maps. + stack_lateral. All strides suppose to be power of 2. The first + one is of largest resolution. + """ + + def __init__(self, feature_strides, **kwargs): + super().__init__(input_transform='multiple_select', **kwargs) + assert len(feature_strides) == len(self.in_channels) + assert min(feature_strides) == feature_strides[0] + self.feature_strides = feature_strides + + self.scale_heads = nn.ModuleList() + for i in range(len(feature_strides)): + head_length = max( + 1, + int(np.log2(feature_strides[i]) - np.log2(feature_strides[0]))) + scale_head = [] + for k in range(head_length): + scale_head.append( + ConvModule( + self.in_channels[i] if k == 0 else self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + if feature_strides[i] != feature_strides[0]: + scale_head.append( + Upsample( + scale_factor=2, + mode='bilinear', + align_corners=self.align_corners)) + self.scale_heads.append(nn.Sequential(*scale_head)) + + def forward(self, inputs): + + x = self._transform_inputs(inputs) + + output = self.scale_heads[0](x[0]) + for i in range(1, len(self.feature_strides)): + # non inplace + output = output + resize( + self.scale_heads[i](x[i]), + size=output.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/gc_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/gc_head.py new file mode 100644 index 0000000..14f0ef0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/gc_head.py @@ -0,0 +1,48 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.cnn import ContextBlock + +from mmseg.registry import MODELS +from .fcn_head import FCNHead + + +@MODELS.register_module() +class GCHead(FCNHead): + """GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond. + + This head is the implementation of `GCNet + `_. + + Args: + ratio (float): Multiplier of channels ratio. Default: 1/4. + pooling_type (str): The pooling type of context aggregation. + Options are 'att', 'avg'. Default: 'avg'. + fusion_types (tuple[str]): The fusion type for feature fusion. + Options are 'channel_add', 'channel_mul'. Default: ('channel_add',) + """ + + def __init__(self, + ratio=1 / 4., + pooling_type='att', + fusion_types=('channel_add', ), + **kwargs): + super().__init__(num_convs=2, **kwargs) + self.ratio = ratio + self.pooling_type = pooling_type + self.fusion_types = fusion_types + self.gc_block = ContextBlock( + in_channels=self.channels, + ratio=self.ratio, + pooling_type=self.pooling_type, + fusion_types=self.fusion_types) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + output = self.convs[0](x) + output = self.gc_block(output) + output = self.convs[1](output) + if self.concat_input: + output = self.conv_cat(torch.cat([x, output], dim=1)) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ham_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ham_head.py new file mode 100644 index 0000000..073d801 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ham_head.py @@ -0,0 +1,255 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Originally from https://github.com/visual-attention-network/segnext +# Licensed under the Apache License, Version 2.0 (the "License") +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.device import get_device + +from mmseg.registry import MODELS +from ..utils import resize +from .decode_head import BaseDecodeHead + + +class Matrix_Decomposition_2D_Base(nn.Module): + """Base class of 2D Matrix Decomposition. + + Args: + MD_S (int): The number of spatial coefficient in + Matrix Decomposition, it may be used for calculation + of the number of latent dimension D in Matrix + Decomposition. Defaults: 1. + MD_R (int): The number of latent dimension R in + Matrix Decomposition. Defaults: 64. + train_steps (int): The number of iteration steps in + Multiplicative Update (MU) rule to solve Non-negative + Matrix Factorization (NMF) in training. Defaults: 6. + eval_steps (int): The number of iteration steps in + Multiplicative Update (MU) rule to solve Non-negative + Matrix Factorization (NMF) in evaluation. Defaults: 7. + inv_t (int): Inverted multiple number to make coefficient + smaller in softmax. Defaults: 100. + rand_init (bool): Whether to initialize randomly. + Defaults: True. + """ + + def __init__(self, + MD_S=1, + MD_R=64, + train_steps=6, + eval_steps=7, + inv_t=100, + rand_init=True): + super().__init__() + + self.S = MD_S + self.R = MD_R + + self.train_steps = train_steps + self.eval_steps = eval_steps + + self.inv_t = inv_t + + self.rand_init = rand_init + + def _build_bases(self, B, S, D, R, device=None): + raise NotImplementedError + + def local_step(self, x, bases, coef): + raise NotImplementedError + + def local_inference(self, x, bases): + # (B * S, D, N)^T @ (B * S, D, R) -> (B * S, N, R) + coef = torch.bmm(x.transpose(1, 2), bases) + coef = F.softmax(self.inv_t * coef, dim=-1) + + steps = self.train_steps if self.training else self.eval_steps + for _ in range(steps): + bases, coef = self.local_step(x, bases, coef) + + return bases, coef + + def compute_coef(self, x, bases, coef): + raise NotImplementedError + + def forward(self, x, return_bases=False): + """Forward Function.""" + B, C, H, W = x.shape + + # (B, C, H, W) -> (B * S, D, N) + D = C // self.S + N = H * W + x = x.view(B * self.S, D, N) + if not self.rand_init and not hasattr(self, 'bases'): + bases = self._build_bases(1, self.S, D, self.R, device=x.device) + self.register_buffer('bases', bases) + + # (S, D, R) -> (B * S, D, R) + if self.rand_init: + bases = self._build_bases(B, self.S, D, self.R, device=x.device) + else: + bases = self.bases.repeat(B, 1, 1) + + bases, coef = self.local_inference(x, bases) + + # (B * S, N, R) + coef = self.compute_coef(x, bases, coef) + + # (B * S, D, R) @ (B * S, N, R)^T -> (B * S, D, N) + x = torch.bmm(bases, coef.transpose(1, 2)) + + # (B * S, D, N) -> (B, C, H, W) + x = x.view(B, C, H, W) + + return x + + +class NMF2D(Matrix_Decomposition_2D_Base): + """Non-negative Matrix Factorization (NMF) module. + + It is inherited from ``Matrix_Decomposition_2D_Base`` module. + """ + + def __init__(self, args=dict()): + super().__init__(**args) + + self.inv_t = 1 + + def _build_bases(self, B, S, D, R, device=None): + """Build bases in initialization.""" + if device is None: + device = get_device() + bases = torch.rand((B * S, D, R)).to(device) + bases = F.normalize(bases, dim=1) + + return bases + + def local_step(self, x, bases, coef): + """Local step in iteration to renew bases and coefficient.""" + # (B * S, D, N)^T @ (B * S, D, R) -> (B * S, N, R) + numerator = torch.bmm(x.transpose(1, 2), bases) + # (B * S, N, R) @ [(B * S, D, R)^T @ (B * S, D, R)] -> (B * S, N, R) + denominator = coef.bmm(bases.transpose(1, 2).bmm(bases)) + # Multiplicative Update + coef = coef * numerator / (denominator + 1e-6) + + # (B * S, D, N) @ (B * S, N, R) -> (B * S, D, R) + numerator = torch.bmm(x, coef) + # (B * S, D, R) @ [(B * S, N, R)^T @ (B * S, N, R)] -> (B * S, D, R) + denominator = bases.bmm(coef.transpose(1, 2).bmm(coef)) + # Multiplicative Update + bases = bases * numerator / (denominator + 1e-6) + + return bases, coef + + def compute_coef(self, x, bases, coef): + """Compute coefficient.""" + # (B * S, D, N)^T @ (B * S, D, R) -> (B * S, N, R) + numerator = torch.bmm(x.transpose(1, 2), bases) + # (B * S, N, R) @ (B * S, D, R)^T @ (B * S, D, R) -> (B * S, N, R) + denominator = coef.bmm(bases.transpose(1, 2).bmm(bases)) + # multiplication update + coef = coef * numerator / (denominator + 1e-6) + + return coef + + +class Hamburger(nn.Module): + """Hamburger Module. It consists of one slice of "ham" (matrix + decomposition) and two slices of "bread" (linear transformation). + + Args: + ham_channels (int): Input and output channels of feature. + ham_kwargs (dict): Config of matrix decomposition module. + norm_cfg (dict | None): Config of norm layers. + """ + + def __init__(self, + ham_channels=512, + ham_kwargs=dict(), + norm_cfg=None, + **kwargs): + super().__init__() + + self.ham_in = ConvModule( + ham_channels, ham_channels, 1, norm_cfg=None, act_cfg=None) + + self.ham = NMF2D(ham_kwargs) + + self.ham_out = ConvModule( + ham_channels, ham_channels, 1, norm_cfg=norm_cfg, act_cfg=None) + + def forward(self, x): + enjoy = self.ham_in(x) + enjoy = F.relu(enjoy, inplace=True) + enjoy = self.ham(enjoy) + enjoy = self.ham_out(enjoy) + ham = F.relu(x + enjoy, inplace=True) + + return ham + + +@MODELS.register_module() +class LightHamHead(BaseDecodeHead): + """SegNeXt decode head. + + This decode head is the implementation of `SegNeXt: Rethinking + Convolutional Attention Design for Semantic + Segmentation `_. + Inspiration from https://github.com/visual-attention-network/segnext. + + Specifically, LightHamHead is inspired by HamNet from + `Is Attention Better Than Matrix Decomposition? + `. + + Args: + ham_channels (int): input channels for Hamburger. + Defaults: 512. + ham_kwargs (int): kwagrs for Ham. Defaults: dict(). + """ + + def __init__(self, ham_channels=512, ham_kwargs=dict(), **kwargs): + super().__init__(input_transform='multiple_select', **kwargs) + self.ham_channels = ham_channels + + self.squeeze = ConvModule( + sum(self.in_channels), + self.ham_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.hamburger = Hamburger(ham_channels, ham_kwargs, **kwargs) + + self.align = ConvModule( + self.ham_channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, inputs): + """Forward function.""" + inputs = self._transform_inputs(inputs) + + inputs = [ + resize( + level, + size=inputs[0].shape[2:], + mode='bilinear', + align_corners=self.align_corners) for level in inputs + ] + + inputs = torch.cat(inputs, dim=1) + # apply a conv block to squeeze feature map + x = self.squeeze(inputs) + # apply hamburger module + x = self.hamburger(x) + + # apply a conv block to align feature map + output = self.align(x) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/isa_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/isa_head.py new file mode 100644 index 0000000..355f215 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/isa_head.py @@ -0,0 +1,143 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn.functional as F +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import SelfAttentionBlock as _SelfAttentionBlock +from .decode_head import BaseDecodeHead + + +class SelfAttentionBlock(_SelfAttentionBlock): + """Self-Attention Module. + + Args: + in_channels (int): Input channels of key/query feature. + channels (int): Output channels of key/query transform. + conv_cfg (dict | None): Config of conv layers. + norm_cfg (dict | None): Config of norm layers. + act_cfg (dict | None): Config of activation layers. + """ + + def __init__(self, in_channels, channels, conv_cfg, norm_cfg, act_cfg): + super().__init__( + key_in_channels=in_channels, + query_in_channels=in_channels, + channels=channels, + out_channels=in_channels, + share_key_query=False, + query_downsample=None, + key_downsample=None, + key_query_num_convs=2, + key_query_norm=True, + value_out_num_convs=1, + value_out_norm=False, + matmul_norm=True, + with_out=False, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + self.output_project = self.build_project( + in_channels, + in_channels, + num_convs=1, + use_conv_module=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x): + """Forward function.""" + context = super().forward(x, x) + return self.output_project(context) + + +@MODELS.register_module() +class ISAHead(BaseDecodeHead): + """Interlaced Sparse Self-Attention for Semantic Segmentation. + + This head is the implementation of `ISA + `_. + + Args: + isa_channels (int): The channels of ISA Module. + down_factor (tuple[int]): The local group size of ISA. + """ + + def __init__(self, isa_channels, down_factor=(8, 8), **kwargs): + super().__init__(**kwargs) + self.down_factor = down_factor + + self.in_conv = ConvModule( + self.in_channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.global_relation = SelfAttentionBlock( + self.channels, + isa_channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.local_relation = SelfAttentionBlock( + self.channels, + isa_channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.out_conv = ConvModule( + self.channels * 2, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, inputs): + """Forward function.""" + x_ = self._transform_inputs(inputs) + x = self.in_conv(x_) + residual = x + + n, c, h, w = x.size() + loc_h, loc_w = self.down_factor # size of local group in H- and W-axes + glb_h, glb_w = math.ceil(h / loc_h), math.ceil(w / loc_w) + pad_h, pad_w = glb_h * loc_h - h, glb_w * loc_w - w + if pad_h > 0 or pad_w > 0: # pad if the size is not divisible + padding = (pad_w // 2, pad_w - pad_w // 2, pad_h // 2, + pad_h - pad_h // 2) + x = F.pad(x, padding) + + # global relation + x = x.view(n, c, glb_h, loc_h, glb_w, loc_w) + # do permutation to gather global group + x = x.permute(0, 3, 5, 1, 2, 4) # (n, loc_h, loc_w, c, glb_h, glb_w) + x = x.reshape(-1, c, glb_h, glb_w) + # apply attention within each global group + x = self.global_relation(x) # (n * loc_h * loc_w, c, glb_h, glb_w) + + # local relation + x = x.view(n, loc_h, loc_w, c, glb_h, glb_w) + # do permutation to gather local group + x = x.permute(0, 4, 5, 3, 1, 2) # (n, glb_h, glb_w, c, loc_h, loc_w) + x = x.reshape(-1, c, loc_h, loc_w) + # apply attention within each local group + x = self.local_relation(x) # (n * glb_h * glb_w, c, loc_h, loc_w) + + # permute each pixel back to its original position + x = x.view(n, glb_h, glb_w, c, loc_h, loc_w) + x = x.permute(0, 3, 1, 4, 2, 5) # (n, c, glb_h, loc_h, glb_w, loc_w) + x = x.reshape(n, c, glb_h * loc_h, glb_w * loc_w) + if pad_h > 0 or pad_w > 0: # remove padding + x = x[:, :, pad_h // 2:pad_h // 2 + h, pad_w // 2:pad_w // 2 + w] + + x = self.out_conv(torch.cat([x, residual], dim=1)) + out = self.cls_seg(x) + + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/knet_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/knet_head.py new file mode 100644 index 0000000..82d3a28 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/knet_head.py @@ -0,0 +1,461 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, build_activation_layer, build_norm_layer +from mmcv.cnn.bricks.transformer import (FFN, MultiheadAttention, + build_transformer_layer) +from mmengine.logging import print_log +from torch import Tensor + +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.registry import MODELS +from mmseg.utils import SampleList + + +@MODELS.register_module() +class KernelUpdator(nn.Module): + """Dynamic Kernel Updator in Kernel Update Head. + + Args: + in_channels (int): The number of channels of input feature map. + Default: 256. + feat_channels (int): The number of middle-stage channels in + the kernel updator. Default: 64. + out_channels (int): The number of output channels. + gate_sigmoid (bool): Whether use sigmoid function in gate + mechanism. Default: True. + gate_norm_act (bool): Whether add normalization and activation + layer in gate mechanism. Default: False. + activate_out: Whether add activation after gate mechanism. + Default: False. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='LN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + """ + + def __init__( + self, + in_channels=256, + feat_channels=64, + out_channels=None, + gate_sigmoid=True, + gate_norm_act=False, + activate_out=False, + norm_cfg=dict(type='LN'), + act_cfg=dict(type='ReLU', inplace=True), + ): + super().__init__() + self.in_channels = in_channels + self.feat_channels = feat_channels + self.out_channels_raw = out_channels + self.gate_sigmoid = gate_sigmoid + self.gate_norm_act = gate_norm_act + self.activate_out = activate_out + self.act_cfg = act_cfg + self.norm_cfg = norm_cfg + self.out_channels = out_channels if out_channels else in_channels + + self.num_params_in = self.feat_channels + self.num_params_out = self.feat_channels + self.dynamic_layer = nn.Linear( + self.in_channels, self.num_params_in + self.num_params_out) + self.input_layer = nn.Linear(self.in_channels, + self.num_params_in + self.num_params_out, + 1) + self.input_gate = nn.Linear(self.in_channels, self.feat_channels, 1) + self.update_gate = nn.Linear(self.in_channels, self.feat_channels, 1) + if self.gate_norm_act: + self.gate_norm = build_norm_layer(norm_cfg, self.feat_channels)[1] + + self.norm_in = build_norm_layer(norm_cfg, self.feat_channels)[1] + self.norm_out = build_norm_layer(norm_cfg, self.feat_channels)[1] + self.input_norm_in = build_norm_layer(norm_cfg, self.feat_channels)[1] + self.input_norm_out = build_norm_layer(norm_cfg, self.feat_channels)[1] + + self.activation = build_activation_layer(act_cfg) + + self.fc_layer = nn.Linear(self.feat_channels, self.out_channels, 1) + self.fc_norm = build_norm_layer(norm_cfg, self.out_channels)[1] + + def forward(self, update_feature, input_feature): + """Forward function of KernelUpdator. + + Args: + update_feature (torch.Tensor): Feature map assembled from + each group. It would be reshaped with last dimension + shape: `self.in_channels`. + input_feature (torch.Tensor): Intermediate feature + with shape: (N, num_classes, conv_kernel_size**2, channels). + Returns: + Tensor: The output tensor of shape (N*C1/C2, K*K, C2), where N is + the number of classes, C1 and C2 are the feature map channels of + KernelUpdateHead and KernelUpdator, respectively. + """ + + update_feature = update_feature.reshape(-1, self.in_channels) + num_proposals = update_feature.size(0) + # dynamic_layer works for + # phi_1 and psi_3 in Eq.(4) and (5) of K-Net paper + parameters = self.dynamic_layer(update_feature) + param_in = parameters[:, :self.num_params_in].view( + -1, self.feat_channels) + param_out = parameters[:, -self.num_params_out:].view( + -1, self.feat_channels) + + # input_layer works for + # phi_2 and psi_4 in Eq.(4) and (5) of K-Net paper + input_feats = self.input_layer( + input_feature.reshape(num_proposals, -1, self.feat_channels)) + input_in = input_feats[..., :self.num_params_in] + input_out = input_feats[..., -self.num_params_out:] + + # `gate_feats` is F^G in K-Net paper + gate_feats = input_in * param_in.unsqueeze(-2) + if self.gate_norm_act: + gate_feats = self.activation(self.gate_norm(gate_feats)) + + input_gate = self.input_norm_in(self.input_gate(gate_feats)) + update_gate = self.norm_in(self.update_gate(gate_feats)) + if self.gate_sigmoid: + input_gate = input_gate.sigmoid() + update_gate = update_gate.sigmoid() + param_out = self.norm_out(param_out) + input_out = self.input_norm_out(input_out) + + if self.activate_out: + param_out = self.activation(param_out) + input_out = self.activation(input_out) + + # Gate mechanism. Eq.(5) in original paper. + # param_out has shape (batch_size, feat_channels, out_channels) + features = update_gate * param_out.unsqueeze( + -2) + input_gate * input_out + + features = self.fc_layer(features) + features = self.fc_norm(features) + features = self.activation(features) + + return features + + +@MODELS.register_module() +class KernelUpdateHead(nn.Module): + """Kernel Update Head in K-Net. + + Args: + num_classes (int): Number of classes. Default: 150. + num_ffn_fcs (int): The number of fully-connected layers in + FFNs. Default: 2. + num_heads (int): The number of parallel attention heads. + Default: 8. + num_mask_fcs (int): The number of fully connected layers for + mask prediction. Default: 3. + feedforward_channels (int): The hidden dimension of FFNs. + Defaults: 2048. + in_channels (int): The number of channels of input feature map. + Default: 256. + out_channels (int): The number of output channels. + Default: 256. + dropout (float): The Probability of an element to be + zeroed in MultiheadAttention and FFN. Default 0.0. + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + ffn_act_cfg (dict): Config of activation layers in FFN. + Default: dict(type='ReLU'). + conv_kernel_size (int): The kernel size of convolution in + Kernel Update Head for dynamic kernel updation. + Default: 1. + feat_transform_cfg (dict | None): Config of feature transform. + Default: None. + kernel_init (bool): Whether initiate mask kernel in mask head. + Default: False. + with_ffn (bool): Whether add FFN in kernel update head. + Default: True. + feat_gather_stride (int): Stride of convolution in feature transform. + Default: 1. + mask_transform_stride (int): Stride of mask transform. + Default: 1. + kernel_updator_cfg (dict): Config of kernel updator. + Default: dict( + type='DynamicConv', + in_channels=256, + feat_channels=64, + out_channels=256, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN')). + """ + + def __init__(self, + num_classes=150, + num_ffn_fcs=2, + num_heads=8, + num_mask_fcs=3, + feedforward_channels=2048, + in_channels=256, + out_channels=256, + dropout=0.0, + act_cfg=dict(type='ReLU', inplace=True), + ffn_act_cfg=dict(type='ReLU', inplace=True), + conv_kernel_size=1, + feat_transform_cfg=None, + kernel_init=False, + with_ffn=True, + feat_gather_stride=1, + mask_transform_stride=1, + kernel_updator_cfg=dict( + type='DynamicConv', + in_channels=256, + feat_channels=64, + out_channels=256, + act_cfg=dict(type='ReLU', inplace=True), + norm_cfg=dict(type='LN'))): + super().__init__() + self.num_classes = num_classes + self.in_channels = in_channels + self.out_channels = out_channels + self.fp16_enabled = False + self.dropout = dropout + self.num_heads = num_heads + self.kernel_init = kernel_init + self.with_ffn = with_ffn + self.conv_kernel_size = conv_kernel_size + self.feat_gather_stride = feat_gather_stride + self.mask_transform_stride = mask_transform_stride + + self.attention = MultiheadAttention(in_channels * conv_kernel_size**2, + num_heads, dropout) + self.attention_norm = build_norm_layer( + dict(type='LN'), in_channels * conv_kernel_size**2)[1] + self.kernel_update_conv = build_transformer_layer(kernel_updator_cfg) + + if feat_transform_cfg is not None: + kernel_size = feat_transform_cfg.pop('kernel_size', 1) + transform_channels = in_channels + self.feat_transform = ConvModule( + transform_channels, + in_channels, + kernel_size, + stride=feat_gather_stride, + padding=int(feat_gather_stride // 2), + **feat_transform_cfg) + else: + self.feat_transform = None + + if self.with_ffn: + self.ffn = FFN( + in_channels, + feedforward_channels, + num_ffn_fcs, + act_cfg=ffn_act_cfg, + dropout=dropout) + self.ffn_norm = build_norm_layer(dict(type='LN'), in_channels)[1] + + self.mask_fcs = nn.ModuleList() + for _ in range(num_mask_fcs): + self.mask_fcs.append( + nn.Linear(in_channels, in_channels, bias=False)) + self.mask_fcs.append( + build_norm_layer(dict(type='LN'), in_channels)[1]) + self.mask_fcs.append(build_activation_layer(act_cfg)) + + self.fc_mask = nn.Linear(in_channels, out_channels) + + def init_weights(self): + """Use xavier initialization for all weight parameter and set + classification head bias as a specific value when use focal loss.""" + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + else: + # adopt the default initialization for + # the weight and bias of the layer norm + pass + if self.kernel_init: + print_log( + 'mask kernel in mask head is normal initialized by std 0.01') + nn.init.normal_(self.fc_mask.weight, mean=0, std=0.01) + + def forward(self, x, proposal_feat, mask_preds, mask_shape=None): + """Forward function of Dynamic Instance Interactive Head. + + Args: + x (Tensor): Feature map from FPN with shape + (batch_size, feature_dimensions, H , W). + proposal_feat (Tensor): Intermediate feature get from + diihead in last stage, has shape + (batch_size, num_proposals, feature_dimensions) + mask_preds (Tensor): mask prediction from the former stage in shape + (batch_size, num_proposals, H, W). + + Returns: + Tuple: The first tensor is predicted mask with shape + (N, num_classes, H, W), the second tensor is dynamic kernel + with shape (N, num_classes, channels, K, K). + """ + N, num_proposals = proposal_feat.shape[:2] + if self.feat_transform is not None: + x = self.feat_transform(x) + + C, H, W = x.shape[-3:] + + mask_h, mask_w = mask_preds.shape[-2:] + if mask_h != H or mask_w != W: + gather_mask = F.interpolate( + mask_preds, (H, W), align_corners=False, mode='bilinear') + else: + gather_mask = mask_preds + + sigmoid_masks = gather_mask.softmax(dim=1) + + # Group Feature Assembling. Eq.(3) in original paper. + # einsum is faster than bmm by 30% + x_feat = torch.einsum('bnhw,bchw->bnc', sigmoid_masks, x) + + # obj_feat in shape [B, N, C, K, K] -> [B, N, C, K*K] -> [B, N, K*K, C] + proposal_feat = proposal_feat.reshape(N, num_proposals, + self.in_channels, + -1).permute(0, 1, 3, 2) + obj_feat = self.kernel_update_conv(x_feat, proposal_feat) + + # [B, N, K*K, C] -> [B, N, K*K*C] -> [N, B, K*K*C] + obj_feat = obj_feat.reshape(N, num_proposals, -1).permute(1, 0, 2) + obj_feat = self.attention_norm(self.attention(obj_feat)) + # [N, B, K*K*C] -> [B, N, K*K*C] + obj_feat = obj_feat.permute(1, 0, 2) + + # obj_feat in shape [B, N, K*K*C] -> [B, N, K*K, C] + obj_feat = obj_feat.reshape(N, num_proposals, -1, self.in_channels) + + # FFN + if self.with_ffn: + obj_feat = self.ffn_norm(self.ffn(obj_feat)) + + mask_feat = obj_feat + + for reg_layer in self.mask_fcs: + mask_feat = reg_layer(mask_feat) + + # [B, N, K*K, C] -> [B, N, C, K*K] + mask_feat = self.fc_mask(mask_feat).permute(0, 1, 3, 2) + + if (self.mask_transform_stride == 2 and self.feat_gather_stride == 1): + mask_x = F.interpolate( + x, scale_factor=0.5, mode='bilinear', align_corners=False) + H, W = mask_x.shape[-2:] + else: + mask_x = x + # group conv is 5x faster than unfold and uses about 1/5 memory + # Group conv vs. unfold vs. concat batch, 2.9ms :13.5ms :3.8ms + # Group conv vs. unfold vs. concat batch, 278 : 1420 : 369 + # but in real training group conv is slower than concat batch + # so we keep using concat batch. + # fold_x = F.unfold( + # mask_x, + # self.conv_kernel_size, + # padding=int(self.conv_kernel_size // 2)) + # mask_feat = mask_feat.reshape(N, num_proposals, -1) + # new_mask_preds = torch.einsum('bnc,bcl->bnl', mask_feat, fold_x) + # [B, N, C, K*K] -> [B*N, C, K, K] + mask_feat = mask_feat.reshape(N, num_proposals, C, + self.conv_kernel_size, + self.conv_kernel_size) + # [B, C, H, W] -> [1, B*C, H, W] + new_mask_preds = [] + for i in range(N): + new_mask_preds.append( + F.conv2d( + mask_x[i:i + 1], + mask_feat[i], + padding=int(self.conv_kernel_size // 2))) + + new_mask_preds = torch.cat(new_mask_preds, dim=0) + new_mask_preds = new_mask_preds.reshape(N, num_proposals, H, W) + if self.mask_transform_stride == 2: + new_mask_preds = F.interpolate( + new_mask_preds, + scale_factor=2, + mode='bilinear', + align_corners=False) + + if mask_shape is not None and mask_shape[0] != H: + new_mask_preds = F.interpolate( + new_mask_preds, + mask_shape, + align_corners=False, + mode='bilinear') + + return new_mask_preds, obj_feat.permute(0, 1, 3, 2).reshape( + N, num_proposals, self.in_channels, self.conv_kernel_size, + self.conv_kernel_size) + + +@MODELS.register_module() +class IterativeDecodeHead(BaseDecodeHead): + """K-Net: Towards Unified Image Segmentation. + + This head is the implementation of + `K-Net: `_. + + Args: + num_stages (int): The number of stages (kernel update heads) + in IterativeDecodeHead. Default: 3. + kernel_generate_head:(dict): Config of kernel generate head which + generate mask predictions, dynamic kernels and class predictions + for next kernel update heads. + kernel_update_head (dict): Config of kernel update head which refine + dynamic kernels and class predictions iteratively. + + """ + + def __init__(self, num_stages, kernel_generate_head, kernel_update_head, + **kwargs): + # ``IterativeDecodeHead`` would skip initialization of + # ``BaseDecodeHead`` which would be called when building + # ``self.kernel_generate_head``. + super(BaseDecodeHead, self).__init__(**kwargs) + assert num_stages == len(kernel_update_head) + self.num_stages = num_stages + self.kernel_generate_head = MODELS.build(kernel_generate_head) + self.kernel_update_head = nn.ModuleList() + self.align_corners = self.kernel_generate_head.align_corners + self.num_classes = self.kernel_generate_head.num_classes + self.input_transform = self.kernel_generate_head.input_transform + self.ignore_index = self.kernel_generate_head.ignore_index + self.out_channels = self.num_classes + + for head_cfg in kernel_update_head: + self.kernel_update_head.append(MODELS.build(head_cfg)) + + def forward(self, inputs): + """Forward function.""" + feats = self.kernel_generate_head._forward_feature(inputs) + sem_seg = self.kernel_generate_head.cls_seg(feats) + seg_kernels = self.kernel_generate_head.conv_seg.weight.clone() + seg_kernels = seg_kernels[None].expand( + feats.size(0), *seg_kernels.size()) + + stage_segs = [sem_seg] + for i in range(self.num_stages): + sem_seg, seg_kernels = self.kernel_update_head[i](feats, + seg_kernels, + sem_seg) + stage_segs.append(sem_seg) + if self.training: + return stage_segs + # only return the prediction of the last stage during testing + return stage_segs[-1] + + def loss_by_feat(self, seg_logits: List[Tensor], + batch_data_samples: SampleList, **kwargs) -> dict: + losses = dict() + for i, logit in enumerate(seg_logits): + loss = self.kernel_generate_head.loss_by_feat( + logit, batch_data_samples) + for k, v in loss.items(): + losses[f'{k}.s{i}'] = v + + return losses diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/lraspp_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/lraspp_head.py new file mode 100644 index 0000000..ba2465f --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/lraspp_head.py @@ -0,0 +1,91 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.utils import is_tuple_of + +from mmseg.registry import MODELS +from ..utils import resize +from .decode_head import BaseDecodeHead + + +@MODELS.register_module() +class LRASPPHead(BaseDecodeHead): + """Lite R-ASPP (LRASPP) head is proposed in Searching for MobileNetV3. + + This head is the improved implementation of `Searching for MobileNetV3 + `_. + + Args: + branch_channels (tuple[int]): The number of output channels in every + each branch. Default: (32, 64). + """ + + def __init__(self, branch_channels=(32, 64), **kwargs): + super().__init__(**kwargs) + if self.input_transform != 'multiple_select': + raise ValueError('in Lite R-ASPP (LRASPP) head, input_transform ' + f'must be \'multiple_select\'. But received ' + f'\'{self.input_transform}\'') + assert is_tuple_of(branch_channels, int) + assert len(branch_channels) == len(self.in_channels) - 1 + self.branch_channels = branch_channels + + self.convs = nn.Sequential() + self.conv_ups = nn.Sequential() + for i in range(len(branch_channels)): + self.convs.add_module( + f'conv{i}', + nn.Conv2d( + self.in_channels[i], branch_channels[i], 1, bias=False)) + self.conv_ups.add_module( + f'conv_up{i}', + ConvModule( + self.channels + branch_channels[i], + self.channels, + 1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + bias=False)) + + self.conv_up_input = nn.Conv2d(self.channels, self.channels, 1) + + self.aspp_conv = ConvModule( + self.in_channels[-1], + self.channels, + 1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + bias=False) + self.image_pool = nn.Sequential( + nn.AvgPool2d(kernel_size=49, stride=(16, 20)), + ConvModule( + self.in_channels[2], + self.channels, + 1, + act_cfg=dict(type='Sigmoid'), + bias=False)) + + def forward(self, inputs): + """Forward function.""" + inputs = self._transform_inputs(inputs) + + x = inputs[-1] + + x = self.aspp_conv(x) * resize( + self.image_pool(x), + size=x.size()[2:], + mode='bilinear', + align_corners=self.align_corners) + x = self.conv_up_input(x) + + for i in range(len(self.branch_channels) - 1, -1, -1): + x = resize( + x, + size=inputs[i].size()[2:], + mode='bilinear', + align_corners=self.align_corners) + x = torch.cat([x, self.convs[i](inputs[i])], 1) + x = self.conv_ups[i](x) + + return self.cls_seg(x) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/mask2former_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/mask2former_head.py new file mode 100644 index 0000000..0135af0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/mask2former_head.py @@ -0,0 +1,163 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.model import BaseModule + +try: + from mmdet.models.dense_heads import \ + Mask2FormerHead as MMDET_Mask2FormerHead +except ModuleNotFoundError: + MMDET_Mask2FormerHead = BaseModule + +from mmengine.structures import InstanceData +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.structures.seg_data_sample import SegDataSample +from mmseg.utils import ConfigType, SampleList + + +@MODELS.register_module() +class Mask2FormerHead(MMDET_Mask2FormerHead): + """Implements the Mask2Former head. + + See `Mask2Former: Masked-attention Mask Transformer for Universal Image + Segmentation `_ for details. + + Args: + num_classes (int): Number of classes. Default: 150. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + ignore_index (int): The label index to be ignored. Default: 255. + """ + + def __init__(self, + num_classes, + align_corners=False, + ignore_index=255, + **kwargs): + super().__init__(**kwargs) + + self.num_classes = num_classes + self.align_corners = align_corners + self.out_channels = num_classes + self.ignore_index = ignore_index + + feat_channels = kwargs['feat_channels'] + self.cls_embed = nn.Linear(feat_channels, self.num_classes + 1) + + def _seg_data_to_instance_data(self, batch_data_samples: SampleList): + """Perform forward propagation to convert paradigm from MMSegmentation + to MMDetection to ensure ``MMDET_Mask2FormerHead`` could be called + normally. Specifically, ``batch_gt_instances`` would be added. + + Args: + batch_data_samples (List[:obj:`SegDataSample`]): The Data + Samples. It usually includes information such as + `gt_sem_seg`. + + Returns: + tuple[Tensor]: A tuple contains two lists. + + - batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``labels``, each is + unique ground truth label id of images, with + shape (num_gt, ) and ``masks``, each is ground truth + masks of each instances of a image, shape (num_gt, h, w). + - batch_img_metas (list[dict]): List of image meta information. + """ + batch_img_metas = [] + batch_gt_instances = [] + + for data_sample in batch_data_samples: + batch_img_metas.append(data_sample.metainfo) + gt_sem_seg = data_sample.gt_sem_seg.data + classes = torch.unique( + gt_sem_seg, + sorted=False, + return_inverse=False, + return_counts=False) + + # remove ignored region + gt_labels = classes[classes != self.ignore_index] + + masks = [] + for class_id in gt_labels: + masks.append(gt_sem_seg == class_id) + + if len(masks) == 0: + gt_masks = torch.zeros( + (0, gt_sem_seg.shape[-2], + gt_sem_seg.shape[-1])).to(gt_sem_seg).long() + else: + gt_masks = torch.stack(masks).squeeze(1).long() + + instance_data = InstanceData(labels=gt_labels, masks=gt_masks) + batch_gt_instances.append(instance_data) + return batch_gt_instances, batch_img_metas + + def loss(self, x: Tuple[Tensor], batch_data_samples: SampleList, + train_cfg: ConfigType) -> dict: + """Perform forward propagation and loss calculation of the decoder head + on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the upstream + network, each is a 4D-tensor. + batch_data_samples (List[:obj:`SegDataSample`]): The Data + Samples. It usually includes information such as + `gt_sem_seg`. + train_cfg (ConfigType): Training config. + + Returns: + dict[str, Tensor]: a dictionary of loss components. + """ + # batch SegDataSample to InstanceDataSample + batch_gt_instances, batch_img_metas = self._seg_data_to_instance_data( + batch_data_samples) + + # forward + all_cls_scores, all_mask_preds = self(x, batch_data_samples) + + # loss + losses = self.loss_by_feat(all_cls_scores, all_mask_preds, + batch_gt_instances, batch_img_metas) + + return losses + + def predict(self, x: Tuple[Tensor], batch_img_metas: List[dict], + test_cfg: ConfigType) -> Tuple[Tensor]: + """Test without augmentaton. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_img_metas (List[:obj:`SegDataSample`]): The Data + Samples. It usually includes information such as + `gt_sem_seg`. + test_cfg (ConfigType): Test config. + + Returns: + Tensor: A tensor of segmentation mask. + """ + batch_data_samples = [ + SegDataSample(metainfo=metainfo) for metainfo in batch_img_metas + ] + + all_cls_scores, all_mask_preds = self(x, batch_data_samples) + mask_cls_results = all_cls_scores[-1] + mask_pred_results = all_mask_preds[-1] + if 'pad_shape' in batch_img_metas[0]: + size = batch_img_metas[0]['pad_shape'] + else: + size = batch_img_metas[0]['img_shape'] + # upsample mask + mask_pred_results = F.interpolate( + mask_pred_results, size=size, mode='bilinear', align_corners=False) + cls_score = F.softmax(mask_cls_results, dim=-1)[..., :-1] + mask_pred = mask_pred_results.sigmoid() + seg_logits = torch.einsum('bqc, bqhw->bchw', cls_score, mask_pred) + return seg_logits diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/maskformer_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/maskformer_head.py new file mode 100644 index 0000000..6e61a7f --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/maskformer_head.py @@ -0,0 +1,174 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.model import BaseModule + +try: + from mmdet.models.dense_heads import MaskFormerHead as MMDET_MaskFormerHead +except ModuleNotFoundError: + MMDET_MaskFormerHead = BaseModule + +from mmengine.structures import InstanceData +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.structures.seg_data_sample import SegDataSample +from mmseg.utils import ConfigType, SampleList + + +@MODELS.register_module() +class MaskFormerHead(MMDET_MaskFormerHead): + """Implements the MaskFormer head. + + See `Per-Pixel Classification is Not All You Need for Semantic Segmentation + `_ for details. + + Args: + num_classes (int): Number of classes. Default: 150. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + ignore_index (int): The label index to be ignored. Default: 255. + """ + + def __init__(self, + num_classes: int = 150, + align_corners: bool = False, + ignore_index: int = 255, + **kwargs) -> None: + super().__init__(**kwargs) + + self.out_channels = kwargs['out_channels'] + self.align_corners = True + self.num_classes = num_classes + self.align_corners = align_corners + self.out_channels = num_classes + self.ignore_index = ignore_index + + feat_channels = kwargs['feat_channels'] + self.cls_embed = nn.Linear(feat_channels, self.num_classes + 1) + + def _seg_data_to_instance_data(self, batch_data_samples: SampleList): + """Perform forward propagation to convert paradigm from MMSegmentation + to MMDetection to ensure ``MMDET_MaskFormerHead`` could be called + normally. Specifically, ``batch_gt_instances`` would be added. + + Args: + batch_data_samples (List[:obj:`SegDataSample`]): The Data + Samples. It usually includes information such as + `gt_sem_seg`. + + Returns: + tuple[Tensor]: A tuple contains two lists. + + - batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``labels``, each is + unique ground truth label id of images, with + shape (num_gt, ) and ``masks``, each is ground truth + masks of each instances of a image, shape (num_gt, h, w). + - batch_img_metas (list[dict]): List of image meta information. + """ + batch_img_metas = [] + batch_gt_instances = [] + for data_sample in batch_data_samples: + # Add `batch_input_shape` in metainfo of data_sample, which would + # be used in MaskFormerHead of MMDetection. + metainfo = data_sample.metainfo + metainfo['batch_input_shape'] = metainfo['img_shape'] + data_sample.set_metainfo(metainfo) + batch_img_metas.append(data_sample.metainfo) + gt_sem_seg = data_sample.gt_sem_seg.data + classes = torch.unique( + gt_sem_seg, + sorted=False, + return_inverse=False, + return_counts=False) + + # remove ignored region + gt_labels = classes[classes != self.ignore_index] + + masks = [] + for class_id in gt_labels: + masks.append(gt_sem_seg == class_id) + + if len(masks) == 0: + gt_masks = torch.zeros((0, gt_sem_seg.shape[-2], + gt_sem_seg.shape[-1])).to(gt_sem_seg) + else: + gt_masks = torch.stack(masks).squeeze(1) + + instance_data = InstanceData( + labels=gt_labels, masks=gt_masks.long()) + batch_gt_instances.append(instance_data) + return batch_gt_instances, batch_img_metas + + def loss(self, x: Tuple[Tensor], batch_data_samples: SampleList, + train_cfg: ConfigType) -> dict: + """Perform forward propagation and loss calculation of the decoder head + on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the upstream + network, each is a 4D-tensor. + batch_data_samples (List[:obj:`SegDataSample`]): The Data + Samples. It usually includes information such as + `gt_sem_seg`. + train_cfg (ConfigType): Training config. + + Returns: + dict[str, Tensor]: a dictionary of loss components. + """ + # batch SegDataSample to InstanceDataSample + batch_gt_instances, batch_img_metas = self._seg_data_to_instance_data( + batch_data_samples) + + # forward + all_cls_scores, all_mask_preds = self(x, batch_data_samples) + + # loss + losses = self.loss_by_feat(all_cls_scores, all_mask_preds, + batch_gt_instances, batch_img_metas) + + return losses + + def predict(self, x: Tuple[Tensor], batch_img_metas: List[dict], + test_cfg: ConfigType) -> Tuple[Tensor]: + """Test without augmentaton. + + Args: + x (tuple[Tensor]): Multi-level features from the + upstream network, each is a 4D-tensor. + batch_img_metas (List[:obj:`SegDataSample`]): The Data + Samples. It usually includes information such as + `gt_sem_seg`. + test_cfg (ConfigType): Test config. + + Returns: + Tensor: A tensor of segmentation mask. + """ + + batch_data_samples = [] + for metainfo in batch_img_metas: + metainfo['batch_input_shape'] = metainfo['img_shape'] + batch_data_samples.append(SegDataSample(metainfo=metainfo)) + # Forward function of MaskFormerHead from MMDetection needs + # 'batch_data_samples' as inputs, which is image shape actually. + all_cls_scores, all_mask_preds = self(x, batch_data_samples) + mask_cls_results = all_cls_scores[-1] + mask_pred_results = all_mask_preds[-1] + + # upsample masks + img_shape = batch_img_metas[0]['batch_input_shape'] + mask_pred_results = F.interpolate( + mask_pred_results, + size=img_shape, + mode='bilinear', + align_corners=False) + + # semantic inference + cls_score = F.softmax(mask_cls_results, dim=-1)[..., :-1] + mask_pred = mask_pred_results.sigmoid() + seg_logits = torch.einsum('bqc,bqhw->bchw', cls_score, mask_pred) + return seg_logits diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/nl_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/nl_head.py new file mode 100644 index 0000000..0ffcc2a --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/nl_head.py @@ -0,0 +1,50 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.cnn import NonLocal2d + +from mmseg.registry import MODELS +from .fcn_head import FCNHead + + +@MODELS.register_module() +class NLHead(FCNHead): + """Non-local Neural Networks. + + This head is the implementation of `NLNet + `_. + + Args: + reduction (int): Reduction factor of projection transform. Default: 2. + use_scale (bool): Whether to scale pairwise_weight by + sqrt(1/inter_channels). Default: True. + mode (str): The nonlocal mode. Options are 'embedded_gaussian', + 'dot_product'. Default: 'embedded_gaussian.'. + """ + + def __init__(self, + reduction=2, + use_scale=True, + mode='embedded_gaussian', + **kwargs): + super().__init__(num_convs=2, **kwargs) + self.reduction = reduction + self.use_scale = use_scale + self.mode = mode + self.nl_block = NonLocal2d( + in_channels=self.channels, + reduction=self.reduction, + use_scale=self.use_scale, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + mode=self.mode) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + output = self.convs[0](x) + output = self.nl_block(output) + output = self.convs[1](output) + if self.concat_input: + output = self.conv_cat(torch.cat([x, output], dim=1)) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ocr_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ocr_head.py new file mode 100644 index 0000000..9afe37b --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/ocr_head.py @@ -0,0 +1,127 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import SelfAttentionBlock as _SelfAttentionBlock +from ..utils import resize +from .cascade_decode_head import BaseCascadeDecodeHead + + +class SpatialGatherModule(nn.Module): + """Aggregate the context features according to the initial predicted + probability distribution. + + Employ the soft-weighted method to aggregate the context. + """ + + def __init__(self, scale): + super().__init__() + self.scale = scale + + def forward(self, feats, probs): + """Forward function.""" + batch_size, num_classes, height, width = probs.size() + channels = feats.size(1) + probs = probs.view(batch_size, num_classes, -1) + feats = feats.view(batch_size, channels, -1) + # [batch_size, height*width, num_classes] + feats = feats.permute(0, 2, 1) + # [batch_size, channels, height*width] + probs = F.softmax(self.scale * probs, dim=2) + # [batch_size, channels, num_classes] + ocr_context = torch.matmul(probs, feats) + ocr_context = ocr_context.permute(0, 2, 1).contiguous().unsqueeze(3) + return ocr_context + + +class ObjectAttentionBlock(_SelfAttentionBlock): + """Make a OCR used SelfAttentionBlock.""" + + def __init__(self, in_channels, channels, scale, conv_cfg, norm_cfg, + act_cfg): + if scale > 1: + query_downsample = nn.MaxPool2d(kernel_size=scale) + else: + query_downsample = None + super().__init__( + key_in_channels=in_channels, + query_in_channels=in_channels, + channels=channels, + out_channels=in_channels, + share_key_query=False, + query_downsample=query_downsample, + key_downsample=None, + key_query_num_convs=2, + key_query_norm=True, + value_out_num_convs=1, + value_out_norm=True, + matmul_norm=True, + with_out=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.bottleneck = ConvModule( + in_channels * 2, + in_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, query_feats, key_feats): + """Forward function.""" + context = super().forward(query_feats, key_feats) + output = self.bottleneck(torch.cat([context, query_feats], dim=1)) + if self.query_downsample is not None: + output = resize(query_feats) + + return output + + +@MODELS.register_module() +class OCRHead(BaseCascadeDecodeHead): + """Object-Contextual Representations for Semantic Segmentation. + + This head is the implementation of `OCRNet + `_. + + Args: + ocr_channels (int): The intermediate channels of OCR block. + scale (int): The scale of probability map in SpatialGatherModule in + Default: 1. + """ + + def __init__(self, ocr_channels, scale=1, **kwargs): + super().__init__(**kwargs) + self.ocr_channels = ocr_channels + self.scale = scale + self.object_context_block = ObjectAttentionBlock( + self.channels, + self.ocr_channels, + self.scale, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.spatial_gather_module = SpatialGatherModule(self.scale) + + self.bottleneck = ConvModule( + self.in_channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, inputs, prev_output): + """Forward function.""" + x = self._transform_inputs(inputs) + feats = self.bottleneck(x) + context = self.spatial_gather_module(feats, prev_output) + object_context = self.object_context_block(feats, context) + output = self.cls_seg(object_context) + + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/pid_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/pid_head.py new file mode 100644 index 0000000..c092cb3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/pid_head.py @@ -0,0 +1,183 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Tuple, Union + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, build_activation_layer, build_norm_layer +from mmengine.model import BaseModule +from torch import Tensor + +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.models.losses import accuracy +from mmseg.models.utils import resize +from mmseg.registry import MODELS +from mmseg.utils import OptConfigType, SampleList + + +class BasePIDHead(BaseModule): + """Base class for PID head. + + Args: + in_channels (int): Number of input channels. + channels (int): Number of output channels. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU', inplace=True). + init_cfg (dict or list[dict], optional): Init config dict. + Default: None. + """ + + def __init__(self, + in_channels: int, + channels: int, + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + init_cfg: OptConfigType = None): + super().__init__(init_cfg) + self.conv = ConvModule( + in_channels, + channels, + kernel_size=3, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + order=('norm', 'act', 'conv')) + _, self.norm = build_norm_layer(norm_cfg, num_features=channels) + self.act = build_activation_layer(act_cfg) + + def forward(self, x: Tensor, cls_seg: Optional[nn.Module]) -> Tensor: + """Forward function. + Args: + x (Tensor): Input tensor. + cls_seg (nn.Module, optional): The classification head. + + Returns: + Tensor: Output tensor. + """ + x = self.conv(x) + x = self.norm(x) + x = self.act(x) + if cls_seg is not None: + x = cls_seg(x) + return x + + +@MODELS.register_module() +class PIDHead(BaseDecodeHead): + """Decode head for PIDNet. + + Args: + in_channels (int): Number of input channels. + channels (int): Number of output channels. + num_classes (int): Number of classes. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU', inplace=True). + """ + + def __init__(self, + in_channels: int, + channels: int, + num_classes: int, + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + **kwargs): + super().__init__( + in_channels, + channels, + num_classes=num_classes, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **kwargs) + self.i_head = BasePIDHead(in_channels, channels, norm_cfg, act_cfg) + self.p_head = BasePIDHead(in_channels // 2, channels, norm_cfg, + act_cfg) + self.d_head = BasePIDHead( + in_channels // 2, + in_channels // 4, + norm_cfg, + ) + self.p_cls_seg = nn.Conv2d(channels, self.out_channels, kernel_size=1) + self.d_cls_seg = nn.Conv2d(in_channels // 4, 1, kernel_size=1) + + def init_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_( + m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + + def forward( + self, + inputs: Union[Tensor, + Tuple[Tensor]]) -> Union[Tensor, Tuple[Tensor]]: + """Forward function. + Args: + inputs (Tensor | tuple[Tensor]): Input tensor or tuple of + Tensor. When training, the input is a tuple of three tensors, + (p_feat, i_feat, d_feat), and the output is a tuple of three + tensors, (p_seg_logit, i_seg_logit, d_seg_logit). + When inference, only the head of integral branch is used, and + input is a tensor of integral feature map, and the output is + the segmentation logit. + + Returns: + Tensor | tuple[Tensor]: Output tensor or tuple of tensors. + """ + if self.training: + x_p, x_i, x_d = inputs + x_p = self.p_head(x_p, self.p_cls_seg) + x_i = self.i_head(x_i, self.cls_seg) + x_d = self.d_head(x_d, self.d_cls_seg) + return x_p, x_i, x_d + else: + return self.i_head(inputs, self.cls_seg) + + def _stack_batch_gt(self, batch_data_samples: SampleList) -> Tuple[Tensor]: + gt_semantic_segs = [ + data_sample.gt_sem_seg.data for data_sample in batch_data_samples + ] + gt_edge_segs = [ + data_sample.gt_edge_map.data for data_sample in batch_data_samples + ] + gt_sem_segs = torch.stack(gt_semantic_segs, dim=0) + gt_edge_segs = torch.stack(gt_edge_segs, dim=0) + return gt_sem_segs, gt_edge_segs + + def loss_by_feat(self, seg_logits: Tuple[Tensor], + batch_data_samples: SampleList) -> dict: + loss = dict() + p_logit, i_logit, d_logit = seg_logits + sem_label, bd_label = self._stack_batch_gt(batch_data_samples) + p_logit = resize( + input=p_logit, + size=sem_label.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + i_logit = resize( + input=i_logit, + size=sem_label.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + d_logit = resize( + input=d_logit, + size=bd_label.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + sem_label = sem_label.squeeze(1) + bd_label = bd_label.squeeze(1) + loss['loss_sem_p'] = self.loss_decode[0]( + p_logit, sem_label, ignore_index=self.ignore_index) + loss['loss_sem_i'] = self.loss_decode[1](i_logit, sem_label) + loss['loss_bd'] = self.loss_decode[2](d_logit, bd_label) + filler = torch.ones_like(sem_label) * self.ignore_index + sem_bd_label = torch.where( + torch.sigmoid(d_logit[:, 0, :, :]) > 0.8, sem_label, filler) + loss['loss_sem_bd'] = self.loss_decode[3](i_logit, sem_bd_label) + loss['acc_seg'] = accuracy( + i_logit, sem_label, ignore_index=self.ignore_index) + return loss diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/point_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/point_head.py new file mode 100644 index 0000000..e8e433d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/point_head.py @@ -0,0 +1,367 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Modified from https://github.com/facebookresearch/detectron2/tree/master/projects/PointRend/point_head/point_head.py # noqa + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +try: + from mmcv.ops import point_sample +except ModuleNotFoundError: + point_sample = None + +from typing import List + +from mmseg.registry import MODELS +from mmseg.utils import SampleList +from ..losses import accuracy +from ..utils import resize +from .cascade_decode_head import BaseCascadeDecodeHead + + +def calculate_uncertainty(seg_logits): + """Estimate uncertainty based on seg logits. + + For each location of the prediction ``seg_logits`` we estimate + uncertainty as the difference between top first and top second + predicted logits. + + Args: + seg_logits (Tensor): Semantic segmentation logits, + shape (batch_size, num_classes, height, width). + + Returns: + scores (Tensor): T uncertainty scores with the most uncertain + locations having the highest uncertainty score, shape ( + batch_size, 1, height, width) + """ + top2_scores = torch.topk(seg_logits, k=2, dim=1)[0] + return (top2_scores[:, 1] - top2_scores[:, 0]).unsqueeze(1) + + +@MODELS.register_module() +class PointHead(BaseCascadeDecodeHead): + """A mask point head use in PointRend. + + This head is implemented of `PointRend: Image Segmentation as + Rendering `_. + ``PointHead`` use shared multi-layer perceptron (equivalent to + nn.Conv1d) to predict the logit of input points. The fine-grained feature + and coarse feature will be concatenate together for predication. + + Args: + num_fcs (int): Number of fc layers in the head. Default: 3. + in_channels (int): Number of input channels. Default: 256. + fc_channels (int): Number of fc channels. Default: 256. + num_classes (int): Number of classes for logits. Default: 80. + class_agnostic (bool): Whether use class agnostic classification. + If so, the output channels of logits will be 1. Default: False. + coarse_pred_each_layer (bool): Whether concatenate coarse feature with + the output of each fc layer. Default: True. + conv_cfg (dict|None): Dictionary to construct and config conv layer. + Default: dict(type='Conv1d')) + norm_cfg (dict|None): Dictionary to construct and config norm layer. + Default: None. + loss_point (dict): Dictionary to construct and config loss layer of + point head. Default: dict(type='CrossEntropyLoss', use_mask=True, + loss_weight=1.0). + """ + + def __init__(self, + num_fcs=3, + coarse_pred_each_layer=True, + conv_cfg=dict(type='Conv1d'), + norm_cfg=None, + act_cfg=dict(type='ReLU', inplace=False), + **kwargs): + super().__init__( + input_transform='multiple_select', + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + init_cfg=dict( + type='Normal', std=0.01, override=dict(name='fc_seg')), + **kwargs) + if point_sample is None: + raise RuntimeError('Please install mmcv-full for ' + 'point_sample ops') + + self.num_fcs = num_fcs + self.coarse_pred_each_layer = coarse_pred_each_layer + + fc_in_channels = sum(self.in_channels) + self.num_classes + fc_channels = self.channels + self.fcs = nn.ModuleList() + for k in range(num_fcs): + fc = ConvModule( + fc_in_channels, + fc_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.fcs.append(fc) + fc_in_channels = fc_channels + fc_in_channels += self.num_classes if self.coarse_pred_each_layer \ + else 0 + self.fc_seg = nn.Conv1d( + fc_in_channels, + self.num_classes, + kernel_size=1, + stride=1, + padding=0) + if self.dropout_ratio > 0: + self.dropout = nn.Dropout(self.dropout_ratio) + delattr(self, 'conv_seg') + + def cls_seg(self, feat): + """Classify each pixel with fc.""" + if self.dropout is not None: + feat = self.dropout(feat) + output = self.fc_seg(feat) + return output + + def forward(self, fine_grained_point_feats, coarse_point_feats): + x = torch.cat([fine_grained_point_feats, coarse_point_feats], dim=1) + for fc in self.fcs: + x = fc(x) + if self.coarse_pred_each_layer: + x = torch.cat((x, coarse_point_feats), dim=1) + return self.cls_seg(x) + + def _get_fine_grained_point_feats(self, x, points): + """Sample from fine grained features. + + Args: + x (list[Tensor]): Feature pyramid from by neck or backbone. + points (Tensor): Point coordinates, shape (batch_size, + num_points, 2). + + Returns: + fine_grained_feats (Tensor): Sampled fine grained feature, + shape (batch_size, sum(channels of x), num_points). + """ + + fine_grained_feats_list = [ + point_sample(_, points, align_corners=self.align_corners) + for _ in x + ] + if len(fine_grained_feats_list) > 1: + fine_grained_feats = torch.cat(fine_grained_feats_list, dim=1) + else: + fine_grained_feats = fine_grained_feats_list[0] + + return fine_grained_feats + + def _get_coarse_point_feats(self, prev_output, points): + """Sample from fine grained features. + + Args: + prev_output (list[Tensor]): Prediction of previous decode head. + points (Tensor): Point coordinates, shape (batch_size, + num_points, 2). + + Returns: + coarse_feats (Tensor): Sampled coarse feature, shape (batch_size, + num_classes, num_points). + """ + + coarse_feats = point_sample( + prev_output, points, align_corners=self.align_corners) + + return coarse_feats + + def loss(self, inputs, prev_output, batch_data_samples: SampleList, + train_cfg, **kwargs): + """Forward function for training. + Args: + inputs (list[Tensor]): List of multi-level img features. + prev_output (Tensor): The output of previous decode head. + batch_data_samples (list[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `img_metas` or `gt_semantic_seg`. + train_cfg (dict): The training config. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + x = self._transform_inputs(inputs) + with torch.no_grad(): + points = self.get_points_train( + prev_output, calculate_uncertainty, cfg=train_cfg) + fine_grained_point_feats = self._get_fine_grained_point_feats( + x, points) + coarse_point_feats = self._get_coarse_point_feats(prev_output, points) + point_logits = self.forward(fine_grained_point_feats, + coarse_point_feats) + + losses = self.loss_by_feat(point_logits, points, batch_data_samples) + + return losses + + def predict(self, inputs, prev_output, batch_img_metas: List[dict], + test_cfg, **kwargs): + """Forward function for testing. + + Args: + inputs (list[Tensor]): List of multi-level img features. + prev_output (Tensor): The output of previous decode head. + img_metas (list[dict]): List of image info dict where each dict + has: 'img_shape', 'scale_factor', 'flip', and may also contain + 'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:Collect`. + test_cfg (dict): The testing config. + + Returns: + Tensor: Output segmentation map. + """ + + x = self._transform_inputs(inputs) + refined_seg_logits = prev_output.clone() + for _ in range(test_cfg.subdivision_steps): + refined_seg_logits = resize( + refined_seg_logits, + scale_factor=test_cfg.scale_factor, + mode='bilinear', + align_corners=self.align_corners) + batch_size, channels, height, width = refined_seg_logits.shape + point_indices, points = self.get_points_test( + refined_seg_logits, calculate_uncertainty, cfg=test_cfg) + fine_grained_point_feats = self._get_fine_grained_point_feats( + x, points) + coarse_point_feats = self._get_coarse_point_feats( + prev_output, points) + point_logits = self.forward(fine_grained_point_feats, + coarse_point_feats) + + point_indices = point_indices.unsqueeze(1).expand(-1, channels, -1) + refined_seg_logits = refined_seg_logits.reshape( + batch_size, channels, height * width) + refined_seg_logits = refined_seg_logits.scatter_( + 2, point_indices, point_logits) + refined_seg_logits = refined_seg_logits.view( + batch_size, channels, height, width) + + return self.predict_by_feat(refined_seg_logits, batch_img_metas, + **kwargs) + + def loss_by_feat(self, point_logits, points, batch_data_samples, **kwargs): + """Compute segmentation loss.""" + gt_semantic_seg = self._stack_batch_gt(batch_data_samples) + point_label = point_sample( + gt_semantic_seg.float(), + points, + mode='nearest', + align_corners=self.align_corners) + point_label = point_label.squeeze(1).long() + + loss = dict() + if not isinstance(self.loss_decode, nn.ModuleList): + losses_decode = [self.loss_decode] + else: + losses_decode = self.loss_decode + for loss_module in losses_decode: + loss['point' + loss_module.loss_name] = loss_module( + point_logits, point_label, ignore_index=self.ignore_index) + + loss['acc_point'] = accuracy( + point_logits, point_label, ignore_index=self.ignore_index) + return loss + + def get_points_train(self, seg_logits, uncertainty_func, cfg): + """Sample points for training. + + Sample points in [0, 1] x [0, 1] coordinate space based on their + uncertainty. The uncertainties are calculated for each point using + 'uncertainty_func' function that takes point's logit prediction as + input. + + Args: + seg_logits (Tensor): Semantic segmentation logits, shape ( + batch_size, num_classes, height, width). + uncertainty_func (func): uncertainty calculation function. + cfg (dict): Training config of point head. + + Returns: + point_coords (Tensor): A tensor of shape (batch_size, num_points, + 2) that contains the coordinates of ``num_points`` sampled + points. + """ + num_points = cfg.num_points + oversample_ratio = cfg.oversample_ratio + importance_sample_ratio = cfg.importance_sample_ratio + assert oversample_ratio >= 1 + assert 0 <= importance_sample_ratio <= 1 + batch_size = seg_logits.shape[0] + num_sampled = int(num_points * oversample_ratio) + point_coords = torch.rand( + batch_size, num_sampled, 2, device=seg_logits.device) + point_logits = point_sample(seg_logits, point_coords) + # It is crucial to calculate uncertainty based on the sampled + # prediction value for the points. Calculating uncertainties of the + # coarse predictions first and sampling them for points leads to + # incorrect results. To illustrate this: assume uncertainty func( + # logits)=-abs(logits), a sampled point between two coarse + # predictions with -1 and 1 logits has 0 logits, and therefore 0 + # uncertainty value. However, if we calculate uncertainties for the + # coarse predictions first, both will have -1 uncertainty, + # and sampled point will get -1 uncertainty. + point_uncertainties = uncertainty_func(point_logits) + num_uncertain_points = int(importance_sample_ratio * num_points) + num_random_points = num_points - num_uncertain_points + idx = torch.topk( + point_uncertainties[:, 0, :], k=num_uncertain_points, dim=1)[1] + shift = num_sampled * torch.arange( + batch_size, dtype=torch.long, device=seg_logits.device) + idx += shift[:, None] + point_coords = point_coords.view(-1, 2)[idx.view(-1), :].view( + batch_size, num_uncertain_points, 2) + if num_random_points > 0: + rand_point_coords = torch.rand( + batch_size, num_random_points, 2, device=seg_logits.device) + point_coords = torch.cat((point_coords, rand_point_coords), dim=1) + return point_coords + + def get_points_test(self, seg_logits, uncertainty_func, cfg): + """Sample points for testing. + + Find ``num_points`` most uncertain points from ``uncertainty_map``. + + Args: + seg_logits (Tensor): A tensor of shape (batch_size, num_classes, + height, width) for class-specific or class-agnostic prediction. + uncertainty_func (func): uncertainty calculation function. + cfg (dict): Testing config of point head. + + Returns: + point_indices (Tensor): A tensor of shape (batch_size, num_points) + that contains indices from [0, height x width) of the most + uncertain points. + point_coords (Tensor): A tensor of shape (batch_size, num_points, + 2) that contains [0, 1] x [0, 1] normalized coordinates of the + most uncertain points from the ``height x width`` grid . + """ + + num_points = cfg.subdivision_num_points + uncertainty_map = uncertainty_func(seg_logits) + batch_size, _, height, width = uncertainty_map.shape + h_step = 1.0 / height + w_step = 1.0 / width + + uncertainty_map = uncertainty_map.view(batch_size, height * width) + num_points = min(height * width, num_points) + point_indices = uncertainty_map.topk(num_points, dim=1)[1] + point_coords = torch.zeros( + batch_size, + num_points, + 2, + dtype=torch.float, + device=seg_logits.device) + point_coords[:, :, 0] = w_step / 2.0 + (point_indices % + width).float() * w_step + point_coords[:, :, 1] = h_step / 2.0 + (point_indices // + width).float() * h_step + return point_indices, point_coords diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/psa_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/psa_head.py new file mode 100644 index 0000000..13ee5c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/psa_head.py @@ -0,0 +1,197 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import resize +from .decode_head import BaseDecodeHead + +try: + from mmcv.ops import PSAMask +except ModuleNotFoundError: + PSAMask = None + + +@MODELS.register_module() +class PSAHead(BaseDecodeHead): + """Point-wise Spatial Attention Network for Scene Parsing. + + This head is the implementation of `PSANet + `_. + + Args: + mask_size (tuple[int]): The PSA mask size. It usually equals input + size. + psa_type (str): The type of psa module. Options are 'collect', + 'distribute', 'bi-direction'. Default: 'bi-direction' + compact (bool): Whether use compact map for 'collect' mode. + Default: True. + shrink_factor (int): The downsample factors of psa mask. Default: 2. + normalization_factor (float): The normalize factor of attention. + psa_softmax (bool): Whether use softmax for attention. + """ + + def __init__(self, + mask_size, + psa_type='bi-direction', + compact=False, + shrink_factor=2, + normalization_factor=1.0, + psa_softmax=True, + **kwargs): + if PSAMask is None: + raise RuntimeError('Please install mmcv-full for PSAMask ops') + super().__init__(**kwargs) + assert psa_type in ['collect', 'distribute', 'bi-direction'] + self.psa_type = psa_type + self.compact = compact + self.shrink_factor = shrink_factor + self.mask_size = mask_size + mask_h, mask_w = mask_size + self.psa_softmax = psa_softmax + if normalization_factor is None: + normalization_factor = mask_h * mask_w + self.normalization_factor = normalization_factor + + self.reduce = ConvModule( + self.in_channels, + self.channels, + kernel_size=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.attention = nn.Sequential( + ConvModule( + self.channels, + self.channels, + kernel_size=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + nn.Conv2d( + self.channels, mask_h * mask_w, kernel_size=1, bias=False)) + if psa_type == 'bi-direction': + self.reduce_p = ConvModule( + self.in_channels, + self.channels, + kernel_size=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.attention_p = nn.Sequential( + ConvModule( + self.channels, + self.channels, + kernel_size=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + nn.Conv2d( + self.channels, mask_h * mask_w, kernel_size=1, bias=False)) + self.psamask_collect = PSAMask('collect', mask_size) + self.psamask_distribute = PSAMask('distribute', mask_size) + else: + self.psamask = PSAMask(psa_type, mask_size) + self.proj = ConvModule( + self.channels * (2 if psa_type == 'bi-direction' else 1), + self.in_channels, + kernel_size=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.bottleneck = ConvModule( + self.in_channels * 2, + self.channels, + kernel_size=3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + identity = x + align_corners = self.align_corners + if self.psa_type in ['collect', 'distribute']: + out = self.reduce(x) + n, c, h, w = out.size() + if self.shrink_factor != 1: + if h % self.shrink_factor and w % self.shrink_factor: + h = (h - 1) // self.shrink_factor + 1 + w = (w - 1) // self.shrink_factor + 1 + align_corners = True + else: + h = h // self.shrink_factor + w = w // self.shrink_factor + align_corners = False + out = resize( + out, + size=(h, w), + mode='bilinear', + align_corners=align_corners) + y = self.attention(out) + if self.compact: + if self.psa_type == 'collect': + y = y.view(n, h * w, + h * w).transpose(1, 2).view(n, h * w, h, w) + else: + y = self.psamask(y) + if self.psa_softmax: + y = F.softmax(y, dim=1) + out = torch.bmm( + out.view(n, c, h * w), y.view(n, h * w, h * w)).view( + n, c, h, w) * (1.0 / self.normalization_factor) + else: + x_col = self.reduce(x) + x_dis = self.reduce_p(x) + n, c, h, w = x_col.size() + if self.shrink_factor != 1: + if h % self.shrink_factor and w % self.shrink_factor: + h = (h - 1) // self.shrink_factor + 1 + w = (w - 1) // self.shrink_factor + 1 + align_corners = True + else: + h = h // self.shrink_factor + w = w // self.shrink_factor + align_corners = False + x_col = resize( + x_col, + size=(h, w), + mode='bilinear', + align_corners=align_corners) + x_dis = resize( + x_dis, + size=(h, w), + mode='bilinear', + align_corners=align_corners) + y_col = self.attention(x_col) + y_dis = self.attention_p(x_dis) + if self.compact: + y_dis = y_dis.view(n, h * w, + h * w).transpose(1, 2).view(n, h * w, h, w) + else: + y_col = self.psamask_collect(y_col) + y_dis = self.psamask_distribute(y_dis) + if self.psa_softmax: + y_col = F.softmax(y_col, dim=1) + y_dis = F.softmax(y_dis, dim=1) + x_col = torch.bmm( + x_col.view(n, c, h * w), y_col.view(n, h * w, h * w)).view( + n, c, h, w) * (1.0 / self.normalization_factor) + x_dis = torch.bmm( + x_dis.view(n, c, h * w), y_dis.view(n, h * w, h * w)).view( + n, c, h, w) * (1.0 / self.normalization_factor) + out = torch.cat([x_col, x_dis], 1) + out = self.proj(out) + out = resize( + out, + size=identity.shape[2:], + mode='bilinear', + align_corners=align_corners) + out = self.bottleneck(torch.cat((identity, out), dim=1)) + out = self.cls_seg(out) + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/psp_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/psp_head.py new file mode 100644 index 0000000..a40ec41 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/psp_head.py @@ -0,0 +1,117 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import resize +from .decode_head import BaseDecodeHead + + +class PPM(nn.ModuleList): + """Pooling Pyramid Module used in PSPNet. + + Args: + pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid + Module. + in_channels (int): Input channels. + channels (int): Channels after modules, before conv_seg. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict): Config of activation layers. + align_corners (bool): align_corners argument of F.interpolate. + """ + + def __init__(self, pool_scales, in_channels, channels, conv_cfg, norm_cfg, + act_cfg, align_corners, **kwargs): + super().__init__() + self.pool_scales = pool_scales + self.align_corners = align_corners + self.in_channels = in_channels + self.channels = channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + for pool_scale in pool_scales: + self.append( + nn.Sequential( + nn.AdaptiveAvgPool2d(pool_scale), + ConvModule( + self.in_channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + **kwargs))) + + def forward(self, x): + """Forward function.""" + ppm_outs = [] + for ppm in self: + ppm_out = ppm(x) + upsampled_ppm_out = resize( + ppm_out, + size=x.size()[2:], + mode='bilinear', + align_corners=self.align_corners) + ppm_outs.append(upsampled_ppm_out) + return ppm_outs + + +@MODELS.register_module() +class PSPHead(BaseDecodeHead): + """Pyramid Scene Parsing Network. + + This head is the implementation of + `PSPNet `_. + + Args: + pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid + Module. Default: (1, 2, 3, 6). + """ + + def __init__(self, pool_scales=(1, 2, 3, 6), **kwargs): + super().__init__(**kwargs) + assert isinstance(pool_scales, (list, tuple)) + self.pool_scales = pool_scales + self.psp_modules = PPM( + self.pool_scales, + self.in_channels, + self.channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + align_corners=self.align_corners) + self.bottleneck = ConvModule( + self.in_channels + len(pool_scales) * self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def _forward_feature(self, inputs): + """Forward function for feature maps before classifying each pixel with + ``self.cls_seg`` fc. + + Args: + inputs (list[Tensor]): List of multi-level img features. + + Returns: + feats (Tensor): A tensor of shape (batch_size, self.channels, + H, W) which is feature map for last layer of decoder head. + """ + x = self._transform_inputs(inputs) + psp_outs = [x] + psp_outs.extend(self.psp_modules(x)) + psp_outs = torch.cat(psp_outs, dim=1) + feats = self.bottleneck(psp_outs) + return feats + + def forward(self, inputs): + """Forward function.""" + output = self._forward_feature(inputs) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/san_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/san_head.py new file mode 100644 index 0000000..d20da80 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/san_head.py @@ -0,0 +1,736 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from functools import partial +from typing import Dict, List, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, build_norm_layer +from mmcv.cnn.bricks.transformer import BaseTransformerLayer +from mmcv.ops import point_sample +from mmengine.dist import all_reduce +from mmengine.model.weight_init import (caffe2_xavier_init, normal_init, + trunc_normal_) +from mmengine.runner.checkpoint import CheckpointLoader, load_state_dict +from mmengine.structures import InstanceData +from torch import Tensor +from torch.nn import functional as F + +from mmseg.models.backbones.vit import TransformerEncoderLayer +from mmseg.registry import MODELS +from mmseg.utils import (ConfigType, MatchMasks, SampleList, + seg_data_to_instance_data) +from ..utils import (MLP, LayerNorm2d, PatchEmbed, cross_attn_layer, + get_uncertain_point_coords_with_randomness, resize) +from .decode_head import BaseDecodeHead + + +class MLPMaskDecoder(nn.Module): + """Module for decoding query and visual features with MLP layers to + generate the attention biases and the mask proposals.""" + + def __init__( + self, + *, + in_channels: int, + total_heads: int = 1, + total_layers: int = 1, + embed_channels: int = 256, + mlp_channels: int = 256, + mlp_num_layers: int = 3, + rescale_attn_bias: bool = False, + ): + super().__init__() + self.total_heads = total_heads + self.total_layers = total_layers + + dense_affine_func = partial(nn.Conv2d, kernel_size=1) + # Query Branch + self.query_mlp = MLP(in_channels, mlp_channels, embed_channels, + mlp_num_layers) + # Pixel Branch + self.pix_mlp = MLP( + in_channels, + mlp_channels, + embed_channels, + mlp_num_layers, + affine_func=dense_affine_func, + ) + # Attention Bias Branch + self.attn_mlp = MLP( + in_channels, + mlp_channels, + embed_channels * self.total_heads * self.total_layers, + mlp_num_layers, + affine_func=dense_affine_func, + ) + if rescale_attn_bias: + self.bias_scaling = nn.Linear(1, 1) + else: + self.bias_scaling = nn.Identity() + + def forward(self, query: torch.Tensor, + x: torch.Tensor) -> Tuple[torch.Tensor, List[torch.Tensor]]: + """Forward function. + Args: + query (Tensor): Query Tokens [B,N,C]. + x (Tensor): Visual features [B,C,H,W] + + Return: + mask_preds (Tensor): Mask proposals. + attn_bias (List[Tensor]): List of attention bias. + """ + query = self.query_mlp(query) + pix = self.pix_mlp(x) + b, c, h, w = pix.shape + # preidict mask + mask_preds = torch.einsum('bqc,bchw->bqhw', query, pix) + # generate attn bias + attn = self.attn_mlp(x) + attn = attn.reshape(b, self.total_layers, self.total_heads, c, h, w) + attn_bias = torch.einsum('bqc,blnchw->blnqhw', query, attn) + attn_bias = self.bias_scaling(attn_bias[..., None]).squeeze(-1) + attn_bias = attn_bias.chunk(self.total_layers, dim=1) + attn_bias = [attn.squeeze(1) for attn in attn_bias] + return mask_preds, attn_bias + + +class SideAdapterNetwork(nn.Module): + """Side Adapter Network for predicting mask proposals and attention bias. + + Args: + in_channels (int): Number of input channels. Default: 3. + clip_channels (int): Number of channels of visual features. + Default: 768. + embed_dims (int): embedding dimension. Default: 240. + patch_size (int): The patch size. Default: 16. + patch_bias (bool): Whether use bias in patch embedding. + Default: True. + num_queries (int): Number of queries for mask proposals. + Default: 100. + fusion_index (List[int]): The layer number of the encode + transformer to fuse with the CLIP feature. + Default: [0, 1, 2, 3]. + cfg_encoder (ConfigType): Configs for the encode layers. + cfg_decoder (ConfigType): Configs for the decode layers. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + """ + + def __init__( + self, + in_channels: int = 3, + clip_channels: int = 768, + embed_dims: int = 240, + patch_size: int = 16, + patch_bias: bool = True, + num_queries: int = 100, + fusion_index: list = [0, 1, 2, 3], + cfg_encoder: ConfigType = ..., + cfg_decoder: ConfigType = ..., + norm_cfg: dict = dict(type='LN'), + ): + super().__init__() + + self.patch_embed = PatchEmbed( + in_channels=in_channels, + embed_dims=embed_dims, + conv_type='Conv2d', + kernel_size=patch_size, + stride=patch_size, + padding=0, + input_size=(640, 640), + bias=patch_bias, + norm_cfg=None, + init_cfg=None, + ) + ori_h, ori_w = self.patch_embed.init_out_size + num_patches = ori_h * ori_w + self.pos_embed = nn.Parameter( + torch.randn(1, num_patches, embed_dims) * .02) + self.query_pos_embed = nn.Parameter( + torch.zeros(1, num_queries, embed_dims)) + self.query_embed = nn.Parameter( + torch.zeros(1, num_queries, embed_dims)) + encode_layers = [] + for i in range(cfg_encoder.num_encode_layer): + encode_layers.append( + TransformerEncoderLayer( + embed_dims=embed_dims, + num_heads=cfg_encoder.num_heads, + feedforward_channels=cfg_encoder.mlp_ratio * embed_dims, + norm_cfg=norm_cfg)) + self.encode_layers = nn.ModuleList(encode_layers) + conv_clips = [] + for i in range(len(fusion_index)): + conv_clips.append( + nn.Sequential( + LayerNorm2d(clip_channels), + ConvModule( + clip_channels, + embed_dims, + kernel_size=1, + norm_cfg=None, + act_cfg=None))) + self.conv_clips = nn.ModuleList(conv_clips) + self.fusion_index = fusion_index + self.mask_decoder = MLPMaskDecoder( + in_channels=embed_dims, + total_heads=cfg_decoder.num_heads, + total_layers=cfg_decoder.num_layers, + embed_channels=cfg_decoder.embed_channels, + mlp_channels=cfg_decoder.mlp_channels, + mlp_num_layers=cfg_decoder.num_mlp, + rescale_attn_bias=cfg_decoder.rescale) + + def init_weights(self): + trunc_normal_(self.pos_embed, std=0.02) + nn.init.normal_(self.query_embed, std=0.02) + nn.init.normal_(self.query_pos_embed, std=0.02) + for i in range(len(self.conv_clips)): + caffe2_xavier_init(self.conv_clips[i][1].conv) + + def fuse_clip(self, fused_index: int, x: torch.Tensor, + clip_feature: torch.Tensor, hwshape: Tuple[int, + int], L: int): + """Fuse CLIP feature and visual tokens.""" + fused_clip = (resize( + self.conv_clips[fused_index](clip_feature.contiguous()), + size=hwshape, + mode='bilinear', + align_corners=False)).permute(0, 2, 3, 1).reshape(x[:, -L:, + ...].shape) + x = torch.cat([x[:, :-L, ...], x[:, -L:, ...] + fused_clip], dim=1) + return x + + def encode_feature(self, image: torch.Tensor, + clip_features: List[torch.Tensor], + deep_supervision_idxs: List[int]) -> List[List]: + """Encode images by a lightweight vision transformer.""" + assert len(self.fusion_index) == len(clip_features) + x, hwshape = self.patch_embed(image) + ori_h, ori_w = self.patch_embed.init_out_size + pos_embed = self.pos_embed + if self.pos_embed.shape[1] != x.shape[1]: + # resize the position embedding + pos_embed = ( + resize( + self.pos_embed.reshape(1, ori_h, ori_w, + -1).permute(0, 3, 1, 2), + size=hwshape, + mode='bicubic', + align_corners=False, + ).flatten(2).permute(0, 2, 1)) + pos_embed = torch.cat([ + self.query_pos_embed.expand(pos_embed.shape[0], -1, -1), pos_embed + ], + dim=1) + x = torch.cat([self.query_embed.expand(x.shape[0], -1, -1), x], dim=1) + x = x + pos_embed + L = hwshape[0] * hwshape[1] + fused_index = 0 + if self.fusion_index[fused_index] == 0: + x = self.fuse_clip(fused_index, x, clip_features[0][0], hwshape, L) + fused_index += 1 + outs = [] + for index, block in enumerate(self.encode_layers, start=1): + x = block(x) + if index < len(self.fusion_index + ) and index == self.fusion_index[fused_index]: + x = self.fuse_clip(fused_index, x, + clip_features[fused_index][0], hwshape, L) + fused_index += 1 + x_query = x[:, :-L, ...] + x_feat = x[:, -L:, ...].permute(0, 2, 1)\ + .reshape(x.shape[0], x.shape[-1], hwshape[0], hwshape[1]) + + if index in deep_supervision_idxs or index == len( + self.encode_layers): + outs.append({'query': x_query, 'x': x_feat}) + + if index < len(self.encode_layers): + x = x + pos_embed + return outs + + def decode_feature(self, features): + mask_embeds = [] + attn_biases = [] + for feature in features: + mask_embed, attn_bias = self.mask_decoder(**feature) + mask_embeds.append(mask_embed) + attn_biases.append(attn_bias) + return mask_embeds, attn_biases + + def forward( + self, image: torch.Tensor, clip_features: List[torch.Tensor], + deep_supervision_idxs: List[int] + ) -> Tuple[List[torch.Tensor], List[List[torch.Tensor]]]: + """Forward function.""" + features = self.encode_feature(image, clip_features, + deep_supervision_idxs) + mask_embeds, attn_biases = self.decode_feature(features) + return mask_embeds, attn_biases + + +class RecWithAttnbias(nn.Module): + """Mask recognition module by applying the attention biases to rest deeper + CLIP layers. + + Args: + sos_token_format (str): The format of sos token. It should be + chosen from ["cls_token", "learnable_token", "pos_embedding"]. + Default: 'cls_token'. + sos_token_num (int): Number of sos token. It should be equal to + the number of quries. Default: 100. + num_layers (int): Number of rest CLIP layers for mask recognition. + Default: 3. + cross_attn (bool): Whether use cross attention to update sos token. + Default: False. + embed_dims (int): The feature dimension of CLIP layers. + Default: 768. + num_heads (int): Parallel attention heads of CLIP layers. + Default: 768. + mlp_ratio (int): Ratio of mlp hidden dim to embedding dim. + Default: 4. + qkv_bias (bool): Whether to use bias in multihead-attention. + Default: True. + out_dims (int): Number of channels of the output mask proposals. + It should be equal to the out_dims of text_encoder. + Default: 512. + final_norm (True): Whether use norm layer for sos token. + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN'). + frozen_exclude (List): List of parameters that are not to be frozen. + """ + + def __init__(self, + sos_token_format: str = 'cls_token', + sos_token_num: int = 100, + num_layers: int = 3, + cross_attn: bool = False, + embed_dims: int = 768, + num_heads: int = 12, + mlp_ratio: int = 4, + num_fcs: int = 2, + qkv_bias: bool = True, + out_dims: int = 512, + final_norm: bool = True, + act_cfg: dict = dict(type='GELU'), + norm_cfg: dict = dict(type='LN'), + frozen_exclude: List = []): + super().__init__() + + assert sos_token_format in [ + 'cls_token', 'learnable_token', 'pos_embedding' + ] + self.sos_token_format = sos_token_format + self.sos_token_num = sos_token_num + self.frozen_exclude = frozen_exclude + self.cross_attn = cross_attn + self.num_layers = num_layers + self.num_heads = num_heads + if sos_token_format in ['learnable_token', 'pos_embedding']: + self.sos_token = nn.Parameter( + torch.randn(sos_token_num, 1, self.proj.shape[0])) + self.frozen.append('sos_token') + + layers = [] + for i in range(num_layers): + layers.append( + BaseTransformerLayer( + attn_cfgs=dict( + type='MultiheadAttention', + embed_dims=embed_dims, + num_heads=num_heads, + batch_first=False, + bias=qkv_bias), + ffn_cfgs=dict( + type='FFN', + embed_dims=embed_dims, + feedforward_channels=mlp_ratio * embed_dims, + act_cfg=act_cfg), + operation_order=('norm', 'self_attn', 'norm', 'ffn'))) + self.layers = nn.ModuleList(layers) + + self.ln_post = build_norm_layer(norm_cfg, embed_dims)[1] + self.proj = nn.Linear(embed_dims, out_dims, bias=False) + + self.final_norm = final_norm + self._freeze() + + def init_weights(self, rec_state_dict): + if hasattr(self, 'sos_token'): + normal_init(self.sos_token, std=0.02) + if rec_state_dict is not None: + load_state_dict(self, rec_state_dict, strict=False, logger=None) + else: + super().init_weights() + + def _freeze(self): + if 'all' in self.frozen_exclude: + return + for name, param in self.named_parameters(): + if not any([exclude in name for exclude in self.frozen_exclude]): + param.requires_grad = False + + def _build_attn_biases(self, attn_biases, target_shape): + formatted_attn_biases = [] + for attn_bias in attn_biases: + # convert it to proper format: N*num_head,L,L + # attn_bias: [N, num_head/1, num_sos,H,W] + n, num_head, num_sos, h, w = attn_bias.shape + # reshape and downsample + attn_bias = F.adaptive_max_pool2d( + attn_bias.reshape(n, num_head * num_sos, h, w), + output_size=target_shape) + attn_bias = attn_bias.reshape(n, num_head, num_sos, *target_shape) + + true_num_head = self.num_heads + assert (num_head == 1 or num_head + == true_num_head), f'num_head={num_head} is not supported.' + if num_head == 1: + attn_bias = attn_bias.repeat(1, true_num_head, 1, 1, 1) + attn_bias = attn_bias.reshape(n * true_num_head, num_sos, -1) + L = attn_bias.shape[-1] + if self.cross_attn: + # [n*num_head, num_sos, L] + formatted_attn_biases.append(attn_bias) + else: + # [n*num_head, num_sos+1+L, num_sos+1+L] + new_attn_bias = attn_bias.new_zeros(num_sos + 1 + L, + num_sos + 1 + L) + new_attn_bias[:, :num_sos] = -100 + new_attn_bias[torch.arange(num_sos), torch.arange(num_sos)] = 0 + new_attn_bias[:num_sos, num_sos] = -100 + new_attn_bias = ( + new_attn_bias[None, ...].expand(n * true_num_head, -1, + -1).clone()) + new_attn_bias[..., :num_sos, -L:] = attn_bias + formatted_attn_biases.append(new_attn_bias) + + if len(formatted_attn_biases) == 1: + formatted_attn_biases = [ + formatted_attn_biases[0] for _ in range(self.num_layers) + ] + return formatted_attn_biases + + def forward(self, bias: List[Tensor], feature: List[Tensor]): + """Forward function to recognize the category of masks + Args: + bias (List[Tensor]): Attention bias for transformer layers + feature (List[Tensor]): Output of the image encoder, + including cls_token and img_feature. + """ + cls_token = feature[1].unsqueeze(0) + img_feature = feature[0] + b, c, h, w = img_feature.shape + # construct clip shadow features + x = torch.cat( + [cls_token, + img_feature.reshape(b, c, -1).permute(2, 0, 1)]) + + # construct sos token + if self.sos_token_format == 'cls_token': + sos_token = cls_token.repeat(self.sos_token_num, 1, 1) + elif self.sos_token_format == 'learnable_token': + sos_token = self.sos_token.expand(-1, b, -1) + elif self.sos_token_format == 'pos_embedding': + sos_token = self.sos_token.expand(-1, b, -1) + cls_token + + # construct attn bias + attn_biases = self._build_attn_biases(bias, target_shape=(h, w)) + + if self.cross_attn: + for i, block in enumerate(self.layers): + if self.cross_attn: + sos_token = cross_attn_layer( + block, + sos_token, + x[1:, ], + attn_biases[i], + ) + if i < len(self.layers) - 1: + x = block(x) + else: + x = torch.cat([sos_token, x], dim=0) + for i, block in enumerate(self.layers): + x = block(x, attn_masks=[attn_biases[i]]) + sos_token = x[:self.sos_token_num] + + sos_token = sos_token.permute(1, 0, 2) # LND -> NLD + sos_token = self.ln_post(sos_token) + sos_token = self.proj(sos_token) + if self.final_norm: + sos_token = F.normalize(sos_token, dim=-1) + return sos_token + + +@MODELS.register_module() +class SideAdapterCLIPHead(BaseDecodeHead): + """Side Adapter Network (SAN) for open-vocabulary semantic segmentation + with pre-trained vision-language model. + + This decode head is the implementation of `Side Adapter Network + for Open-Vocabulary Semantic Segmentation` + . + Modified from https://github.com/MendelXu/SAN/blob/main/san/model/side_adapter/side_adapter.py # noqa:E501 + Copyright (c) 2023 MendelXu. + Licensed under the MIT License + + Args: + num_classes (int): the number of classes. + san_cfg (ConfigType): Configs for SideAdapterNetwork module + maskgen_cfg (ConfigType): Configs for RecWithAttnbias module + """ + + def __init__(self, num_classes: int, san_cfg: ConfigType, + maskgen_cfg: ConfigType, deep_supervision_idxs: List[int], + train_cfg: ConfigType, **kwargs): + super().__init__( + in_channels=san_cfg.in_channels, + channels=san_cfg.embed_dims, + num_classes=num_classes, + **kwargs) + assert san_cfg.num_queries == maskgen_cfg.sos_token_num, \ + 'num_queries in san_cfg should be equal to sos_token_num ' \ + 'in maskgen_cfg' + del self.conv_seg + self.side_adapter_network = SideAdapterNetwork(**san_cfg) + self.rec_with_attnbias = RecWithAttnbias(**maskgen_cfg) + self.deep_supervision_idxs = deep_supervision_idxs + self.train_cfg = train_cfg + if train_cfg: + self.match_masks = MatchMasks( + num_points=train_cfg.num_points, + num_queries=san_cfg.num_queries, + num_classes=num_classes, + assigner=train_cfg.assigner) + + def init_weights(self): + + rec_state_dict = None + if isinstance(self.init_cfg, dict) and \ + self.init_cfg.get('type') == 'Pretrained_Part': + checkpoint = CheckpointLoader.load_checkpoint( + self.init_cfg['checkpoint'], logger=None, map_location='cpu') + + rec_state_dict = checkpoint.copy() + para_prefix = 'decode_head.rec_with_attnbias' + prefix_len = len(para_prefix) + 1 + for k, v in checkpoint.items(): + rec_state_dict.pop(k) + if para_prefix in k: + rec_state_dict[k[prefix_len:]] = v + + self.side_adapter_network.init_weights() + self.rec_with_attnbias.init_weights(rec_state_dict) + + def forward(self, inputs: Tuple[Tensor], + deep_supervision_idxs) -> Tuple[List]: + """Forward function. + + Args: + inputs (Tuple[Tensor]): A triplet including images, + list of multi-level visual features from image encoder and + class embeddings from text_encoder. + + Returns: + mask_props (List[Tensor]): Mask proposals predicted by SAN. + mask_logits (List[Tensor]): Class logits of mask proposals. + """ + imgs, clip_feature, class_embeds = inputs + # predict mask proposals and attention bias + mask_props, attn_biases = self.side_adapter_network( + imgs, clip_feature, deep_supervision_idxs) + + # mask recognition with attention bias + mask_embeds = [ + self.rec_with_attnbias(att_bias, clip_feature[-1]) + for att_bias in attn_biases + ] + # Obtain class prediction of masks by comparing the similarity + # between the image token and the text embedding of class names. + mask_logits = [ + torch.einsum('bqc,nc->bqn', mask_embed, class_embeds) + for mask_embed in mask_embeds + ] + return mask_props, mask_logits + + def predict(self, inputs: Tuple[Tensor], batch_img_metas: List[dict], + test_cfg: ConfigType) -> Tensor: + """Forward function for prediction. + + Args: + inputs (Tuple[Tensor]): Images, visual features from image encoder + and class embedding from text encoder. + batch_img_metas (dict): List Image info where each dict may also + contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', and 'pad_shape'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + test_cfg (dict): The testing config. + + Returns: + Tensor: Outputs segmentation logits map. + """ + mask_props, mask_logits = self.forward(inputs, []) + + return self.predict_by_feat([mask_props[-1], mask_logits[-1]], + batch_img_metas) + + def predict_by_feat(self, seg_logits: List[Tensor], + batch_img_metas: List[dict]) -> Tensor: + """1. Transform a batch of mask proposals to the input shape. + 2. Generate segmentation map with mask proposals and class logits. + """ + mask_pred = seg_logits[0] + cls_score = seg_logits[1] + if isinstance(batch_img_metas[0]['img_shape'], torch.Size): + # slide inference + size = batch_img_metas[0]['img_shape'] + elif 'pad_shape' in batch_img_metas[0]: + size = batch_img_metas[0]['pad_shape'][:2] + else: + size = batch_img_metas[0]['img_shape'] + # upsample mask + mask_pred = F.interpolate( + mask_pred, size=size, mode='bilinear', align_corners=False) + + mask_cls = F.softmax(cls_score, dim=-1)[..., :-1] + mask_pred = mask_pred.sigmoid() + seg_logits = torch.einsum('bqc,bqhw->bchw', mask_cls, mask_pred) + return seg_logits + + def loss(self, x: Tuple[Tensor], batch_data_samples: SampleList, + train_cfg: ConfigType) -> dict: + """Perform forward propagation and loss calculation of the decoder head + on the features of the upstream network. + + Args: + x (tuple[Tensor]): Multi-level features from the upstream + network, each is a 4D-tensor. + batch_data_samples (List[:obj:`SegDataSample`]): The Data + Samples. It usually includes information such as + `gt_sem_seg`. + train_cfg (ConfigType): Training config. + + Returns: + dict[str, Tensor]: a dictionary of loss components. + """ + # batch SegDataSample to InstanceDataSample + batch_gt_instances = seg_data_to_instance_data(self.ignore_index, + batch_data_samples) + + # forward + all_mask_props, all_mask_logits = self.forward( + x, self.deep_supervision_idxs) + + # loss + losses = self.loss_by_feat(all_mask_logits, all_mask_props, + batch_gt_instances) + + return losses + + def loss_by_feat( + self, all_cls_scores: Tensor, all_mask_preds: Tensor, + batch_gt_instances: List[InstanceData]) -> Dict[str, Tensor]: + """Loss function. + + Args: + all_cls_scores (Tensor): Classification scores for all decoder + layers with shape (num_decoder, batch_size, num_queries, + cls_out_channels). Note `cls_out_channels` should includes + background. + all_mask_preds (Tensor): Mask scores for all decoder layers with + shape (num_decoder, batch_size, num_queries, h, w). + batch_gt_instances (list[obj:`InstanceData`]): each contains + ``labels`` and ``masks``. + + Returns: + dict[str, Tensor]: A dictionary of loss components. + """ + num_dec_layers = len(all_cls_scores) + batch_gt_instances_list = [ + batch_gt_instances for _ in range(num_dec_layers) + ] + + losses = [] + for i in range(num_dec_layers): + cls_scores = all_cls_scores[i] + mask_preds = all_mask_preds[i] + # matching N mask predictions to K category labels + (labels, mask_targets, mask_weights, + avg_factor) = self.match_masks.get_targets( + cls_scores, mask_preds, batch_gt_instances_list[i]) + cls_scores = cls_scores.flatten(0, 1) + labels = labels.flatten(0, 1) + num_total_masks = cls_scores.new_tensor([avg_factor], + dtype=torch.float) + all_reduce(num_total_masks, op='mean') + num_total_masks = max(num_total_masks, 1) + + # extract positive ones + # shape (batch_size, num_queries, h, w) -> (num_total_gts, h, w) + mask_preds = mask_preds[mask_weights > 0] + + if mask_targets.shape[0] != 0: + with torch.no_grad(): + points_coords = get_uncertain_point_coords_with_randomness( + mask_preds.unsqueeze(1), None, + self.train_cfg.num_points, + self.train_cfg.oversample_ratio, + self.train_cfg.importance_sample_ratio) + # shape (num_total_gts, h, w) + # -> (num_total_gts, num_points) + mask_point_targets = point_sample( + mask_targets.unsqueeze(1).float(), + points_coords).squeeze(1) + # shape (num_queries, h, w) -> (num_queries, num_points) + mask_point_preds = point_sample( + mask_preds.unsqueeze(1), points_coords).squeeze(1) + + if not isinstance(self.loss_decode, nn.ModuleList): + losses_decode = [self.loss_decode] + else: + losses_decode = self.loss_decode + loss = dict() + for loss_decode in losses_decode: + if 'loss_cls' in loss_decode.loss_name: + if loss_decode.loss_name == 'loss_cls_ce': + loss[loss_decode.loss_name] = loss_decode( + cls_scores, labels) + else: + assert False, "Only support 'CrossEntropyLoss' in" \ + ' classification loss' + + elif 'loss_mask' in loss_decode.loss_name: + if mask_targets.shape[0] == 0: + loss[loss_decode.loss_name] = mask_preds.sum() + elif loss_decode.loss_name == 'loss_mask_ce': + loss[loss_decode.loss_name] = loss_decode( + mask_point_preds, + mask_point_targets, + avg_factor=num_total_masks * + self.train_cfg.num_points) + elif loss_decode.loss_name == 'loss_mask_dice': + loss[loss_decode.loss_name] = loss_decode( + mask_point_preds, + mask_point_targets, + avg_factor=num_total_masks) + else: + assert False, "Only support 'CrossEntropyLoss' and" \ + " 'DiceLoss' in mask loss" + else: + assert False, "Only support for 'loss_cls' and 'loss_mask'" + + losses.append(loss) + + loss_dict = dict() + # loss from the last decoder layer + loss_dict.update(losses[-1]) + # loss from other decoder layers + for i, loss in enumerate(losses[:-1]): + for k, v in loss.items(): + loss_dict[f'd{self.deep_supervision_idxs[i]}.{k}'] = v + return loss_dict diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/segformer_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/segformer_head.py new file mode 100644 index 0000000..f9eb0b3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/segformer_head.py @@ -0,0 +1,66 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.registry import MODELS +from ..utils import resize + + +@MODELS.register_module() +class SegformerHead(BaseDecodeHead): + """The all mlp Head of segformer. + + This head is the implementation of + `Segformer ` _. + + Args: + interpolate_mode: The interpolate mode of MLP head upsample operation. + Default: 'bilinear'. + """ + + def __init__(self, interpolate_mode='bilinear', **kwargs): + super().__init__(input_transform='multiple_select', **kwargs) + + self.interpolate_mode = interpolate_mode + num_inputs = len(self.in_channels) + + assert num_inputs == len(self.in_index) + + self.convs = nn.ModuleList() + for i in range(num_inputs): + self.convs.append( + ConvModule( + in_channels=self.in_channels[i], + out_channels=self.channels, + kernel_size=1, + stride=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + + self.fusion_conv = ConvModule( + in_channels=self.channels * num_inputs, + out_channels=self.channels, + kernel_size=1, + norm_cfg=self.norm_cfg) + + def forward(self, inputs): + # Receive 4 stage backbone feature map: 1/4, 1/8, 1/16, 1/32 + inputs = self._transform_inputs(inputs) + outs = [] + for idx in range(len(inputs)): + x = inputs[idx] + conv = self.convs[idx] + outs.append( + resize( + input=conv(x), + size=inputs[0].shape[2:], + mode=self.interpolate_mode, + align_corners=self.align_corners)) + + out = self.fusion_conv(torch.cat(outs, dim=1)) + + out = self.cls_seg(out) + + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/segmenter_mask_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/segmenter_mask_head.py new file mode 100644 index 0000000..85d2773 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/segmenter_mask_head.py @@ -0,0 +1,132 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_norm_layer +from mmengine.model import ModuleList +from mmengine.model.weight_init import (constant_init, trunc_normal_, + trunc_normal_init) + +from mmseg.models.backbones.vit import TransformerEncoderLayer +from mmseg.registry import MODELS +from .decode_head import BaseDecodeHead + + +@MODELS.register_module() +class SegmenterMaskTransformerHead(BaseDecodeHead): + """Segmenter: Transformer for Semantic Segmentation. + + This head is the implementation of + `Segmenter: `_. + + Args: + backbone_cfg:(dict): Config of backbone of + Context Path. + in_channels (int): The number of channels of input image. + num_layers (int): The depth of transformer. + num_heads (int): The number of attention heads. + embed_dims (int): The number of embedding dimension. + mlp_ratio (int): ratio of mlp hidden dim to embedding dim. + Default: 4. + drop_path_rate (float): stochastic depth rate. Default 0.1. + drop_rate (float): Probability of an element to be zeroed. + Default 0.0 + attn_drop_rate (float): The drop out rate for attention layer. + Default 0.0 + num_fcs (int): The number of fully-connected layers for FFNs. + Default: 2. + qkv_bias (bool): Enable bias for qkv if True. Default: True. + act_cfg (dict): The activation config for FFNs. + Default: dict(type='GELU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN') + init_std (float): The value of std in weight initialization. + Default: 0.02. + """ + + def __init__( + self, + in_channels, + num_layers, + num_heads, + embed_dims, + mlp_ratio=4, + drop_path_rate=0.1, + drop_rate=0.0, + attn_drop_rate=0.0, + num_fcs=2, + qkv_bias=True, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + init_std=0.02, + **kwargs, + ): + super().__init__(in_channels=in_channels, **kwargs) + + dpr = [x.item() for x in torch.linspace(0, drop_path_rate, num_layers)] + self.layers = ModuleList() + for i in range(num_layers): + self.layers.append( + TransformerEncoderLayer( + embed_dims=embed_dims, + num_heads=num_heads, + feedforward_channels=mlp_ratio * embed_dims, + attn_drop_rate=attn_drop_rate, + drop_rate=drop_rate, + drop_path_rate=dpr[i], + num_fcs=num_fcs, + qkv_bias=qkv_bias, + act_cfg=act_cfg, + norm_cfg=norm_cfg, + batch_first=True, + )) + + self.dec_proj = nn.Linear(in_channels, embed_dims) + + self.cls_emb = nn.Parameter( + torch.randn(1, self.num_classes, embed_dims)) + self.patch_proj = nn.Linear(embed_dims, embed_dims, bias=False) + self.classes_proj = nn.Linear(embed_dims, embed_dims, bias=False) + + self.decoder_norm = build_norm_layer( + norm_cfg, embed_dims, postfix=1)[1] + self.mask_norm = build_norm_layer( + norm_cfg, self.num_classes, postfix=2)[1] + + self.init_std = init_std + + delattr(self, 'conv_seg') + + def init_weights(self): + trunc_normal_(self.cls_emb, std=self.init_std) + trunc_normal_init(self.patch_proj, std=self.init_std) + trunc_normal_init(self.classes_proj, std=self.init_std) + for n, m in self.named_modules(): + if isinstance(m, nn.Linear): + trunc_normal_init(m, std=self.init_std, bias=0) + elif isinstance(m, nn.LayerNorm): + constant_init(m, val=1.0, bias=0.0) + + def forward(self, inputs): + x = self._transform_inputs(inputs) + b, c, h, w = x.shape + x = x.permute(0, 2, 3, 1).contiguous().view(b, -1, c) + + x = self.dec_proj(x) + cls_emb = self.cls_emb.expand(x.size(0), -1, -1) + x = torch.cat((x, cls_emb), 1) + for layer in self.layers: + x = layer(x) + x = self.decoder_norm(x) + + patches = self.patch_proj(x[:, :-self.num_classes]) + cls_seg_feat = self.classes_proj(x[:, -self.num_classes:]) + + patches = F.normalize(patches, dim=2, p=2) + cls_seg_feat = F.normalize(cls_seg_feat, dim=2, p=2) + + masks = patches @ cls_seg_feat.transpose(1, 2) + masks = self.mask_norm(masks) + masks = masks.permute(0, 2, 1).contiguous().view(b, -1, h, w) + + return masks diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/sep_aspp_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/sep_aspp_head.py new file mode 100644 index 0000000..9dba68c --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/sep_aspp_head.py @@ -0,0 +1,102 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule + +from mmseg.registry import MODELS +from ..utils import resize +from .aspp_head import ASPPHead, ASPPModule + + +class DepthwiseSeparableASPPModule(ASPPModule): + """Atrous Spatial Pyramid Pooling (ASPP) Module with depthwise separable + conv.""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + for i, dilation in enumerate(self.dilations): + if dilation > 1: + self[i] = DepthwiseSeparableConvModule( + self.in_channels, + self.channels, + 3, + dilation=dilation, + padding=dilation, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + +@MODELS.register_module() +class DepthwiseSeparableASPPHead(ASPPHead): + """Encoder-Decoder with Atrous Separable Convolution for Semantic Image + Segmentation. + + This head is the implementation of `DeepLabV3+ + `_. + + Args: + c1_in_channels (int): The input channels of c1 decoder. If is 0, + the no decoder will be used. + c1_channels (int): The intermediate channels of c1 decoder. + """ + + def __init__(self, c1_in_channels, c1_channels, **kwargs): + super().__init__(**kwargs) + assert c1_in_channels >= 0 + self.aspp_modules = DepthwiseSeparableASPPModule( + dilations=self.dilations, + in_channels=self.in_channels, + channels=self.channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + if c1_in_channels > 0: + self.c1_bottleneck = ConvModule( + c1_in_channels, + c1_channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + else: + self.c1_bottleneck = None + self.sep_bottleneck = nn.Sequential( + DepthwiseSeparableConvModule( + self.channels + c1_channels, + self.channels, + 3, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + DepthwiseSeparableConvModule( + self.channels, + self.channels, + 3, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg)) + + def forward(self, inputs): + """Forward function.""" + x = self._transform_inputs(inputs) + aspp_outs = [ + resize( + self.image_pool(x), + size=x.size()[2:], + mode='bilinear', + align_corners=self.align_corners) + ] + aspp_outs.extend(self.aspp_modules(x)) + aspp_outs = torch.cat(aspp_outs, dim=1) + output = self.bottleneck(aspp_outs) + if self.c1_bottleneck is not None: + c1_output = self.c1_bottleneck(inputs[0]) + output = resize( + input=output, + size=c1_output.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + output = torch.cat([output, c1_output], dim=1) + output = self.sep_bottleneck(output) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/sep_fcn_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/sep_fcn_head.py new file mode 100644 index 0000000..3b15983 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/sep_fcn_head.py @@ -0,0 +1,60 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.cnn import DepthwiseSeparableConvModule + +from mmseg.registry import MODELS +from .fcn_head import FCNHead + + +@MODELS.register_module() +class DepthwiseSeparableFCNHead(FCNHead): + """Depthwise-Separable Fully Convolutional Network for Semantic + Segmentation. + + This head is implemented according to `Fast-SCNN: Fast Semantic + Segmentation Network `_. + + Args: + in_channels(int): Number of output channels of FFM. + channels(int): Number of middle-stage channels in the decode head. + concat_input(bool): Whether to concatenate original decode input into + the result of several consecutive convolution layers. + Default: True. + num_classes(int): Used to determine the dimension of + final prediction tensor. + in_index(int): Correspond with 'out_indices' in FastSCNN backbone. + norm_cfg (dict | None): Config of norm layers. + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + loss_decode(dict): Config of loss type and some + relevant additional options. + dw_act_cfg (dict):Activation config of depthwise ConvModule. If it is + 'default', it will be the same as `act_cfg`. Default: None. + """ + + def __init__(self, dw_act_cfg=None, **kwargs): + super().__init__(**kwargs) + self.convs[0] = DepthwiseSeparableConvModule( + self.in_channels, + self.channels, + kernel_size=self.kernel_size, + padding=self.kernel_size // 2, + norm_cfg=self.norm_cfg, + dw_act_cfg=dw_act_cfg) + + for i in range(1, self.num_convs): + self.convs[i] = DepthwiseSeparableConvModule( + self.channels, + self.channels, + kernel_size=self.kernel_size, + padding=self.kernel_size // 2, + norm_cfg=self.norm_cfg, + dw_act_cfg=dw_act_cfg) + + if self.concat_input: + self.conv_cat = DepthwiseSeparableConvModule( + self.in_channels + self.channels, + self.channels, + kernel_size=self.kernel_size, + padding=self.kernel_size // 2, + norm_cfg=self.norm_cfg, + dw_act_cfg=dw_act_cfg) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/setr_mla_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/setr_mla_head.py new file mode 100644 index 0000000..1975991 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/setr_mla_head.py @@ -0,0 +1,62 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import Upsample +from .decode_head import BaseDecodeHead + + +@MODELS.register_module() +class SETRMLAHead(BaseDecodeHead): + """Multi level feature aggretation head of SETR. + + MLA head of `SETR `_. + + Args: + mlahead_channels (int): Channels of conv-conv-4x of multi-level feature + aggregation. Default: 128. + up_scale (int): The scale factor of interpolate. Default:4. + """ + + def __init__(self, mla_channels=128, up_scale=4, **kwargs): + super().__init__(input_transform='multiple_select', **kwargs) + self.mla_channels = mla_channels + + num_inputs = len(self.in_channels) + + # Refer to self.cls_seg settings of BaseDecodeHead + assert self.channels == num_inputs * mla_channels + + self.up_convs = nn.ModuleList() + for i in range(num_inputs): + self.up_convs.append( + nn.Sequential( + ConvModule( + in_channels=self.in_channels[i], + out_channels=mla_channels, + kernel_size=3, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + ConvModule( + in_channels=mla_channels, + out_channels=mla_channels, + kernel_size=3, + padding=1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + Upsample( + scale_factor=up_scale, + mode='bilinear', + align_corners=self.align_corners))) + + def forward(self, inputs): + inputs = self._transform_inputs(inputs) + outs = [] + for x, up_conv in zip(inputs, self.up_convs): + outs.append(up_conv(x)) + out = torch.cat(outs, dim=1) + out = self.cls_seg(out) + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/setr_up_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/setr_up_head.py new file mode 100644 index 0000000..9c796d8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/setr_up_head.py @@ -0,0 +1,81 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import ConvModule, build_norm_layer + +from mmseg.registry import MODELS +from ..utils import Upsample +from .decode_head import BaseDecodeHead + + +@MODELS.register_module() +class SETRUPHead(BaseDecodeHead): + """Naive upsampling head and Progressive upsampling head of SETR. + + Naive or PUP head of `SETR `_. + + Args: + norm_layer (dict): Config dict for input normalization. + Default: norm_layer=dict(type='LN', eps=1e-6, requires_grad=True). + num_convs (int): Number of decoder convolutions. Default: 1. + up_scale (int): The scale factor of interpolate. Default:4. + kernel_size (int): The kernel size of convolution when decoding + feature information from backbone. Default: 3. + init_cfg (dict | list[dict] | None): Initialization config dict. + Default: dict( + type='Constant', val=1.0, bias=0, layer='LayerNorm'). + """ + + def __init__(self, + norm_layer=dict(type='LN', eps=1e-6, requires_grad=True), + num_convs=1, + up_scale=4, + kernel_size=3, + init_cfg=[ + dict(type='Constant', val=1.0, bias=0, layer='LayerNorm'), + dict( + type='Normal', + std=0.01, + override=dict(name='conv_seg')) + ], + **kwargs): + + assert kernel_size in [1, 3], 'kernel_size must be 1 or 3.' + + super().__init__(init_cfg=init_cfg, **kwargs) + + assert isinstance(self.in_channels, int) + + _, self.norm = build_norm_layer(norm_layer, self.in_channels) + + self.up_convs = nn.ModuleList() + in_channels = self.in_channels + out_channels = self.channels + for _ in range(num_convs): + self.up_convs.append( + nn.Sequential( + ConvModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=1, + padding=int(kernel_size - 1) // 2, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + Upsample( + scale_factor=up_scale, + mode='bilinear', + align_corners=self.align_corners))) + in_channels = out_channels + + def forward(self, x): + x = self._transform_inputs(x) + + n, c, h, w = x.shape + x = x.reshape(n, c, h * w).transpose(2, 1).contiguous() + x = self.norm(x) + x = x.transpose(1, 2).reshape(n, c, h, w).contiguous() + + for up_conv in self.up_convs: + x = up_conv(x) + out = self.cls_seg(x) + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/stdc_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/stdc_head.py new file mode 100644 index 0000000..1c1c21e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/stdc_head.py @@ -0,0 +1,97 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn.functional as F +from mmengine.structures import PixelData +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample +from mmseg.utils import SampleList +from .fcn_head import FCNHead + + +@MODELS.register_module() +class STDCHead(FCNHead): + """This head is the implementation of `Rethinking BiSeNet For Real-time + Semantic Segmentation `_. + + Args: + boundary_threshold (float): The threshold of calculating boundary. + Default: 0.1. + """ + + def __init__(self, boundary_threshold=0.1, **kwargs): + super().__init__(**kwargs) + self.boundary_threshold = boundary_threshold + # Using register buffer to make laplacian kernel on the same + # device of `seg_label`. + self.register_buffer( + 'laplacian_kernel', + torch.tensor([-1, -1, -1, -1, 8, -1, -1, -1, -1], + dtype=torch.float32, + requires_grad=False).reshape((1, 1, 3, 3))) + self.fusion_kernel = torch.nn.Parameter( + torch.tensor([[6. / 10], [3. / 10], [1. / 10]], + dtype=torch.float32).reshape(1, 3, 1, 1), + requires_grad=False) + + def loss_by_feat(self, seg_logits: Tensor, + batch_data_samples: SampleList) -> dict: + """Compute Detail Aggregation Loss.""" + # Note: The paper claims `fusion_kernel` is a trainable 1x1 conv + # parameters. However, it is a constant in original repo and other + # codebase because it would not be added into computation graph + # after threshold operation. + seg_label = self._stack_batch_gt(batch_data_samples).to( + self.laplacian_kernel) + boundary_targets = F.conv2d( + seg_label, self.laplacian_kernel, padding=1) + boundary_targets = boundary_targets.clamp(min=0) + boundary_targets[boundary_targets > self.boundary_threshold] = 1 + boundary_targets[boundary_targets <= self.boundary_threshold] = 0 + + boundary_targets_x2 = F.conv2d( + seg_label, self.laplacian_kernel, stride=2, padding=1) + boundary_targets_x2 = boundary_targets_x2.clamp(min=0) + + boundary_targets_x4 = F.conv2d( + seg_label, self.laplacian_kernel, stride=4, padding=1) + boundary_targets_x4 = boundary_targets_x4.clamp(min=0) + + boundary_targets_x4_up = F.interpolate( + boundary_targets_x4, boundary_targets.shape[2:], mode='nearest') + boundary_targets_x2_up = F.interpolate( + boundary_targets_x2, boundary_targets.shape[2:], mode='nearest') + + boundary_targets_x2_up[ + boundary_targets_x2_up > self.boundary_threshold] = 1 + boundary_targets_x2_up[ + boundary_targets_x2_up <= self.boundary_threshold] = 0 + + boundary_targets_x4_up[ + boundary_targets_x4_up > self.boundary_threshold] = 1 + boundary_targets_x4_up[ + boundary_targets_x4_up <= self.boundary_threshold] = 0 + + boundary_targets_pyramids = torch.stack( + (boundary_targets, boundary_targets_x2_up, boundary_targets_x4_up), + dim=1) + + boundary_targets_pyramids = boundary_targets_pyramids.squeeze(2) + boudary_targets_pyramid = F.conv2d(boundary_targets_pyramids, + self.fusion_kernel) + + boudary_targets_pyramid[ + boudary_targets_pyramid > self.boundary_threshold] = 1 + boudary_targets_pyramid[ + boudary_targets_pyramid <= self.boundary_threshold] = 0 + + seg_labels = boudary_targets_pyramid.long() + batch_sample_list = [] + for label in seg_labels: + seg_data_sample = SegDataSample() + seg_data_sample.gt_sem_seg = PixelData(data=label) + batch_sample_list.append(seg_data_sample) + + loss = super().loss_by_feat(seg_logits, batch_sample_list) + return loss diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/uper_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/uper_head.py new file mode 100644 index 0000000..b1ccc31 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/uper_head.py @@ -0,0 +1,139 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.registry import MODELS +from ..utils import resize +from .decode_head import BaseDecodeHead +from .psp_head import PPM + + +@MODELS.register_module() +class UPerHead(BaseDecodeHead): + """Unified Perceptual Parsing for Scene Understanding. + + This head is the implementation of `UPerNet + `_. + + Args: + pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid + Module applied on the last feature. Default: (1, 2, 3, 6). + """ + + def __init__(self, pool_scales=(1, 2, 3, 6), **kwargs): + super().__init__(input_transform='multiple_select', **kwargs) + # PSP Module + self.psp_modules = PPM( + pool_scales, + self.in_channels[-1], + self.channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + align_corners=self.align_corners) + self.bottleneck = ConvModule( + self.in_channels[-1] + len(pool_scales) * self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + # FPN Module + self.lateral_convs = nn.ModuleList() + self.fpn_convs = nn.ModuleList() + for in_channels in self.in_channels[:-1]: # skip the top layer + l_conv = ConvModule( + in_channels, + self.channels, + 1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + inplace=False) + fpn_conv = ConvModule( + self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + inplace=False) + self.lateral_convs.append(l_conv) + self.fpn_convs.append(fpn_conv) + + self.fpn_bottleneck = ConvModule( + len(self.in_channels) * self.channels, + self.channels, + 3, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + def psp_forward(self, inputs): + """Forward function of PSP module.""" + x = inputs[-1] + psp_outs = [x] + psp_outs.extend(self.psp_modules(x)) + psp_outs = torch.cat(psp_outs, dim=1) + output = self.bottleneck(psp_outs) + + return output + + def _forward_feature(self, inputs): + """Forward function for feature maps before classifying each pixel with + ``self.cls_seg`` fc. + + Args: + inputs (list[Tensor]): List of multi-level img features. + + Returns: + feats (Tensor): A tensor of shape (batch_size, self.channels, + H, W) which is feature map for last layer of decoder head. + """ + inputs = self._transform_inputs(inputs) + + # build laterals + laterals = [ + lateral_conv(inputs[i]) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + + laterals.append(self.psp_forward(inputs)) + + # build top-down path + used_backbone_levels = len(laterals) + for i in range(used_backbone_levels - 1, 0, -1): + prev_shape = laterals[i - 1].shape[2:] + laterals[i - 1] = laterals[i - 1] + resize( + laterals[i], + size=prev_shape, + mode='bilinear', + align_corners=self.align_corners) + + # build outputs + fpn_outs = [ + self.fpn_convs[i](laterals[i]) + for i in range(used_backbone_levels - 1) + ] + # append psp feature + fpn_outs.append(laterals[-1]) + + for i in range(used_backbone_levels - 1, 0, -1): + fpn_outs[i] = resize( + fpn_outs[i], + size=fpn_outs[0].shape[2:], + mode='bilinear', + align_corners=self.align_corners) + fpn_outs = torch.cat(fpn_outs, dim=1) + feats = self.fpn_bottleneck(fpn_outs) + return feats + + def forward(self, inputs): + """Forward function.""" + output = self._forward_feature(inputs) + output = self.cls_seg(output) + return output diff --git a/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/vpd_depth_head.py b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/vpd_depth_head.py new file mode 100644 index 0000000..65bdfbd --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/decode_heads/vpd_depth_head.py @@ -0,0 +1,253 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional, Sequence, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_conv_layer, build_norm_layer, build_upsample_layer +from mmengine.model import BaseModule +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.utils import SampleList +from ..utils import resize +from .decode_head import BaseDecodeHead + + +class VPDDepthDecoder(BaseModule): + """VPD Depth Decoder class. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + num_deconv_layers (int): Number of deconvolution layers. + num_deconv_filters (List[int]): List of output channels for + deconvolution layers. + init_cfg (Optional[Union[Dict, List[Dict]]], optional): Configuration + for weight initialization. Defaults to Normal for Conv2d and + ConvTranspose2d layers. + """ + + def __init__(self, + in_channels: int, + out_channels: int, + num_deconv_layers: int, + num_deconv_filters: List[int], + init_cfg: Optional[Union[Dict, List[Dict]]] = dict( + type='Normal', + std=0.001, + layer=['Conv2d', 'ConvTranspose2d'])): + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + + self.deconv_layers = self._make_deconv_layer( + num_deconv_layers, + num_deconv_filters, + ) + + conv_layers = [] + conv_layers.append( + build_conv_layer( + dict(type='Conv2d'), + in_channels=num_deconv_filters[-1], + out_channels=out_channels, + kernel_size=3, + stride=1, + padding=1)) + conv_layers.append(build_norm_layer(dict(type='BN'), out_channels)[1]) + conv_layers.append(nn.ReLU(inplace=True)) + self.conv_layers = nn.Sequential(*conv_layers) + + self.up_sample = nn.Upsample( + scale_factor=2, mode='bilinear', align_corners=False) + + def forward(self, x): + """Forward pass through the decoder network.""" + out = self.deconv_layers(x) + out = self.conv_layers(out) + + out = self.up_sample(out) + out = self.up_sample(out) + + return out + + def _make_deconv_layer(self, num_layers, num_deconv_filters): + """Make deconv layers.""" + + layers = [] + in_channels = self.in_channels + for i in range(num_layers): + + num_channels = num_deconv_filters[i] + layers.append( + build_upsample_layer( + dict(type='deconv'), + in_channels=in_channels, + out_channels=num_channels, + kernel_size=2, + stride=2, + padding=0, + output_padding=0, + bias=False)) + layers.append(nn.BatchNorm2d(num_channels)) + layers.append(nn.ReLU(inplace=True)) + in_channels = num_channels + + return nn.Sequential(*layers) + + +@MODELS.register_module() +class VPDDepthHead(BaseDecodeHead): + """Depth Prediction Head for VPD. + + .. _`VPD`: https://arxiv.org/abs/2303.02153 + + Args: + max_depth (float): Maximum depth value. Defaults to 10.0. + in_channels (Sequence[int]): Number of input channels for each + convolutional layer. + embed_dim (int): Dimension of embedding. Defaults to 192. + feature_dim (int): Dimension of aggregated feature. Defaults to 1536. + num_deconv_layers (int): Number of deconvolution layers in the + decoder. Defaults to 3. + num_deconv_filters (Sequence[int]): Number of filters for each deconv + layer. Defaults to (32, 32, 32). + fmap_border (Union[int, Sequence[int]]): Feature map border for + cropping. Defaults to 0. + align_corners (bool): Flag for align_corners in interpolation. + Defaults to False. + loss_decode (dict): Configurations for the loss function. Defaults to + dict(type='SiLogLoss'). + init_cfg (dict): Initialization configurations. Defaults to + dict(type='TruncNormal', std=0.02, layer=['Conv2d', 'Linear']). + """ + + num_classes = 1 + out_channels = 1 + input_transform = None + + def __init__( + self, + max_depth: float = 10.0, + in_channels: Sequence[int] = [320, 640, 1280, 1280], + embed_dim: int = 192, + feature_dim: int = 1536, + num_deconv_layers: int = 3, + num_deconv_filters: Sequence[int] = (32, 32, 32), + fmap_border: Union[int, Sequence[int]] = 0, + align_corners: bool = False, + loss_decode: dict = dict(type='SiLogLoss'), + init_cfg=dict( + type='TruncNormal', std=0.02, layer=['Conv2d', 'Linear']), + ): + + super(BaseDecodeHead, self).__init__(init_cfg=init_cfg) + + # initialize parameters + self.in_channels = in_channels + self.max_depth = max_depth + self.align_corners = align_corners + + # feature map border + if isinstance(fmap_border, int): + fmap_border = (fmap_border, fmap_border) + self.fmap_border = fmap_border + + # define network layers + self.conv1 = nn.Sequential( + nn.Conv2d(in_channels[0], in_channels[0], 3, stride=2, padding=1), + nn.GroupNorm(16, in_channels[0]), + nn.ReLU(), + nn.Conv2d(in_channels[0], in_channels[0], 3, stride=2, padding=1), + ) + self.conv2 = nn.Conv2d( + in_channels[1], in_channels[1], 3, stride=2, padding=1) + + self.conv_aggregation = nn.Sequential( + nn.Conv2d(sum(in_channels), feature_dim, 1), + nn.GroupNorm(16, feature_dim), + nn.ReLU(), + ) + + self.decoder = VPDDepthDecoder( + in_channels=embed_dim * 8, + out_channels=embed_dim, + num_deconv_layers=num_deconv_layers, + num_deconv_filters=num_deconv_filters) + + self.depth_pred_layer = nn.Sequential( + nn.Conv2d( + embed_dim, embed_dim, kernel_size=3, stride=1, padding=1), + nn.ReLU(inplace=False), + nn.Conv2d(embed_dim, 1, kernel_size=3, stride=1, padding=1)) + + # build loss + if isinstance(loss_decode, dict): + self.loss_decode = MODELS.build(loss_decode) + elif isinstance(loss_decode, (list, tuple)): + self.loss_decode = nn.ModuleList() + for loss in loss_decode: + self.loss_decode.append(MODELS.build(loss)) + else: + raise TypeError(f'loss_decode must be a dict or sequence of dict,\ + but got {type(loss_decode)}') + + def _stack_batch_gt(self, batch_data_samples: SampleList) -> Tensor: + gt_depth_maps = [ + data_sample.gt_depth_map.data for data_sample in batch_data_samples + ] + return torch.stack(gt_depth_maps, dim=0) + + def forward(self, x): + x = [ + x[0], x[1], + torch.cat([x[2], F.interpolate(x[3], scale_factor=2)], dim=1) + ] + x = torch.cat([self.conv1(x[0]), self.conv2(x[1]), x[2]], dim=1) + x = self.conv_aggregation(x) + + x = x[:, :, :x.size(2) - self.fmap_border[0], :x.size(3) - + self.fmap_border[1]].contiguous() + x = self.decoder(x) + out = self.depth_pred_layer(x) + + depth = torch.sigmoid(out) * self.max_depth + + return depth + + def loss_by_feat(self, pred_depth_map: Tensor, + batch_data_samples: SampleList) -> dict: + """Compute depth estimation loss. + + Args: + pred_depth_map (Tensor): The output from decode head forward + function. + batch_data_samples (List[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `metainfo` and `gt_dpeth_map`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + + gt_depth_map = self._stack_batch_gt(batch_data_samples) + loss = dict() + pred_depth_map = resize( + input=pred_depth_map, + size=gt_depth_map.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + + if not isinstance(self.loss_decode, nn.ModuleList): + losses_decode = [self.loss_decode] + else: + losses_decode = self.loss_decode + for loss_decode in losses_decode: + if loss_decode.loss_name not in loss: + loss[loss_decode.loss_name] = loss_decode( + pred_depth_map, gt_depth_map) + else: + loss[loss_decode.loss_name] += loss_decode( + pred_depth_map, gt_depth_map) + + return loss diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/__init__.py new file mode 100644 index 0000000..0467cb3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/__init__.py @@ -0,0 +1,21 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .accuracy import Accuracy, accuracy +from .boundary_loss import BoundaryLoss +from .cross_entropy_loss import (CrossEntropyLoss, binary_cross_entropy, + cross_entropy, mask_cross_entropy) +from .dice_loss import DiceLoss +from .focal_loss import FocalLoss +from .huasdorff_distance_loss import HuasdorffDisstanceLoss +from .lovasz_loss import LovaszLoss +from .ohem_cross_entropy_loss import OhemCrossEntropy +from .silog_loss import SiLogLoss +from .tversky_loss import TverskyLoss +from .utils import reduce_loss, weight_reduce_loss, weighted_loss + +__all__ = [ + 'accuracy', 'Accuracy', 'cross_entropy', 'binary_cross_entropy', + 'mask_cross_entropy', 'CrossEntropyLoss', 'reduce_loss', + 'weight_reduce_loss', 'weighted_loss', 'LovaszLoss', 'DiceLoss', + 'FocalLoss', 'TverskyLoss', 'OhemCrossEntropy', 'BoundaryLoss', + 'HuasdorffDisstanceLoss', 'SiLogLoss' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/accuracy.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/accuracy.py new file mode 100644 index 0000000..1d9e2d7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/accuracy.py @@ -0,0 +1,92 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn + + +def accuracy(pred, target, topk=1, thresh=None, ignore_index=None): + """Calculate accuracy according to the prediction and target. + + Args: + pred (torch.Tensor): The model prediction, shape (N, num_class, ...) + target (torch.Tensor): The target of each prediction, shape (N, , ...) + ignore_index (int | None): The label index to be ignored. Default: None + topk (int | tuple[int], optional): If the predictions in ``topk`` + matches the target, the predictions will be regarded as + correct ones. Defaults to 1. + thresh (float, optional): If not None, predictions with scores under + this threshold are considered incorrect. Default to None. + + Returns: + float | tuple[float]: If the input ``topk`` is a single integer, + the function will return a single float as accuracy. If + ``topk`` is a tuple containing multiple integers, the + function will return a tuple containing accuracies of + each ``topk`` number. + """ + assert isinstance(topk, (int, tuple)) + if isinstance(topk, int): + topk = (topk, ) + return_single = True + else: + return_single = False + + maxk = max(topk) + if pred.size(0) == 0: + accu = [pred.new_tensor(0.) for i in range(len(topk))] + return accu[0] if return_single else accu + assert pred.ndim == target.ndim + 1 + assert pred.size(0) == target.size(0) + assert maxk <= pred.size(1), \ + f'maxk {maxk} exceeds pred dimension {pred.size(1)}' + pred_value, pred_label = pred.topk(maxk, dim=1) + # transpose to shape (maxk, N, ...) + pred_label = pred_label.transpose(0, 1) + correct = pred_label.eq(target.unsqueeze(0).expand_as(pred_label)) + if thresh is not None: + # Only prediction values larger than thresh are counted as correct + correct = correct & (pred_value > thresh).t() + if ignore_index is not None: + correct = correct[:, target != ignore_index] + res = [] + eps = torch.finfo(torch.float32).eps + for k in topk: + # Avoid causing ZeroDivisionError when all pixels + # of an image are ignored + correct_k = correct[:k].reshape(-1).float().sum(0, keepdim=True) + eps + if ignore_index is not None: + total_num = target[target != ignore_index].numel() + eps + else: + total_num = target.numel() + eps + res.append(correct_k.mul_(100.0 / total_num)) + return res[0] if return_single else res + + +class Accuracy(nn.Module): + """Accuracy calculation module.""" + + def __init__(self, topk=(1, ), thresh=None, ignore_index=None): + """Module to calculate the accuracy. + + Args: + topk (tuple, optional): The criterion used to calculate the + accuracy. Defaults to (1,). + thresh (float, optional): If not None, predictions with scores + under this threshold are considered incorrect. Default to None. + """ + super().__init__() + self.topk = topk + self.thresh = thresh + self.ignore_index = ignore_index + + def forward(self, pred, target): + """Forward function to calculate accuracy. + + Args: + pred (torch.Tensor): Prediction of models. + target (torch.Tensor): Target for each prediction. + + Returns: + tuple[float]: The accuracies under different topk criterions. + """ + return accuracy(pred, target, self.topk, self.thresh, + self.ignore_index) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/boundary_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/boundary_loss.py new file mode 100644 index 0000000..e86b850 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/boundary_loss.py @@ -0,0 +1,62 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch import Tensor + +from mmseg.registry import MODELS + + +@MODELS.register_module() +class BoundaryLoss(nn.Module): + """Boundary loss. + + This function is modified from + `PIDNet `_. # noqa + Licensed under the MIT License. + + + Args: + loss_weight (float): Weight of the loss. Defaults to 1.0. + loss_name (str): Name of the loss item. If you want this loss + item to be included into the backward graph, `loss_` must be the + prefix of the name. Defaults to 'loss_boundary'. + """ + + def __init__(self, + loss_weight: float = 1.0, + loss_name: str = 'loss_boundary'): + super().__init__() + self.loss_weight = loss_weight + self.loss_name_ = loss_name + + def forward(self, bd_pre: Tensor, bd_gt: Tensor) -> Tensor: + """Forward function. + Args: + bd_pre (Tensor): Predictions of the boundary head. + bd_gt (Tensor): Ground truth of the boundary. + + Returns: + Tensor: Loss tensor. + """ + log_p = bd_pre.permute(0, 2, 3, 1).contiguous().view(1, -1) + target_t = bd_gt.view(1, -1).float() + + pos_index = (target_t == 1) + neg_index = (target_t == 0) + + weight = torch.zeros_like(log_p) + pos_num = pos_index.sum() + neg_num = neg_index.sum() + sum_num = pos_num + neg_num + weight[pos_index] = neg_num * 1.0 / sum_num + weight[neg_index] = pos_num * 1.0 / sum_num + + loss = F.binary_cross_entropy_with_logits( + log_p, target_t, weight, reduction='mean') + + return self.loss_weight * loss + + @property + def loss_name(self): + return self.loss_name_ diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/cross_entropy_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/cross_entropy_loss.py new file mode 100644 index 0000000..988fb78 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/cross_entropy_loss.py @@ -0,0 +1,311 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmseg.registry import MODELS +from .utils import get_class_weight, weight_reduce_loss + + +def cross_entropy(pred, + label, + weight=None, + class_weight=None, + reduction='mean', + avg_factor=None, + ignore_index=-100, + avg_non_ignore=False): + """cross_entropy. The wrapper function for :func:`F.cross_entropy` + + Args: + pred (torch.Tensor): The prediction with shape (N, 1). + label (torch.Tensor): The learning label of the prediction. + weight (torch.Tensor, optional): Sample-wise loss weight. + Default: None. + class_weight (list[float], optional): The weight for each class. + Default: None. + reduction (str, optional): The method used to reduce the loss. + Options are 'none', 'mean' and 'sum'. Default: 'mean'. + avg_factor (int, optional): Average factor that is used to average + the loss. Default: None. + ignore_index (int): Specifies a target value that is ignored and + does not contribute to the input gradients. When + ``avg_non_ignore `` is ``True``, and the ``reduction`` is + ``''mean''``, the loss is averaged over non-ignored targets. + Defaults: -100. + avg_non_ignore (bool): The flag decides to whether the loss is + only averaged over non-ignored targets. Default: False. + `New in version 0.23.0.` + """ + + # class_weight is a manual rescaling weight given to each class. + # If given, has to be a Tensor of size C element-wise losses + loss = F.cross_entropy( + pred, + label, + weight=class_weight, + reduction='none', + ignore_index=ignore_index) + + # apply weights and do the reduction + # average loss over non-ignored elements + # pytorch's official cross_entropy average loss over non-ignored elements + # refer to https://github.com/pytorch/pytorch/blob/56b43f4fec1f76953f15a627694d4bba34588969/torch/nn/functional.py#L2660 # noqa + if (avg_factor is None) and reduction == 'mean': + if class_weight is None: + if avg_non_ignore: + avg_factor = label.numel() - (label + == ignore_index).sum().item() + else: + avg_factor = label.numel() + + else: + # the average factor should take the class weights into account + label_weights = torch.stack([class_weight[cls] for cls in label + ]).to(device=class_weight.device) + + if avg_non_ignore: + label_weights[label == ignore_index] = 0 + avg_factor = label_weights.sum() + + if weight is not None: + weight = weight.float() + loss = weight_reduce_loss( + loss, weight=weight, reduction=reduction, avg_factor=avg_factor) + + return loss + + +def _expand_onehot_labels(labels, label_weights, target_shape, ignore_index): + """Expand onehot labels to match the size of prediction.""" + bin_labels = labels.new_zeros(target_shape) + valid_mask = (labels >= 0) & (labels != ignore_index) + inds = torch.nonzero(valid_mask, as_tuple=True) + + if inds[0].numel() > 0: + if labels.dim() == 3: + bin_labels[inds[0], labels[valid_mask], inds[1], inds[2]] = 1 + else: + bin_labels[inds[0], labels[valid_mask]] = 1 + + valid_mask = valid_mask.unsqueeze(1).expand(target_shape).float() + + if label_weights is None: + bin_label_weights = valid_mask + else: + bin_label_weights = label_weights.unsqueeze(1).expand(target_shape) + bin_label_weights = bin_label_weights * valid_mask + + return bin_labels, bin_label_weights, valid_mask + + +def binary_cross_entropy(pred, + label, + weight=None, + reduction='mean', + avg_factor=None, + class_weight=None, + ignore_index=-100, + avg_non_ignore=False, + **kwargs): + """Calculate the binary CrossEntropy loss. + + Args: + pred (torch.Tensor): The prediction with shape (N, 1). + label (torch.Tensor): The learning label of the prediction. + Note: In bce loss, label < 0 is invalid. + weight (torch.Tensor, optional): Sample-wise loss weight. + reduction (str, optional): The method used to reduce the loss. + Options are "none", "mean" and "sum". + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + class_weight (list[float], optional): The weight for each class. + ignore_index (int): The label index to be ignored. Default: -100. + avg_non_ignore (bool): The flag decides to whether the loss is + only averaged over non-ignored targets. Default: False. + `New in version 0.23.0.` + + Returns: + torch.Tensor: The calculated loss + """ + if pred.size(1) == 1: + # For binary class segmentation, the shape of pred is + # [N, 1, H, W] and that of label is [N, H, W]. + # As the ignore_index often set as 255, so the + # binary class label check should mask out + # ignore_index + assert label[label != ignore_index].max() <= 1, \ + 'For pred with shape [N, 1, H, W], its label must have at ' \ + 'most 2 classes' + pred = pred.squeeze(1) + if pred.dim() != label.dim(): + assert (pred.dim() == 2 and label.dim() == 1) or ( + pred.dim() == 4 and label.dim() == 3), \ + 'Only pred shape [N, C], label shape [N] or pred shape [N, C, ' \ + 'H, W], label shape [N, H, W] are supported' + # `weight` returned from `_expand_onehot_labels` + # has been treated for valid (non-ignore) pixels + label, weight, valid_mask = _expand_onehot_labels( + label, weight, pred.shape, ignore_index) + else: + # should mask out the ignored elements + valid_mask = ((label >= 0) & (label != ignore_index)).float() + if weight is not None: + weight = weight * valid_mask + else: + weight = valid_mask + # average loss over non-ignored and valid elements + if reduction == 'mean' and avg_factor is None and avg_non_ignore: + avg_factor = valid_mask.sum().item() + + loss = F.binary_cross_entropy_with_logits( + pred, label.float(), pos_weight=class_weight, reduction='none') + # do the reduction for the weighted loss + loss = weight_reduce_loss( + loss, weight, reduction=reduction, avg_factor=avg_factor) + + return loss + + +def mask_cross_entropy(pred, + target, + label, + reduction='mean', + avg_factor=None, + class_weight=None, + ignore_index=None, + **kwargs): + """Calculate the CrossEntropy loss for masks. + + Args: + pred (torch.Tensor): The prediction with shape (N, C), C is the number + of classes. + target (torch.Tensor): The learning label of the prediction. + label (torch.Tensor): ``label`` indicates the class label of the mask' + corresponding object. This will be used to select the mask in the + of the class which the object belongs to when the mask prediction + if not class-agnostic. + reduction (str, optional): The method used to reduce the loss. + Options are "none", "mean" and "sum". + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + class_weight (list[float], optional): The weight for each class. + ignore_index (None): Placeholder, to be consistent with other loss. + Default: None. + + Returns: + torch.Tensor: The calculated loss + """ + assert ignore_index is None, 'BCE loss does not support ignore_index' + # TODO: handle these two reserved arguments + assert reduction == 'mean' and avg_factor is None + num_rois = pred.size()[0] + inds = torch.arange(0, num_rois, dtype=torch.long, device=pred.device) + pred_slice = pred[inds, label].squeeze(1) + return F.binary_cross_entropy_with_logits( + pred_slice, target, weight=class_weight, reduction='mean')[None] + + +@MODELS.register_module() +class CrossEntropyLoss(nn.Module): + """CrossEntropyLoss. + + Args: + use_sigmoid (bool, optional): Whether the prediction uses sigmoid + of softmax. Defaults to False. + use_mask (bool, optional): Whether to use mask cross entropy loss. + Defaults to False. + reduction (str, optional): . Defaults to 'mean'. + Options are "none", "mean" and "sum". + class_weight (list[float] | str, optional): Weight of each class. If in + str format, read them from a file. Defaults to None. + loss_weight (float, optional): Weight of the loss. Defaults to 1.0. + loss_name (str, optional): Name of the loss item. If you want this loss + item to be included into the backward graph, `loss_` must be the + prefix of the name. Defaults to 'loss_ce'. + avg_non_ignore (bool): The flag decides to whether the loss is + only averaged over non-ignored targets. Default: False. + `New in version 0.23.0.` + """ + + def __init__(self, + use_sigmoid=False, + use_mask=False, + reduction='mean', + class_weight=None, + loss_weight=1.0, + loss_name='loss_ce', + avg_non_ignore=False): + super().__init__() + assert (use_sigmoid is False) or (use_mask is False) + self.use_sigmoid = use_sigmoid + self.use_mask = use_mask + self.reduction = reduction + self.loss_weight = loss_weight + self.class_weight = get_class_weight(class_weight) + self.avg_non_ignore = avg_non_ignore + if not self.avg_non_ignore and self.reduction == 'mean': + warnings.warn( + 'Default ``avg_non_ignore`` is False, if you would like to ' + 'ignore the certain label and average loss over non-ignore ' + 'labels, which is the same with PyTorch official ' + 'cross_entropy, set ``avg_non_ignore=True``.') + + if self.use_sigmoid: + self.cls_criterion = binary_cross_entropy + elif self.use_mask: + self.cls_criterion = mask_cross_entropy + else: + self.cls_criterion = cross_entropy + self._loss_name = loss_name + + def extra_repr(self): + """Extra repr.""" + s = f'avg_non_ignore={self.avg_non_ignore}' + return s + + def forward(self, + cls_score, + label, + weight=None, + avg_factor=None, + reduction_override=None, + ignore_index=-100, + **kwargs): + """Forward function.""" + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.class_weight is not None: + class_weight = cls_score.new_tensor(self.class_weight) + else: + class_weight = None + # Note: for BCE loss, label < 0 is invalid. + loss_cls = self.loss_weight * self.cls_criterion( + cls_score, + label, + weight, + class_weight=class_weight, + reduction=reduction, + avg_factor=avg_factor, + avg_non_ignore=self.avg_non_ignore, + ignore_index=ignore_index, + **kwargs) + return loss_cls + + @property + def loss_name(self): + """Loss Name. + + This function must be implemented and will return the name of this + loss function. This name will be used to combine different loss items + by simple sum operation. In addition, if you want this loss item to be + included into the backward graph, `loss_` must be the prefix of the + name. + + Returns: + str: The name of this loss item. + """ + return self._loss_name diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/dice_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/dice_loss.py new file mode 100644 index 0000000..e5a6f6d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/dice_loss.py @@ -0,0 +1,242 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Union + +import torch +import torch.nn as nn + +from mmseg.registry import MODELS +from .utils import weight_reduce_loss + + +def _expand_onehot_labels_dice(pred: torch.Tensor, + target: torch.Tensor) -> torch.Tensor: + """Expand onehot labels to match the size of prediction. + + Args: + pred (torch.Tensor): The prediction, has a shape (N, num_class, H, W). + target (torch.Tensor): The learning label of the prediction, + has a shape (N, H, W). + + Returns: + torch.Tensor: The target after one-hot encoding, + has a shape (N, num_class, H, W). + """ + num_classes = pred.shape[1] + one_hot_target = torch.clamp(target, min=0, max=num_classes) + one_hot_target = torch.nn.functional.one_hot(one_hot_target, + num_classes + 1) + one_hot_target = one_hot_target[..., :num_classes].permute(0, 3, 1, 2) + return one_hot_target + + +def dice_loss_old(pred: torch.Tensor, + target: torch.Tensor, + weight: Union[torch.Tensor, None], + eps: float = 1e-3, + reduction: Union[str, None] = 'mean', + naive_dice: Union[bool, None] = False, + avg_factor: Union[int, None] = None, + ignore_index: Union[int, None] = 255) -> float: + """Calculate dice loss, there are two forms of dice loss is supported: + + - the one proposed in `V-Net: Fully Convolutional Neural + Networks for Volumetric Medical Image Segmentation + `_. + - the dice loss in which the power of the number in the + denominator is the first power instead of the second + power. + + Args: + pred (torch.Tensor): The prediction, has a shape (n, *) + target (torch.Tensor): The learning label of the prediction, + shape (n, *), same shape of pred. + weight (torch.Tensor, optional): The weight of loss for each + prediction, has a shape (n,). Defaults to None. + eps (float): Avoid dividing by zero. Default: 1e-3. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. + Options are "none", "mean" and "sum". + naive_dice (bool, optional): If false, use the dice + loss defined in the V-Net paper, otherwise, use the + naive dice loss in which the power of the number in the + denominator is the first power instead of the second + power.Defaults to False. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + ignore_index (int, optional): The label index to be ignored. + Defaults to 255. + """ + if ignore_index is not None: + num_classes = pred.shape[1] + pred = pred[:, torch.arange(num_classes) != ignore_index, :, :] + target = target[:, torch.arange(num_classes) != ignore_index, :, :] + assert pred.shape[1] != 0 # if the ignored index is the only class + input = pred.flatten(1) + target = target.flatten(1).float() + a = torch.sum(input * target, 1) + if naive_dice: + b = torch.sum(input, 1) + c = torch.sum(target, 1) + d = (2 * a + eps) / (b + c + eps) + else: + b = torch.sum(input * input, 1) + eps + c = torch.sum(target * target, 1) + eps + d = (2 * a) / (b + c) + + loss = 1 - d + if weight is not None: + assert weight.ndim == loss.ndim + assert len(weight) == len(pred) + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + +# TODO # 解决dice_loss和OhemSampler是weight不匹配问题 +def dice_loss(pred: torch.Tensor, + target: torch.Tensor, + weight: Union[torch.Tensor, None], + eps: float = 1e-3, + reduction: Union[str, None] = 'mean', + naive_dice: Union[bool, None] = False, + avg_factor: Union[int, None] = None, + ignore_index: Union[int, None] = 255) -> float: + """Calculate dice loss.""" + if ignore_index is not None: + num_classes = pred.shape[1] + pred = pred[:, torch.arange(num_classes) != ignore_index, :, :] + target = target[:, torch.arange(num_classes) != ignore_index, :, :] + assert pred.shape[1] != 0 # if the ignored index is the only class + + input = pred.flatten(1) # Flatten all dimensions except batch + target = target.flatten(1).float() + + a = torch.sum(input * target, 1) + if naive_dice: + b = torch.sum(input, 1) + c = torch.sum(target, 1) + d = (2 * a + eps) / (b + c + eps) + else: + b = torch.sum(input * input, 1) + eps + c = torch.sum(target * target, 1) + eps + d = (2 * a) / (b + c) + + loss = 1 - d + + if weight is not None: + # TODO # Adjust weight to match loss shape if necessary + if weight.ndim > 1: + weight = weight.mean(dim=(1, 2)) # Reduce weight to match the batch size dimension + assert weight.ndim == loss.ndim, f"Weight shape {weight.shape} must match loss shape {loss.shape}" + assert len(weight) == len(pred), f"Weight length {len(weight)} must match batch size {len(pred)}" + + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + +@MODELS.register_module() +class DiceLoss(nn.Module): + + def __init__(self, + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=False, + loss_weight=1.0, + ignore_index=255, + eps=1e-3, + loss_name='loss_dice'): + """Compute dice loss. + + Args: + use_sigmoid (bool, optional): Whether to the prediction is + used for sigmoid or softmax. Defaults to True. + activate (bool): Whether to activate the predictions inside, + this will disable the inside sigmoid operation. + Defaults to True. + reduction (str, optional): The method used + to reduce the loss. Options are "none", + "mean" and "sum". Defaults to 'mean'. + naive_dice (bool, optional): If false, use the dice + loss defined in the V-Net paper, otherwise, use the + naive dice loss in which the power of the number in the + denominator is the first power instead of the second + power. Defaults to False. + loss_weight (float, optional): Weight of loss. Defaults to 1.0. + ignore_index (int, optional): The label index to be ignored. + Default: 255. + eps (float): Avoid dividing by zero. Defaults to 1e-3. + loss_name (str, optional): Name of the loss item. If you want this + loss item to be included into the backward graph, `loss_` must + be the prefix of the name. Defaults to 'loss_dice'. + """ + + super().__init__() + self.use_sigmoid = use_sigmoid + self.reduction = reduction + self.naive_dice = naive_dice + self.loss_weight = loss_weight + self.eps = eps + self.activate = activate + self.ignore_index = ignore_index + self._loss_name = loss_name + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None, + ignore_index=255, + **kwargs): + """Forward function. + + Args: + pred (torch.Tensor): The prediction, has a shape (n, *). + target (torch.Tensor): The label of the prediction, + shape (n, *), same shape of pred. + weight (torch.Tensor, optional): The weight of loss for each + prediction, has a shape (n,). Defaults to None. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + reduction_override (str, optional): The reduction method used to + override the original reduction method of the loss. + Options are "none", "mean" and "sum". + + Returns: + torch.Tensor: The calculated loss + """ + one_hot_target = target + if (pred.shape != target.shape): + one_hot_target = _expand_onehot_labels_dice(pred, target) + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.activate: + if self.use_sigmoid: + pred = pred.sigmoid() + elif pred.shape[1] != 1: + # softmax does not work when there is only 1 class + pred = pred.softmax(dim=1) + loss = self.loss_weight * dice_loss( + pred, + one_hot_target, + weight, + eps=self.eps, + reduction=reduction, + naive_dice=self.naive_dice, + avg_factor=avg_factor, + ignore_index=self.ignore_index) + + return loss + + @property + def loss_name(self): + """Loss Name. + + This function must be implemented and will return the name of this + loss function. This name will be used to combine different loss items + by simple sum operation. In addition, if you want this loss item to be + included into the backward graph, `loss_` must be the prefix of the + name. + Returns: + str: The name of this loss item. + """ + return self._loss_name diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/focal_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/focal_loss.py new file mode 100644 index 0000000..6507ed7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/focal_loss.py @@ -0,0 +1,337 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Modified from https://github.com/open-mmlab/mmdetection +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.ops import sigmoid_focal_loss as _sigmoid_focal_loss + +from mmseg.registry import MODELS +from .utils import weight_reduce_loss + + +# This method is used when cuda is not available +def py_sigmoid_focal_loss(pred, + target, + one_hot_target=None, + weight=None, + gamma=2.0, + alpha=0.5, + class_weight=None, + valid_mask=None, + reduction='mean', + avg_factor=None): + """PyTorch version of `Focal Loss `_. + + Args: + pred (torch.Tensor): The prediction with shape (N, C), C is the + number of classes + target (torch.Tensor): The learning label of the prediction with + shape (N, C) + one_hot_target (None): Placeholder. It should be None. + weight (torch.Tensor, optional): Sample-wise loss weight. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + alpha (float | list[float], optional): A balanced form for Focal Loss. + Defaults to 0.5. + class_weight (list[float], optional): Weight of each class. + Defaults to None. + valid_mask (torch.Tensor, optional): A mask uses 1 to mark the valid + samples and uses 0 to mark the ignored samples. Default: None. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + """ + if isinstance(alpha, list): + alpha = pred.new_tensor(alpha) + pred_sigmoid = pred.sigmoid() + target = target.type_as(pred) + one_minus_pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target) + focal_weight = (alpha * target + (1 - alpha) * + (1 - target)) * one_minus_pt.pow(gamma) + + loss = F.binary_cross_entropy_with_logits( + pred, target, reduction='none') * focal_weight + final_weight = torch.ones(1, pred.size(1)).type_as(loss) + if weight is not None: + if weight.shape != loss.shape and weight.size(0) == loss.size(0): + # For most cases, weight is of shape (N, ), + # which means it does not have the second axis num_class + weight = weight.view(-1, 1) + assert weight.dim() == loss.dim() + final_weight = final_weight * weight + if class_weight is not None: + final_weight = final_weight * pred.new_tensor(class_weight) + if valid_mask is not None: + final_weight = final_weight * valid_mask + loss = weight_reduce_loss(loss, final_weight, reduction, avg_factor) + return loss + + +def sigmoid_focal_loss(pred, + target, + one_hot_target, + weight=None, + gamma=2.0, + alpha=0.5, + class_weight=None, + valid_mask=None, + reduction='mean', + avg_factor=None): + r"""A wrapper of cuda version `Focal Loss + `_. + Args: + pred (torch.Tensor): The prediction with shape (N, C), C is the number + of classes. + target (torch.Tensor): The learning label of the prediction. It's shape + should be (N, ) + one_hot_target (torch.Tensor): The learning label with shape (N, C) + weight (torch.Tensor, optional): Sample-wise loss weight. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + alpha (float | list[float], optional): A balanced form for Focal Loss. + Defaults to 0.5. + class_weight (list[float], optional): Weight of each class. + Defaults to None. + valid_mask (torch.Tensor, optional): A mask uses 1 to mark the valid + samples and uses 0 to mark the ignored samples. Default: None. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. Options are "none", "mean" and "sum". + avg_factor (int, optional): Average factor that is used to average + the loss. Defaults to None. + """ + # Function.apply does not accept keyword arguments, so the decorator + # "weighted_loss" is not applicable + final_weight = torch.ones(1, pred.size(1)).type_as(pred) + if isinstance(alpha, list): + # _sigmoid_focal_loss doesn't accept alpha of list type. Therefore, if + # a list is given, we set the input alpha as 0.5. This means setting + # equal weight for foreground class and background class. By + # multiplying the loss by 2, the effect of setting alpha as 0.5 is + # undone. The alpha of type list is used to regulate the loss in the + # post-processing process. + loss = _sigmoid_focal_loss(pred.contiguous(), target.contiguous(), + gamma, 0.5, None, 'none') * 2 + alpha = pred.new_tensor(alpha) + final_weight = final_weight * ( + alpha * one_hot_target + (1 - alpha) * (1 - one_hot_target)) + else: + loss = _sigmoid_focal_loss(pred.contiguous(), target.contiguous(), + gamma, alpha, None, 'none') + if weight is not None: + if weight.shape != loss.shape and weight.size(0) == loss.size(0): + # For most cases, weight is of shape (N, ), + # which means it does not have the second axis num_class + weight = weight.view(-1, 1) + assert weight.dim() == loss.dim() + final_weight = final_weight * weight + if class_weight is not None: + final_weight = final_weight * pred.new_tensor(class_weight) + if valid_mask is not None: + final_weight = final_weight * valid_mask + loss = weight_reduce_loss(loss, final_weight, reduction, avg_factor) + return loss + + +@MODELS.register_module() +class FocalLoss(nn.Module): + + def __init__(self, + use_sigmoid=True, + gamma=2.0, + alpha=0.5, + reduction='mean', + class_weight=None, + loss_weight=1.0, + loss_name='loss_focal'): + """`Focal Loss `_ + Args: + use_sigmoid (bool, optional): Whether to the prediction is + used for sigmoid or softmax. Defaults to True. + gamma (float, optional): The gamma for calculating the modulating + factor. Defaults to 2.0. + alpha (float | list[float], optional): A balanced form for Focal + Loss. Defaults to 0.5. When a list is provided, the length + of the list should be equal to the number of classes. + Please be careful that this parameter is not the + class-wise weight but the weight of a binary classification + problem. This binary classification problem regards the + pixels which belong to one class as the foreground + and the other pixels as the background, each element in + the list is the weight of the corresponding foreground class. + The value of alpha or each element of alpha should be a float + in the interval [0, 1]. If you want to specify the class-wise + weight, please use `class_weight` parameter. + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. Options are "none", "mean" and + "sum". + class_weight (list[float], optional): Weight of each class. + Defaults to None. + loss_weight (float, optional): Weight of loss. Defaults to 1.0. + loss_name (str, optional): Name of the loss item. If you want this + loss item to be included into the backward graph, `loss_` must + be the prefix of the name. Defaults to 'loss_focal'. + """ + super().__init__() + assert use_sigmoid is True, \ + 'AssertionError: Only sigmoid focal loss supported now.' + assert reduction in ('none', 'mean', 'sum'), \ + "AssertionError: reduction should be 'none', 'mean' or " \ + "'sum'" + assert isinstance(alpha, (float, list)), \ + 'AssertionError: alpha should be of type float' + assert isinstance(gamma, float), \ + 'AssertionError: gamma should be of type float' + assert isinstance(loss_weight, float), \ + 'AssertionError: loss_weight should be of type float' + assert isinstance(loss_name, str), \ + 'AssertionError: loss_name should be of type str' + assert isinstance(class_weight, list) or class_weight is None, \ + 'AssertionError: class_weight must be None or of type list' + self.use_sigmoid = use_sigmoid + self.gamma = gamma + self.alpha = alpha + self.reduction = reduction + self.class_weight = class_weight + self.loss_weight = loss_weight + self._loss_name = loss_name + + def forward(self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None, + ignore_index=255, + **kwargs): + """Forward function. + + Args: + pred (torch.Tensor): The prediction with shape + (N, C) where C = number of classes, or + (N, C, d_1, d_2, ..., d_K) with K≥1 in the + case of K-dimensional loss. + target (torch.Tensor): The ground truth. If containing class + indices, shape (N) where each value is 0≤targets[i]≤C−1, + or (N, d_1, d_2, ..., d_K) with K≥1 in the case of + K-dimensional loss. If containing class probabilities, + same shape as the input. + weight (torch.Tensor, optional): The weight of loss for each + prediction. Defaults to None. + avg_factor (int, optional): Average factor that is used to + average the loss. Defaults to None. + reduction_override (str, optional): The reduction method used + to override the original reduction method of the loss. + Options are "none", "mean" and "sum". + ignore_index (int, optional): The label index to be ignored. + Default: 255 + Returns: + torch.Tensor: The calculated loss + """ + assert isinstance(ignore_index, int), \ + 'ignore_index must be of type int' + assert reduction_override in (None, 'none', 'mean', 'sum'), \ + "AssertionError: reduction should be 'none', 'mean' or " \ + "'sum'" + assert pred.shape == target.shape or \ + (pred.size(0) == target.size(0) and + pred.shape[2:] == target.shape[1:]), \ + "The shape of pred doesn't match the shape of target" + + original_shape = pred.shape + + # [B, C, d_1, d_2, ..., d_k] -> [C, B, d_1, d_2, ..., d_k] + pred = pred.transpose(0, 1) + # [C, B, d_1, d_2, ..., d_k] -> [C, N] + pred = pred.reshape(pred.size(0), -1) + # [C, N] -> [N, C] + pred = pred.transpose(0, 1).contiguous() + + if original_shape == target.shape: + # target with shape [B, C, d_1, d_2, ...] + # transform it's shape into [N, C] + # [B, C, d_1, d_2, ...] -> [C, B, d_1, d_2, ..., d_k] + target = target.transpose(0, 1) + # [C, B, d_1, d_2, ..., d_k] -> [C, N] + target = target.reshape(target.size(0), -1) + # [C, N] -> [N, C] + target = target.transpose(0, 1).contiguous() + else: + # target with shape [B, d_1, d_2, ...] + # transform it's shape into [N, ] + target = target.view(-1).contiguous() + valid_mask = (target != ignore_index).view(-1, 1) + # avoid raising error when using F.one_hot() + target = torch.where(target == ignore_index, target.new_tensor(0), + target) + + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.use_sigmoid: + num_classes = pred.size(1) + if torch.cuda.is_available() and pred.is_cuda: + if target.dim() == 1: + one_hot_target = F.one_hot( + target, num_classes=num_classes + 1) + if num_classes == 1: + one_hot_target = one_hot_target[:, 1] + target = 1 - target + else: + one_hot_target = one_hot_target[:, :num_classes] + else: + one_hot_target = target + target = target.argmax(dim=1) + valid_mask = (target != ignore_index).view(-1, 1) + calculate_loss_func = sigmoid_focal_loss + else: + one_hot_target = None + if target.dim() == 1: + target = F.one_hot(target, num_classes=num_classes + 1) + if num_classes == 1: + target = target[:, 1] + else: + target = target[:, num_classes] + else: + valid_mask = (target.argmax(dim=1) != ignore_index).view( + -1, 1) + calculate_loss_func = py_sigmoid_focal_loss + + loss_cls = self.loss_weight * calculate_loss_func( + pred, + target, + one_hot_target, + weight, + gamma=self.gamma, + alpha=self.alpha, + class_weight=self.class_weight, + valid_mask=valid_mask, + reduction=reduction, + avg_factor=avg_factor) + + if reduction == 'none': + # [N, C] -> [C, N] + loss_cls = loss_cls.transpose(0, 1) + # [C, N] -> [C, B, d1, d2, ...] + # original_shape: [B, C, d1, d2, ...] + loss_cls = loss_cls.reshape(original_shape[1], + original_shape[0], + *original_shape[2:]) + # [C, B, d1, d2, ...] -> [B, C, d1, d2, ...] + loss_cls = loss_cls.transpose(0, 1).contiguous() + else: + raise NotImplementedError + return loss_cls + + @property + def loss_name(self): + """Loss Name. + + This function must be implemented and will return the name of this + loss function. This name will be used to combine different loss items + by simple sum operation. In addition, if you want this loss item to be + included into the backward graph, `loss_` must be the prefix of the + name. + Returns: + str: The name of this loss item. + """ + return self._loss_name diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/huasdorff_distance_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/huasdorff_distance_loss.py new file mode 100644 index 0000000..d950ba7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/huasdorff_distance_loss.py @@ -0,0 +1,160 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Modified from https://github.com/JunMa11/SegWithDistMap/blob/ +master/code/train_LA_HD.py (Apache-2.0 License)""" +import torch +import torch.nn as nn +import torch.nn.functional as F +from scipy.ndimage import distance_transform_edt as distance +from torch import Tensor + +from mmseg.registry import MODELS +from .utils import get_class_weight, weighted_loss + + +def compute_dtm(img_gt: Tensor, pred: Tensor) -> Tensor: + """ + compute the distance transform map of foreground in mask + Args: + img_gt: Ground truth of the image, (b, h, w) + pred: Predictions of the segmentation head after softmax, (b, c, h, w) + + Returns: + output: the foreground Distance Map (SDM) + dtm(x) = 0; x in segmentation boundary + inf|x-y|; x in segmentation + """ + + fg_dtm = torch.zeros_like(pred) + out_shape = pred.shape + for b in range(out_shape[0]): # batch size + for c in range(1, out_shape[1]): # default 0 channel is background + posmask = img_gt[b].byte() + if posmask.any(): + posdis = distance(posmask) + fg_dtm[b][c] = torch.from_numpy(posdis) + + return fg_dtm + + +@weighted_loss +def hd_loss(seg_soft: Tensor, + gt: Tensor, + seg_dtm: Tensor, + gt_dtm: Tensor, + class_weight=None, + ignore_index=255) -> Tensor: + """ + compute huasdorff distance loss for segmentation + Args: + seg_soft: softmax results, shape=(b,c,x,y) + gt: ground truth, shape=(b,x,y) + seg_dtm: segmentation distance transform map, shape=(b,c,x,y) + gt_dtm: ground truth distance transform map, shape=(b,c,x,y) + + Returns: + output: hd_loss + """ + assert seg_soft.shape[0] == gt.shape[0] + total_loss = 0 + num_class = seg_soft.shape[1] + if class_weight is not None: + assert class_weight.ndim == num_class + for i in range(1, num_class): + if i != ignore_index: + delta_s = (seg_soft[:, i, ...] - gt.float())**2 + s_dtm = seg_dtm[:, i, ...]**2 + g_dtm = gt_dtm[:, i, ...]**2 + dtm = s_dtm + g_dtm + multiplied = torch.einsum('bxy, bxy->bxy', delta_s, dtm) + hd_loss = multiplied.mean() + if class_weight is not None: + hd_loss *= class_weight[i] + total_loss += hd_loss + + return total_loss / num_class + + +@MODELS.register_module() +class HuasdorffDisstanceLoss(nn.Module): + """HuasdorffDisstanceLoss. This loss is proposed in `How Distance Transform + Maps Boost Segmentation CNNs: An Empirical Study. + + `_. + Args: + reduction (str, optional): The method used to reduce the loss into + a scalar. Defaults to 'mean'. + class_weight (list[float] | str, optional): Weight of each class. If in + str format, read them from a file. Defaults to None. + loss_weight (float): Weight of the loss. Defaults to 1.0. + ignore_index (int | None): The label index to be ignored. Default: 255. + loss_name (str): Name of the loss item. If you want this loss + item to be included into the backward graph, `loss_` must be the + prefix of the name. Defaults to 'loss_boundary'. + """ + + def __init__(self, + reduction='mean', + class_weight=None, + loss_weight=1.0, + ignore_index=255, + loss_name='loss_huasdorff_disstance', + **kwargs): + super().__init__() + self.reduction = reduction + self.loss_weight = loss_weight + self.class_weight = get_class_weight(class_weight) + self._loss_name = loss_name + self.ignore_index = ignore_index + + def forward(self, + pred: Tensor, + target: Tensor, + avg_factor=None, + reduction_override=None, + **kwargs) -> Tensor: + """Forward function. + + Args: + pred (Tensor): Predictions of the segmentation head. (B, C, H, W) + target (Tensor): Ground truth of the image. (B, H, W) + avg_factor (int, optional): Average factor that is used to + average the loss. Defaults to None. + reduction_override (str, optional): The reduction method used + to override the original reduction method of the loss. + Options are "none", "mean" and "sum". + Returns: + Tensor: Loss tensor. + """ + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.class_weight is not None: + class_weight = pred.new_tensor(self.class_weight) + else: + class_weight = None + + pred_soft = F.softmax(pred, dim=1) + valid_mask = (target != self.ignore_index).long() + target = target * valid_mask + + with torch.no_grad(): + gt_dtm = compute_dtm(target.cpu(), pred_soft) + gt_dtm = gt_dtm.float() + seg_dtm2 = compute_dtm( + pred_soft.argmax(dim=1, keepdim=False).cpu(), pred_soft) + seg_dtm2 = seg_dtm2.float() + + loss_hd = self.loss_weight * hd_loss( + pred_soft, + target, + seg_dtm=seg_dtm2, + gt_dtm=gt_dtm, + reduction=reduction, + avg_factor=avg_factor, + class_weight=class_weight, + ignore_index=self.ignore_index) + return loss_hd + + @property + def loss_name(self): + return self._loss_name diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/kldiv_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/kldiv_loss.py new file mode 100644 index 0000000..496ef97 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/kldiv_loss.py @@ -0,0 +1,99 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmseg.registry import MODELS + + +@MODELS.register_module() +class KLDivLoss(nn.Module): + + def __init__(self, + temperature: float = 1.0, + reduction: str = 'mean', + loss_name: str = 'loss_kld'): + """Kullback-Leibler divergence Loss. + + + + Args: + temperature (float, optional): Temperature param + reduction (str, optional): The method to reduce the loss into a + scalar. Default is "mean". Options are "none", "sum", + and "mean" + """ + + assert isinstance(temperature, (float, int)), \ + 'Expected temperature to be' \ + f'float or int, but got {temperature.__class__.__name__} instead' + assert temperature != 0., 'Temperature must not be zero' + + assert reduction in ['mean', 'none', 'sum'], \ + 'Reduction must be one of the options ("mean", ' \ + f'"sum", "none"), but got {reduction}' + + super().__init__() + self.temperature = temperature + self.reduction = reduction + self._loss_name = loss_name + + def forward(self, input: torch.Tensor, target: torch.Tensor): + """Forward function. Calculate KL divergence Loss. + + Args: + input (Tensor): Logit tensor, + the data type is float32 or float64. + The shape is (N, C) where N is batchsize and C is number of + channels. + If there more than 2 dimensions, shape is (N, C, D1, D2, ... + Dk), k>= 1 + target (Tensor): Logit tensor, + the data type is float32 or float64. + input and target must be with the same shape. + + Returns: + (Tensor): Reduced loss. + """ + assert isinstance(input, torch.Tensor), 'Expected input to' \ + f'be Tensor, but got {input.__class__.__name__} instead' + assert isinstance(target, torch.Tensor), 'Expected target to' \ + f'be Tensor, but got {target.__class__.__name__} instead' + + assert input.shape == target.shape, 'Input and target ' \ + 'must have same shape,' \ + f'but got shapes {input.shape} and {target.shape}' + + input = F.softmax(input / self.temperature, dim=1) + target = F.softmax(target / self.temperature, dim=1) + + loss = F.kl_div(input, target, reduction='none', log_target=False) + loss = loss * self.temperature**2 + + batch_size = input.shape[0] + + if self.reduction == 'sum': + # Change view to calculate instance-wise sum + loss = loss.view(batch_size, -1) + return torch.sum(loss, dim=1) + + elif self.reduction == 'mean': + # Change view to calculate instance-wise mean + loss = loss.view(batch_size, -1) + return torch.mean(loss, dim=1) + + return loss + + @property + def loss_name(self): + """Loss Name. + + This function must be implemented and will return the name of this + loss function. This name will be used to combine different loss items + by simple sum operation. In addition, if you want this loss item to be + included into the backward graph, `loss_` must be the prefix of the + name. + Returns: + str: The name of this loss item. + """ + return self._loss_name diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/lovasz_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/lovasz_loss.py new file mode 100644 index 0000000..b47f9d8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/lovasz_loss.py @@ -0,0 +1,323 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Modified from https://github.com/bermanmaxim/LovaszSoftmax/blob/master/pytor +ch/lovasz_losses.py Lovasz-Softmax and Jaccard hinge loss in PyTorch Maxim +Berman 2018 ESAT-PSI KU Leuven (MIT License)""" + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.utils import is_list_of + +from mmseg.registry import MODELS +from .utils import get_class_weight, weight_reduce_loss + + +def lovasz_grad(gt_sorted): + """Computes gradient of the Lovasz extension w.r.t sorted errors. + + See Alg. 1 in paper. + """ + p = len(gt_sorted) + gts = gt_sorted.sum() + intersection = gts - gt_sorted.float().cumsum(0) + union = gts + (1 - gt_sorted).float().cumsum(0) + jaccard = 1. - intersection / union + if p > 1: # cover 1-pixel case + jaccard[1:p] = jaccard[1:p] - jaccard[0:-1] + return jaccard + + +def flatten_binary_logits(logits, labels, ignore_index=None): + """Flattens predictions in the batch (binary case) Remove labels equal to + 'ignore_index'.""" + logits = logits.view(-1) + labels = labels.view(-1) + if ignore_index is None: + return logits, labels + valid = (labels != ignore_index) + vlogits = logits[valid] + vlabels = labels[valid] + return vlogits, vlabels + + +def flatten_probs(probs, labels, ignore_index=None): + """Flattens predictions in the batch.""" + if probs.dim() == 3: + # assumes output of a sigmoid layer + B, H, W = probs.size() + probs = probs.view(B, 1, H, W) + B, C, H, W = probs.size() + probs = probs.permute(0, 2, 3, 1).contiguous().view(-1, C) # B*H*W, C=P,C + labels = labels.view(-1) + if ignore_index is None: + return probs, labels + valid = (labels != ignore_index) + vprobs = probs[valid.nonzero().squeeze()] + vlabels = labels[valid] + return vprobs, vlabels + + +def lovasz_hinge_flat(logits, labels): + """Binary Lovasz hinge loss. + + Args: + logits (torch.Tensor): [P], logits at each prediction + (between -infty and +infty). + labels (torch.Tensor): [P], binary ground truth labels (0 or 1). + + Returns: + torch.Tensor: The calculated loss. + """ + if len(labels) == 0: + # only void pixels, the gradients should be 0 + return logits.sum() * 0. + signs = 2. * labels.float() - 1. + errors = (1. - logits * signs) + errors_sorted, perm = torch.sort(errors, dim=0, descending=True) + perm = perm.data + gt_sorted = labels[perm] + grad = lovasz_grad(gt_sorted) + loss = torch.dot(F.relu(errors_sorted), grad) + return loss + + +def lovasz_hinge(logits, + labels, + classes='present', + per_image=False, + class_weight=None, + reduction='mean', + avg_factor=None, + ignore_index=255): + """Binary Lovasz hinge loss. + + Args: + logits (torch.Tensor): [B, H, W], logits at each pixel + (between -infty and +infty). + labels (torch.Tensor): [B, H, W], binary ground truth masks (0 or 1). + classes (str | list[int], optional): Placeholder, to be consistent with + other loss. Default: None. + per_image (bool, optional): If per_image is True, compute the loss per + image instead of per batch. Default: False. + class_weight (list[float], optional): Placeholder, to be consistent + with other loss. Default: None. + reduction (str, optional): The method used to reduce the loss. Options + are "none", "mean" and "sum". This parameter only works when + per_image is True. Default: 'mean'. + avg_factor (int, optional): Average factor that is used to average + the loss. This parameter only works when per_image is True. + Default: None. + ignore_index (int | None): The label index to be ignored. Default: 255. + + Returns: + torch.Tensor: The calculated loss. + """ + if per_image: + loss = [ + lovasz_hinge_flat(*flatten_binary_logits( + logit.unsqueeze(0), label.unsqueeze(0), ignore_index)) + for logit, label in zip(logits, labels) + ] + loss = weight_reduce_loss( + torch.stack(loss), None, reduction, avg_factor) + else: + loss = lovasz_hinge_flat( + *flatten_binary_logits(logits, labels, ignore_index)) + return loss + + +def lovasz_softmax_flat(probs, labels, classes='present', class_weight=None): + """Multi-class Lovasz-Softmax loss. + + Args: + probs (torch.Tensor): [P, C], class probabilities at each prediction + (between 0 and 1). + labels (torch.Tensor): [P], ground truth labels (between 0 and C - 1). + classes (str | list[int], optional): Classes chosen to calculate loss. + 'all' for all classes, 'present' for classes present in labels, or + a list of classes to average. Default: 'present'. + class_weight (list[float], optional): The weight for each class. + Default: None. + + Returns: + torch.Tensor: The calculated loss. + """ + if probs.numel() == 0: + # only void pixels, the gradients should be 0 + return probs * 0. + C = probs.size(1) + losses = [] + class_to_sum = list(range(C)) if classes in ['all', 'present'] else classes + for c in class_to_sum: + fg = (labels == c).float() # foreground for class c + if (classes == 'present' and fg.sum() == 0): + continue + if C == 1: + if len(classes) > 1: + raise ValueError('Sigmoid output possible only with 1 class') + class_pred = probs[:, 0] + else: + class_pred = probs[:, c] + errors = (fg - class_pred).abs() + errors_sorted, perm = torch.sort(errors, 0, descending=True) + perm = perm.data + fg_sorted = fg[perm] + loss = torch.dot(errors_sorted, lovasz_grad(fg_sorted)) + if class_weight is not None: + loss *= class_weight[c] + losses.append(loss) + return torch.stack(losses).mean() + + +def lovasz_softmax(probs, + labels, + classes='present', + per_image=False, + class_weight=None, + reduction='mean', + avg_factor=None, + ignore_index=255): + """Multi-class Lovasz-Softmax loss. + + Args: + probs (torch.Tensor): [B, C, H, W], class probabilities at each + prediction (between 0 and 1). + labels (torch.Tensor): [B, H, W], ground truth labels (between 0 and + C - 1). + classes (str | list[int], optional): Classes chosen to calculate loss. + 'all' for all classes, 'present' for classes present in labels, or + a list of classes to average. Default: 'present'. + per_image (bool, optional): If per_image is True, compute the loss per + image instead of per batch. Default: False. + class_weight (list[float], optional): The weight for each class. + Default: None. + reduction (str, optional): The method used to reduce the loss. Options + are "none", "mean" and "sum". This parameter only works when + per_image is True. Default: 'mean'. + avg_factor (int, optional): Average factor that is used to average + the loss. This parameter only works when per_image is True. + Default: None. + ignore_index (int | None): The label index to be ignored. Default: 255. + + Returns: + torch.Tensor: The calculated loss. + """ + + if per_image: + loss = [ + lovasz_softmax_flat( + *flatten_probs( + prob.unsqueeze(0), label.unsqueeze(0), ignore_index), + classes=classes, + class_weight=class_weight) + for prob, label in zip(probs, labels) + ] + loss = weight_reduce_loss( + torch.stack(loss), None, reduction, avg_factor) + else: + loss = lovasz_softmax_flat( + *flatten_probs(probs, labels, ignore_index), + classes=classes, + class_weight=class_weight) + return loss + + +@MODELS.register_module() +class LovaszLoss(nn.Module): + """LovaszLoss. + + This loss is proposed in `The Lovasz-Softmax loss: A tractable surrogate + for the optimization of the intersection-over-union measure in neural + networks `_. + + Args: + loss_type (str, optional): Binary or multi-class loss. + Default: 'multi_class'. Options are "binary" and "multi_class". + classes (str | list[int], optional): Classes chosen to calculate loss. + 'all' for all classes, 'present' for classes present in labels, or + a list of classes to average. Default: 'present'. + per_image (bool, optional): If per_image is True, compute the loss per + image instead of per batch. Default: False. + reduction (str, optional): The method used to reduce the loss. Options + are "none", "mean" and "sum". This parameter only works when + per_image is True. Default: 'mean'. + class_weight (list[float] | str, optional): Weight of each class. If in + str format, read them from a file. Defaults to None. + loss_weight (float, optional): Weight of the loss. Defaults to 1.0. + loss_name (str, optional): Name of the loss item. If you want this loss + item to be included into the backward graph, `loss_` must be the + prefix of the name. Defaults to 'loss_lovasz'. + """ + + def __init__(self, + loss_type='multi_class', + classes='present', + per_image=False, + reduction='mean', + class_weight=None, + loss_weight=1.0, + loss_name='loss_lovasz'): + super().__init__() + assert loss_type in ('binary', 'multi_class'), "loss_type should be \ + 'binary' or 'multi_class'." + + if loss_type == 'binary': + self.cls_criterion = lovasz_hinge + else: + self.cls_criterion = lovasz_softmax + assert classes in ('all', 'present') or is_list_of(classes, int) + if not per_image: + assert reduction == 'none', "reduction should be 'none' when \ + per_image is False." + + self.classes = classes + self.per_image = per_image + self.reduction = reduction + self.loss_weight = loss_weight + self.class_weight = get_class_weight(class_weight) + self._loss_name = loss_name + + def forward(self, + cls_score, + label, + weight=None, + avg_factor=None, + reduction_override=None, + **kwargs): + """Forward function.""" + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + if self.class_weight is not None: + class_weight = cls_score.new_tensor(self.class_weight) + else: + class_weight = None + + # if multi-class loss, transform logits to probs + if self.cls_criterion == lovasz_softmax: + cls_score = F.softmax(cls_score, dim=1) + + loss_cls = self.loss_weight * self.cls_criterion( + cls_score, + label, + self.classes, + self.per_image, + class_weight=class_weight, + reduction=reduction, + avg_factor=avg_factor, + **kwargs) + return loss_cls + + @property + def loss_name(self): + """Loss Name. + + This function must be implemented and will return the name of this + loss function. This name will be used to combine different loss items + by simple sum operation. In addition, if you want this loss item to be + included into the backward graph, `loss_` must be the prefix of the + name. + Returns: + str: The name of this loss item. + """ + return self._loss_name diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/ohem_cross_entropy_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/ohem_cross_entropy_loss.py new file mode 100644 index 0000000..a519b4d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/ohem_cross_entropy_loss.py @@ -0,0 +1,94 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Union + +import torch.nn as nn +import torch.nn.functional as F +from torch import Tensor + +from mmseg.registry import MODELS + + +@MODELS.register_module() +class OhemCrossEntropy(nn.Module): + """OhemCrossEntropy loss. + + This func is modified from + `PIDNet `_. # noqa + + Licensed under the MIT License. + + Args: + ignore_label (int): Labels to ignore when computing the loss. + Default: 255 + thresh (float, optional): The threshold for hard example selection. + Below which, are prediction with low confidence. If not + specified, the hard examples will be pixels of top ``min_kept`` + loss. Default: 0.7. + min_kept (int, optional): The minimum number of predictions to keep. + Default: 100000. + loss_weight (float): Weight of the loss. Defaults to 1.0. + class_weight (list[float] | str, optional): Weight of each class. If in + str format, read them from a file. Defaults to None. + loss_name (str): Name of the loss item. If you want this loss + item to be included into the backward graph, `loss_` must be the + prefix of the name. Defaults to 'loss_boundary'. + """ + + def __init__(self, + ignore_label: int = 255, + thres: float = 0.7, + min_kept: int = 100000, + loss_weight: float = 1.0, + class_weight: Optional[Union[List[float], str]] = None, + loss_name: str = 'loss_ohem'): + super().__init__() + self.thresh = thres + self.min_kept = max(1, min_kept) + self.ignore_label = ignore_label + self.loss_weight = loss_weight + self.loss_name_ = loss_name + self.class_weight = class_weight + + def forward(self, score: Tensor, target: Tensor) -> Tensor: + """Forward function. + Args: + score (Tensor): Predictions of the segmentation head. + target (Tensor): Ground truth of the image. + + Returns: + Tensor: Loss tensor. + """ + # score: (N, C, H, W) + pred = F.softmax(score, dim=1) + if self.class_weight is not None: + class_weight = score.new_tensor(self.class_weight) + else: + class_weight = None + + pixel_losses = F.cross_entropy( + score, + target, + weight=class_weight, + ignore_index=self.ignore_label, + reduction='none').contiguous().view(-1) # (N*H*W) + mask = target.contiguous().view(-1) != self.ignore_label # (N*H*W) + + tmp_target = target.clone() # (N, H, W) + tmp_target[tmp_target == self.ignore_label] = 0 + # pred: (N, C, H, W) -> (N*H*W, C) + pred = pred.gather(1, tmp_target.unsqueeze(1)) + # pred: (N*H*W, C) -> (N*H*W), ind: (N*H*W) + pred, ind = pred.contiguous().view(-1, )[mask].contiguous().sort() + if pred.numel() > 0: + min_value = pred[min(self.min_kept, pred.numel() - 1)] + else: + return score.new_tensor(0.0) + threshold = max(min_value, self.thresh) + + pixel_losses = pixel_losses[mask][ind] + pixel_losses = pixel_losses[pred < threshold] + return self.loss_weight * pixel_losses.mean() + + @property + def loss_name(self): + return self.loss_name_ diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/silog_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/silog_loss.py new file mode 100644 index 0000000..ecc07aa --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/silog_loss.py @@ -0,0 +1,122 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Union + +import torch +import torch.nn as nn +from torch import Tensor + +from mmseg.registry import MODELS +from .utils import weight_reduce_loss + + +def silog_loss(pred: Tensor, + target: Tensor, + weight: Optional[Tensor] = None, + eps: float = 1e-4, + reduction: Union[str, None] = 'mean', + avg_factor: Optional[int] = None) -> Tensor: + """Computes the Scale-Invariant Logarithmic (SI-Log) loss between + prediction and target. + + Args: + pred (Tensor): Predicted output. + target (Tensor): Ground truth. + weight (Optional[Tensor]): Optional weight to apply on the loss. + eps (float): Epsilon value to avoid division and log(0). + reduction (Union[str, None]): Specifies the reduction to apply to the + output: 'mean', 'sum' or None. + avg_factor (Optional[int]): Optional average factor for the loss. + + Returns: + Tensor: The calculated SI-Log loss. + """ + pred, target = pred.flatten(1), target.flatten(1) + valid_mask = (target > eps).detach().float() + + diff_log = torch.log(target.clamp(min=eps)) - torch.log( + pred.clamp(min=eps)) + + valid_mask = (target > eps).detach() & (~torch.isnan(diff_log)) + diff_log[~valid_mask] = 0.0 + valid_mask = valid_mask.float() + + diff_log_sq_mean = (diff_log.pow(2) * valid_mask).sum( + dim=1) / valid_mask.sum(dim=1).clamp(min=eps) + diff_log_mean = (diff_log * valid_mask).sum(dim=1) / valid_mask.sum( + dim=1).clamp(min=eps) + + loss = torch.sqrt(diff_log_sq_mean - 0.5 * diff_log_mean.pow(2)) + + if weight is not None: + weight = weight.float() + + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + + +@MODELS.register_module() +class SiLogLoss(nn.Module): + """Compute SiLog loss. + + Args: + reduction (str, optional): The method used + to reduce the loss. Options are "none", + "mean" and "sum". Defaults to 'mean'. + loss_weight (float, optional): Weight of loss. Defaults to 1.0. + eps (float): Avoid dividing by zero. Defaults to 1e-3. + loss_name (str, optional): Name of the loss item. If you want this + loss item to be included into the backward graph, `loss_` must + be the prefix of the name. Defaults to 'loss_silog'. + """ + + def __init__(self, + reduction='mean', + loss_weight=1.0, + eps=1e-6, + loss_name='loss_silog'): + super().__init__() + self.reduction = reduction + self.loss_weight = loss_weight + self.eps = eps + self._loss_name = loss_name + + def forward( + self, + pred, + target, + weight=None, + avg_factor=None, + reduction_override=None, + ): + + assert pred.shape == target.shape, 'the shapes of pred ' \ + f'({pred.shape}) and target ({target.shape}) are mismatch' + + assert reduction_override in (None, 'none', 'mean', 'sum') + reduction = ( + reduction_override if reduction_override else self.reduction) + + loss = self.loss_weight * silog_loss( + pred, + target, + weight, + eps=self.eps, + reduction=reduction, + avg_factor=avg_factor, + ) + + return loss + + @property + def loss_name(self): + """Loss Name. + + This function must be implemented and will return the name of this + loss function. This name will be used to combine different loss items + by simple sum operation. In addition, if you want this loss item to be + included into the backward graph, `loss_` must be the prefix of the + name. + Returns: + str: The name of this loss item. + """ + return self._loss_name diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/tversky_loss.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/tversky_loss.py new file mode 100644 index 0000000..bfca1af --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/tversky_loss.py @@ -0,0 +1,137 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Modified from +https://github.com/JunMa11/SegLoss/blob/master/losses_pytorch/dice_loss.py#L333 +(Apache-2.0 License)""" +import torch +import torch.nn as nn +import torch.nn.functional as F + +from ..builder import LOSSES +from .utils import get_class_weight, weighted_loss + + +@weighted_loss +def tversky_loss(pred, + target, + valid_mask, + alpha=0.3, + beta=0.7, + smooth=1, + class_weight=None, + ignore_index=255): + assert pred.shape[0] == target.shape[0] + total_loss = 0 + num_classes = pred.shape[1] + for i in range(num_classes): + if i != ignore_index: + tversky_loss = binary_tversky_loss( + pred[:, i], + target[..., i], + valid_mask=valid_mask, + alpha=alpha, + beta=beta, + smooth=smooth) + if class_weight is not None: + tversky_loss *= class_weight[i] + total_loss += tversky_loss + return total_loss / num_classes + + +@weighted_loss +def binary_tversky_loss(pred, + target, + valid_mask, + alpha=0.3, + beta=0.7, + smooth=1): + assert pred.shape[0] == target.shape[0] + pred = pred.reshape(pred.shape[0], -1) + target = target.reshape(target.shape[0], -1) + valid_mask = valid_mask.reshape(valid_mask.shape[0], -1) + + TP = torch.sum(torch.mul(pred, target) * valid_mask, dim=1) + FP = torch.sum(torch.mul(pred, 1 - target) * valid_mask, dim=1) + FN = torch.sum(torch.mul(1 - pred, target) * valid_mask, dim=1) + tversky = (TP + smooth) / (TP + alpha * FP + beta * FN + smooth) + + return 1 - tversky + + +@LOSSES.register_module() +class TverskyLoss(nn.Module): + """TverskyLoss. This loss is proposed in `Tversky loss function for image + segmentation using 3D fully convolutional deep networks. + + `_. + Args: + smooth (float): A float number to smooth loss, and avoid NaN error. + Default: 1. + class_weight (list[float] | str, optional): Weight of each class. If in + str format, read them from a file. Defaults to None. + loss_weight (float, optional): Weight of the loss. Default to 1.0. + ignore_index (int | None): The label index to be ignored. Default: 255. + alpha(float, in [0, 1]): + The coefficient of false positives. Default: 0.3. + beta (float, in [0, 1]): + The coefficient of false negatives. Default: 0.7. + Note: alpha + beta = 1. + loss_name (str, optional): Name of the loss item. If you want this loss + item to be included into the backward graph, `loss_` must be the + prefix of the name. Defaults to 'loss_tversky'. + """ + + def __init__(self, + smooth=1, + class_weight=None, + loss_weight=1.0, + ignore_index=255, + alpha=0.3, + beta=0.7, + loss_name='loss_tversky'): + super().__init__() + self.smooth = smooth + self.class_weight = get_class_weight(class_weight) + self.loss_weight = loss_weight + self.ignore_index = ignore_index + assert (alpha + beta == 1.0), 'Sum of alpha and beta but be 1.0!' + self.alpha = alpha + self.beta = beta + self._loss_name = loss_name + + def forward(self, pred, target, **kwargs): + if self.class_weight is not None: + class_weight = pred.new_tensor(self.class_weight) + else: + class_weight = None + + pred = F.softmax(pred, dim=1) + num_classes = pred.shape[1] + one_hot_target = F.one_hot( + torch.clamp(target.long(), 0, num_classes - 1), + num_classes=num_classes) + valid_mask = (target != self.ignore_index).long() + + loss = self.loss_weight * tversky_loss( + pred, + one_hot_target, + valid_mask=valid_mask, + alpha=self.alpha, + beta=self.beta, + smooth=self.smooth, + class_weight=class_weight, + ignore_index=self.ignore_index) + return loss + + @property + def loss_name(self): + """Loss Name. + + This function must be implemented and will return the name of this + loss function. This name will be used to combine different loss items + by simple sum operation. In addition, if you want this loss item to be + included into the backward graph, `loss_` must be the prefix of the + name. + Returns: + str: The name of this loss item. + """ + return self._loss_name diff --git a/Seg_All_In_One_MMSeg/mmseg/models/losses/utils.py b/Seg_All_In_One_MMSeg/mmseg/models/losses/utils.py new file mode 100644 index 0000000..0478034 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/losses/utils.py @@ -0,0 +1,129 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import functools + +import numpy as np +import torch +import torch.nn.functional as F +from mmengine.fileio import load + + +def get_class_weight(class_weight): + """Get class weight for loss function. + + Args: + class_weight (list[float] | str | None): If class_weight is a str, + take it as a file name and read from it. + """ + if isinstance(class_weight, str): + # take it as a file path + if class_weight.endswith('.npy'): + class_weight = np.load(class_weight) + else: + # pkl, json or yaml + class_weight = load(class_weight) + + return class_weight + + +def reduce_loss(loss, reduction) -> torch.Tensor: + """Reduce loss as specified. + + Args: + loss (Tensor): Elementwise loss tensor. + reduction (str): Options are "none", "mean" and "sum". + + Return: + Tensor: Reduced loss tensor. + """ + reduction_enum = F._Reduction.get_enum(reduction) + # none: 0, elementwise_mean:1, sum: 2 + if reduction_enum == 0: + return loss + elif reduction_enum == 1: + return loss.mean() + elif reduction_enum == 2: + return loss.sum() + + +def weight_reduce_loss(loss, + weight=None, + reduction='mean', + avg_factor=None) -> torch.Tensor: + """Apply element-wise weight and reduce loss. + + Args: + loss (Tensor): Element-wise loss. + weight (Tensor): Element-wise weights. + reduction (str): Same as built-in losses of PyTorch. + avg_factor (float): Average factor when computing the mean of losses. + + Returns: + Tensor: Processed loss values. + """ + # if weight is specified, apply element-wise weight + if weight is not None: + assert weight.dim() == loss.dim() + if weight.dim() > 1: + assert weight.size(1) == 1 or weight.size(1) == loss.size(1) + loss = loss * weight + + # if avg_factor is not specified, just reduce the loss + if avg_factor is None: + loss = reduce_loss(loss, reduction) + else: + # if reduction is mean, then average the loss by avg_factor + if reduction == 'mean': + # Avoid causing ZeroDivisionError when avg_factor is 0.0, + # i.e., all labels of an image belong to ignore index. + eps = torch.finfo(torch.float32).eps + loss = loss.sum() / (avg_factor + eps) + # if reduction is 'none', then do nothing, otherwise raise an error + elif reduction != 'none': + raise ValueError('avg_factor can not be used with reduction="sum"') + return loss + + +def weighted_loss(loss_func): + """Create a weighted version of a given loss function. + + To use this decorator, the loss function must have the signature like + `loss_func(pred, target, **kwargs)`. The function only needs to compute + element-wise loss without any reduction. This decorator will add weight + and reduction arguments to the function. The decorated function will have + the signature like `loss_func(pred, target, weight=None, reduction='mean', + avg_factor=None, **kwargs)`. + + :Example: + + >>> import torch + >>> @weighted_loss + >>> def l1_loss(pred, target): + >>> return (pred - target).abs() + + >>> pred = torch.Tensor([0, 2, 3]) + >>> target = torch.Tensor([1, 1, 1]) + >>> weight = torch.Tensor([1, 0, 1]) + + >>> l1_loss(pred, target) + tensor(1.3333) + >>> l1_loss(pred, target, weight) + tensor(1.) + >>> l1_loss(pred, target, reduction='none') + tensor([1., 1., 2.]) + >>> l1_loss(pred, target, weight, avg_factor=2) + tensor(1.5000) + """ + + @functools.wraps(loss_func) + def wrapper(pred, + target, + weight=None, + reduction='mean', + avg_factor=None, + **kwargs): + # get element-wise loss + loss = loss_func(pred, target, **kwargs) + loss = weight_reduce_loss(loss, weight, reduction, avg_factor) + return loss + + return wrapper diff --git a/Seg_All_In_One_MMSeg/mmseg/models/necks/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/necks/__init__.py new file mode 100644 index 0000000..ff03186 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/necks/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .featurepyramid import Feature2Pyramid +from .fpn import FPN +from .ic_neck import ICNeck +from .jpu import JPU +from .mla_neck import MLANeck +from .multilevel_neck import MultiLevelNeck + +__all__ = [ + 'FPN', 'MultiLevelNeck', 'MLANeck', 'ICNeck', 'JPU', 'Feature2Pyramid' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/necks/featurepyramid.py b/Seg_All_In_One_MMSeg/mmseg/models/necks/featurepyramid.py new file mode 100644 index 0000000..dc1250d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/necks/featurepyramid.py @@ -0,0 +1,67 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import build_norm_layer + +from mmseg.registry import MODELS + + +@MODELS.register_module() +class Feature2Pyramid(nn.Module): + """Feature2Pyramid. + + A neck structure connect ViT backbone and decoder_heads. + + Args: + embed_dims (int): Embedding dimension. + rescales (list[float]): Different sampling multiples were + used to obtain pyramid features. Default: [4, 2, 1, 0.5]. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='SyncBN', requires_grad=True). + """ + + def __init__(self, + embed_dim, + rescales=[4, 2, 1, 0.5], + norm_cfg=dict(type='SyncBN', requires_grad=True)): + super().__init__() + self.rescales = rescales + self.upsample_4x = None + for k in self.rescales: + if k == 4: + self.upsample_4x = nn.Sequential( + nn.ConvTranspose2d( + embed_dim, embed_dim, kernel_size=2, stride=2), + build_norm_layer(norm_cfg, embed_dim)[1], + nn.GELU(), + nn.ConvTranspose2d( + embed_dim, embed_dim, kernel_size=2, stride=2), + ) + elif k == 2: + self.upsample_2x = nn.Sequential( + nn.ConvTranspose2d( + embed_dim, embed_dim, kernel_size=2, stride=2)) + elif k == 1: + self.identity = nn.Identity() + elif k == 0.5: + self.downsample_2x = nn.MaxPool2d(kernel_size=2, stride=2) + elif k == 0.25: + self.downsample_4x = nn.MaxPool2d(kernel_size=4, stride=4) + else: + raise KeyError(f'invalid {k} for feature2pyramid') + + def forward(self, inputs): + assert len(inputs) == len(self.rescales) + outputs = [] + if self.upsample_4x is not None: + ops = [ + self.upsample_4x, self.upsample_2x, self.identity, + self.downsample_2x + ] + else: + ops = [ + self.upsample_2x, self.identity, self.downsample_2x, + self.downsample_4x + ] + for i in range(len(inputs)): + outputs.append(ops[i](inputs[i])) + return tuple(outputs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/necks/fpn.py b/Seg_All_In_One_MMSeg/mmseg/models/necks/fpn.py new file mode 100644 index 0000000..ddab74c --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/necks/fpn.py @@ -0,0 +1,212 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..utils import resize + + +@MODELS.register_module() +class FPN(BaseModule): + """Feature Pyramid Network. + + This neck is the implementation of `Feature Pyramid Networks for Object + Detection `_. + + Args: + in_channels (list[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale). + num_outs (int): Number of output scales. + start_level (int): Index of the start input backbone level used to + build the feature pyramid. Default: 0. + end_level (int): Index of the end input backbone level (exclusive) to + build the feature pyramid. Default: -1, which means the last level. + add_extra_convs (bool | str): If bool, it decides whether to add conv + layers on top of the original feature maps. Default to False. + If True, its actual mode is specified by `extra_convs_on_inputs`. + If str, it specifies the source feature map of the extra convs. + Only the following options are allowed + + - 'on_input': Last feat map of neck inputs (i.e. backbone feature). + - 'on_lateral': Last feature map after lateral convs. + - 'on_output': The last output feature map after fpn convs. + extra_convs_on_inputs (bool, deprecated): Whether to apply extra convs + on the original feature from the backbone. If True, + it is equivalent to `add_extra_convs='on_input'`. If False, it is + equivalent to set `add_extra_convs='on_output'`. Default to True. + relu_before_extra_convs (bool): Whether to apply relu before the extra + conv. Default: False. + no_norm_on_lateral (bool): Whether to apply norm on lateral. + Default: False. + conv_cfg (dict): Config dict for convolution layer. Default: None. + norm_cfg (dict): Config dict for normalization layer. Default: None. + act_cfg (dict): Config dict for activation layer in ConvModule. + Default: None. + upsample_cfg (dict): Config dict for interpolate layer. + Default: dict(mode='nearest'). + init_cfg (dict or list[dict], optional): Initialization config dict. + + Example: + >>> import torch + >>> in_channels = [2, 3, 5, 7] + >>> scales = [340, 170, 84, 43] + >>> inputs = [torch.rand(1, c, s, s) + ... for c, s in zip(in_channels, scales)] + >>> self = FPN(in_channels, 11, len(in_channels)).eval() + >>> outputs = self.forward(inputs) + >>> for i in range(len(outputs)): + ... print(f'outputs[{i}].shape = {outputs[i].shape}') + outputs[0].shape = torch.Size([1, 11, 340, 340]) + outputs[1].shape = torch.Size([1, 11, 170, 170]) + outputs[2].shape = torch.Size([1, 11, 84, 84]) + outputs[3].shape = torch.Size([1, 11, 43, 43]) + """ + + def __init__(self, + in_channels, + out_channels, + num_outs, + start_level=0, + end_level=-1, + add_extra_convs=False, + extra_convs_on_inputs=False, + relu_before_extra_convs=False, + no_norm_on_lateral=False, + conv_cfg=None, + norm_cfg=None, + act_cfg=None, + upsample_cfg=dict(mode='nearest'), + init_cfg=dict( + type='Xavier', layer='Conv2d', distribution='uniform')): + super().__init__(init_cfg) + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.num_ins = len(in_channels) + self.num_outs = num_outs + self.relu_before_extra_convs = relu_before_extra_convs + self.no_norm_on_lateral = no_norm_on_lateral + self.fp16_enabled = False + self.upsample_cfg = upsample_cfg.copy() + + if end_level == -1: + self.backbone_end_level = self.num_ins + assert num_outs >= self.num_ins - start_level + else: + # if end_level < inputs, no extra level is allowed + self.backbone_end_level = end_level + assert end_level <= len(in_channels) + assert num_outs == end_level - start_level + self.start_level = start_level + self.end_level = end_level + self.add_extra_convs = add_extra_convs + assert isinstance(add_extra_convs, (str, bool)) + if isinstance(add_extra_convs, str): + # Extra_convs_source choices: 'on_input', 'on_lateral', 'on_output' + assert add_extra_convs in ('on_input', 'on_lateral', 'on_output') + elif add_extra_convs: # True + if extra_convs_on_inputs: + # For compatibility with previous release + # TODO: deprecate `extra_convs_on_inputs` + self.add_extra_convs = 'on_input' + else: + self.add_extra_convs = 'on_output' + + self.lateral_convs = nn.ModuleList() + self.fpn_convs = nn.ModuleList() + + for i in range(self.start_level, self.backbone_end_level): + l_conv = ConvModule( + in_channels[i], + out_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg if not self.no_norm_on_lateral else None, + act_cfg=act_cfg, + inplace=False) + fpn_conv = ConvModule( + out_channels, + out_channels, + 3, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + inplace=False) + + self.lateral_convs.append(l_conv) + self.fpn_convs.append(fpn_conv) + + # add extra conv layers (e.g., RetinaNet) + extra_levels = num_outs - self.backbone_end_level + self.start_level + if self.add_extra_convs and extra_levels >= 1: + for i in range(extra_levels): + if i == 0 and self.add_extra_convs == 'on_input': + in_channels = self.in_channels[self.backbone_end_level - 1] + else: + in_channels = out_channels + extra_fpn_conv = ConvModule( + in_channels, + out_channels, + 3, + stride=2, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + inplace=False) + self.fpn_convs.append(extra_fpn_conv) + + def forward(self, inputs): + assert len(inputs) == len(self.in_channels) + + # build laterals + laterals = [ + lateral_conv(inputs[i + self.start_level]) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + + # build top-down path + used_backbone_levels = len(laterals) + for i in range(used_backbone_levels - 1, 0, -1): + # In some cases, fixing `scale factor` (e.g. 2) is preferred, but + # it cannot co-exist with `size` in `F.interpolate`. + if 'scale_factor' in self.upsample_cfg: + laterals[i - 1] = laterals[i - 1] + resize( + laterals[i], **self.upsample_cfg) + else: + prev_shape = laterals[i - 1].shape[2:] + laterals[i - 1] = laterals[i - 1] + resize( + laterals[i], size=prev_shape, **self.upsample_cfg) + + # build outputs + # part 1: from original levels + outs = [ + self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels) + ] + # part 2: add extra levels + if self.num_outs > len(outs): + # use max pool to get more levels on top of outputs + # (e.g., Faster R-CNN, Mask R-CNN) + if not self.add_extra_convs: + for i in range(self.num_outs - used_backbone_levels): + outs.append(F.max_pool2d(outs[-1], 1, stride=2)) + # add conv layers on top of original feature maps (RetinaNet) + else: + if self.add_extra_convs == 'on_input': + extra_source = inputs[self.backbone_end_level - 1] + elif self.add_extra_convs == 'on_lateral': + extra_source = laterals[-1] + elif self.add_extra_convs == 'on_output': + extra_source = outs[-1] + else: + raise NotImplementedError + outs.append(self.fpn_convs[used_backbone_levels](extra_source)) + for i in range(used_backbone_levels + 1, self.num_outs): + if self.relu_before_extra_convs: + outs.append(self.fpn_convs[i](F.relu(outs[-1]))) + else: + outs.append(self.fpn_convs[i](outs[-1])) + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/necks/ic_neck.py b/Seg_All_In_One_MMSeg/mmseg/models/necks/ic_neck.py new file mode 100644 index 0000000..9763541 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/necks/ic_neck.py @@ -0,0 +1,148 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..utils import resize + + +class CascadeFeatureFusion(BaseModule): + """Cascade Feature Fusion Unit in ICNet. + + Args: + low_channels (int): The number of input channels for + low resolution feature map. + high_channels (int): The number of input channels for + high resolution feature map. + out_channels (int): The number of output channels. + conv_cfg (dict): Dictionary to construct and config conv layer. + Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN'). + act_cfg (dict): Dictionary to construct and config act layer. + Default: dict(type='ReLU'). + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + + Returns: + x (Tensor): The output tensor of shape (N, out_channels, H, W). + x_low (Tensor): The output tensor of shape (N, out_channels, H, W) + for Cascade Label Guidance in auxiliary heads. + """ + + def __init__(self, + low_channels, + high_channels, + out_channels, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + align_corners=False, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.align_corners = align_corners + self.conv_low = ConvModule( + low_channels, + out_channels, + 3, + padding=2, + dilation=2, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.conv_high = ConvModule( + high_channels, + out_channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, x_low, x_high): + x_low = resize( + x_low, + size=x_high.size()[2:], + mode='bilinear', + align_corners=self.align_corners) + # Note: Different from original paper, `x_low` is underwent + # `self.conv_low` rather than another 1x1 conv classifier + # before being used for auxiliary head. + x_low = self.conv_low(x_low) + x_high = self.conv_high(x_high) + x = x_low + x_high + x = F.relu(x, inplace=True) + return x, x_low + + +@MODELS.register_module() +class ICNeck(BaseModule): + """ICNet for Real-Time Semantic Segmentation on High-Resolution Images. + + This head is the implementation of `ICHead + `_. + + Args: + in_channels (int): The number of input image channels. Default: 3. + out_channels (int): The numbers of output feature channels. + Default: 128. + conv_cfg (dict): Dictionary to construct and config conv layer. + Default: None. + norm_cfg (dict): Dictionary to construct and config norm layer. + Default: dict(type='BN'). + act_cfg (dict): Dictionary to construct and config act layer. + Default: dict(type='ReLU'). + align_corners (bool): align_corners argument of F.interpolate. + Default: False. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels=(64, 256, 256), + out_channels=128, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + align_corners=False, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + assert len(in_channels) == 3, 'Length of input channels \ + must be 3!' + + self.in_channels = in_channels + self.out_channels = out_channels + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.align_corners = align_corners + self.cff_24 = CascadeFeatureFusion( + self.in_channels[2], + self.in_channels[1], + self.out_channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + align_corners=self.align_corners) + + self.cff_12 = CascadeFeatureFusion( + self.out_channels, + self.in_channels[0], + self.out_channels, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + align_corners=self.align_corners) + + def forward(self, inputs): + assert len(inputs) == 3, 'Length of input feature \ + maps must be 3!' + + x_sub1, x_sub2, x_sub4 = inputs + x_cff_24, x_24 = self.cff_24(x_sub4, x_sub2) + x_cff_12, x_12 = self.cff_12(x_cff_24, x_sub1) + # Note: `x_cff_12` is used for decode_head, + # `x_24` and `x_12` are used for auxiliary head. + return x_24, x_12, x_cff_12 diff --git a/Seg_All_In_One_MMSeg/mmseg/models/necks/jpu.py b/Seg_All_In_One_MMSeg/mmseg/models/necks/jpu.py new file mode 100644 index 0000000..3ea0fe2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/necks/jpu.py @@ -0,0 +1,131 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from ..utils import resize + + +@MODELS.register_module() +class JPU(BaseModule): + """FastFCN: Rethinking Dilated Convolution in the Backbone + for Semantic Segmentation. + + This Joint Pyramid Upsampling (JPU) neck is the implementation of + `FastFCN `_. + + Args: + in_channels (Tuple[int], optional): The number of input channels + for each convolution operations before upsampling. + Default: (512, 1024, 2048). + mid_channels (int): The number of output channels of JPU. + Default: 512. + start_level (int): Index of the start input backbone level used to + build the feature pyramid. Default: 0. + end_level (int): Index of the end input backbone level (exclusive) to + build the feature pyramid. Default: -1, which means the last level. + dilations (tuple[int]): Dilation rate of each Depthwise + Separable ConvModule. Default: (1, 2, 4, 8). + align_corners (bool, optional): The align_corners argument of + resize operation. Default: False. + conv_cfg (dict | None): Config of conv layers. + Default: None. + norm_cfg (dict | None): Config of norm layers. + Default: dict(type='BN'). + act_cfg (dict): Config of activation layers. + Default: dict(type='ReLU'). + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + in_channels=(512, 1024, 2048), + mid_channels=512, + start_level=0, + end_level=-1, + dilations=(1, 2, 4, 8), + align_corners=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + assert isinstance(in_channels, tuple) + assert isinstance(dilations, tuple) + self.in_channels = in_channels + self.mid_channels = mid_channels + self.start_level = start_level + self.num_ins = len(in_channels) + if end_level == -1: + self.backbone_end_level = self.num_ins + else: + self.backbone_end_level = end_level + assert end_level <= len(in_channels) + + self.dilations = dilations + self.align_corners = align_corners + + self.conv_layers = nn.ModuleList() + self.dilation_layers = nn.ModuleList() + for i in range(self.start_level, self.backbone_end_level): + conv_layer = nn.Sequential( + ConvModule( + self.in_channels[i], + self.mid_channels, + kernel_size=3, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.conv_layers.append(conv_layer) + for i in range(len(dilations)): + dilation_layer = nn.Sequential( + DepthwiseSeparableConvModule( + in_channels=(self.backbone_end_level - self.start_level) * + self.mid_channels, + out_channels=self.mid_channels, + kernel_size=3, + stride=1, + padding=dilations[i], + dilation=dilations[i], + dw_norm_cfg=norm_cfg, + dw_act_cfg=None, + pw_norm_cfg=norm_cfg, + pw_act_cfg=act_cfg)) + self.dilation_layers.append(dilation_layer) + + def forward(self, inputs): + """Forward function.""" + assert len(inputs) == len(self.in_channels), 'Length of inputs must \ + be the same with self.in_channels!' + + feats = [ + self.conv_layers[i - self.start_level](inputs[i]) + for i in range(self.start_level, self.backbone_end_level) + ] + + h, w = feats[0].shape[2:] + for i in range(1, len(feats)): + feats[i] = resize( + feats[i], + size=(h, w), + mode='bilinear', + align_corners=self.align_corners) + + feat = torch.cat(feats, dim=1) + concat_feat = torch.cat([ + self.dilation_layers[i](feat) for i in range(len(self.dilations)) + ], + dim=1) + + outs = [] + + # Default: outs[2] is the output of JPU for decoder head, outs[1] is + # the feature map from backbone for auxiliary head. Additionally, + # outs[0] can also be used for auxiliary head. + for i in range(self.start_level, self.backbone_end_level - 1): + outs.append(inputs[i]) + outs.append(concat_feat) + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/necks/mla_neck.py b/Seg_All_In_One_MMSeg/mmseg/models/necks/mla_neck.py new file mode 100644 index 0000000..db250ae --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/necks/mla_neck.py @@ -0,0 +1,118 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import ConvModule, build_norm_layer + +from mmseg.registry import MODELS + + +class MLAModule(nn.Module): + + def __init__(self, + in_channels=[1024, 1024, 1024, 1024], + out_channels=256, + norm_cfg=None, + act_cfg=None): + super().__init__() + self.channel_proj = nn.ModuleList() + for i in range(len(in_channels)): + self.channel_proj.append( + ConvModule( + in_channels=in_channels[i], + out_channels=out_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + self.feat_extract = nn.ModuleList() + for i in range(len(in_channels)): + self.feat_extract.append( + ConvModule( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + def forward(self, inputs): + + # feat_list -> [p2, p3, p4, p5] + feat_list = [] + for x, conv in zip(inputs, self.channel_proj): + feat_list.append(conv(x)) + + # feat_list -> [p5, p4, p3, p2] + # mid_list -> [m5, m4, m3, m2] + feat_list = feat_list[::-1] + mid_list = [] + for feat in feat_list: + if len(mid_list) == 0: + mid_list.append(feat) + else: + mid_list.append(mid_list[-1] + feat) + + # mid_list -> [m5, m4, m3, m2] + # out_list -> [o2, o3, o4, o5] + out_list = [] + for mid, conv in zip(mid_list, self.feat_extract): + out_list.append(conv(mid)) + + return tuple(out_list) + + +@MODELS.register_module() +class MLANeck(nn.Module): + """Multi-level Feature Aggregation. + + This neck is `The Multi-level Feature Aggregation construction of + SETR `_. + + + Args: + in_channels (List[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale). + norm_layer (dict): Config dict for input normalization. + Default: norm_layer=dict(type='LN', eps=1e-6, requires_grad=True). + norm_cfg (dict): Config dict for normalization layer. Default: None. + act_cfg (dict): Config dict for activation layer in ConvModule. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + norm_layer=dict(type='LN', eps=1e-6, requires_grad=True), + norm_cfg=None, + act_cfg=None): + super().__init__() + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + + # In order to build general vision transformer backbone, we have to + # move MLA to neck. + self.norm = nn.ModuleList([ + build_norm_layer(norm_layer, in_channels[i])[1] + for i in range(len(in_channels)) + ]) + + self.mla = MLAModule( + in_channels=in_channels, + out_channels=out_channels, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, inputs): + assert len(inputs) == len(self.in_channels) + + # Convert from nchw to nlc + outs = [] + for i in range(len(inputs)): + x = inputs[i] + n, c, h, w = x.shape + x = x.reshape(n, c, h * w).transpose(2, 1).contiguous() + x = self.norm[i](x) + x = x.transpose(1, 2).reshape(n, c, h, w).contiguous() + outs.append(x) + + outs = self.mla(outs) + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/necks/multilevel_neck.py b/Seg_All_In_One_MMSeg/mmseg/models/necks/multilevel_neck.py new file mode 100644 index 0000000..c997125 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/necks/multilevel_neck.py @@ -0,0 +1,79 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model.weight_init import xavier_init + +from mmseg.registry import MODELS +from ..utils import resize + + +@MODELS.register_module() +class MultiLevelNeck(nn.Module): + """MultiLevelNeck. + + A neck structure connect vit backbone and decoder_heads. + + Args: + in_channels (List[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale). + scales (List[float]): Scale factors for each input feature map. + Default: [0.5, 1, 2, 4] + norm_cfg (dict): Config dict for normalization layer. Default: None. + act_cfg (dict): Config dict for activation layer in ConvModule. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + scales=[0.5, 1, 2, 4], + norm_cfg=None, + act_cfg=None): + super().__init__() + assert isinstance(in_channels, list) + self.in_channels = in_channels + self.out_channels = out_channels + self.scales = scales + self.num_outs = len(scales) + self.lateral_convs = nn.ModuleList() + self.convs = nn.ModuleList() + for in_channel in in_channels: + self.lateral_convs.append( + ConvModule( + in_channel, + out_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + for _ in range(self.num_outs): + self.convs.append( + ConvModule( + out_channels, + out_channels, + kernel_size=3, + padding=1, + stride=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + + # default init_weights for conv(msra) and norm in ConvModule + def init_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + xavier_init(m, distribution='uniform') + + def forward(self, inputs): + assert len(inputs) == len(self.in_channels) + inputs = [ + lateral_conv(inputs[i]) + for i, lateral_conv in enumerate(self.lateral_convs) + ] + # for len(inputs) not equal to self.num_outs + if len(inputs) == 1: + inputs = [inputs[0] for _ in range(self.num_outs)] + outs = [] + for i in range(self.num_outs): + x_resize = resize( + inputs[i], scale_factor=self.scales[i], mode='bilinear') + outs.append(self.convs[i](x_resize)) + return tuple(outs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/segmentors/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/__init__.py new file mode 100644 index 0000000..59b012f --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base import BaseSegmentor +from .cascade_encoder_decoder import CascadeEncoderDecoder +from .depth_estimator import DepthEstimator +from .encoder_decoder import EncoderDecoder +from .multimodal_encoder_decoder import MultimodalEncoderDecoder +from .seg_tta import SegTTAModel + +__all__ = [ + 'BaseSegmentor', 'EncoderDecoder', 'CascadeEncoderDecoder', 'SegTTAModel', + 'MultimodalEncoderDecoder', 'DepthEstimator' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/segmentors/base.py b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/base.py new file mode 100644 index 0000000..17a0bb2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/base.py @@ -0,0 +1,200 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod +from typing import List, Tuple + +from mmengine.model import BaseModel +from mmengine.structures import PixelData +from torch import Tensor + +from mmseg.structures import SegDataSample +from mmseg.utils import (ForwardResults, OptConfigType, OptMultiConfig, + OptSampleList, SampleList) +from ..utils import resize + + +class BaseSegmentor(BaseModel, metaclass=ABCMeta): + """Base class for segmentors. + + Args: + data_preprocessor (dict, optional): Model preprocessing config + for processing the input data. it usually includes + ``to_rgb``, ``pad_size_divisor``, ``pad_val``, + ``mean`` and ``std``. Default to None. + init_cfg (dict, optional): the config to control the + initialization. Default to None. + """ + + def __init__(self, + data_preprocessor: OptConfigType = None, + init_cfg: OptMultiConfig = None): + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + + @property + def with_neck(self) -> bool: + """bool: whether the segmentor has neck""" + return hasattr(self, 'neck') and self.neck is not None + + @property + def with_auxiliary_head(self) -> bool: + """bool: whether the segmentor has auxiliary head""" + return hasattr(self, + 'auxiliary_head') and self.auxiliary_head is not None + + @property + def with_decode_head(self) -> bool: + """bool: whether the segmentor has decode head""" + return hasattr(self, 'decode_head') and self.decode_head is not None + + @abstractmethod + def extract_feat(self, inputs: Tensor) -> bool: + """Placeholder for extract features from images.""" + pass + + @abstractmethod + def encode_decode(self, inputs: Tensor, batch_data_samples: SampleList): + """Placeholder for encode images with backbone and decode into a + semantic segmentation map of the same size as input.""" + pass + + def forward(self, + inputs: Tensor, + data_samples: OptSampleList = None, + mode: str = 'tensor') -> ForwardResults: + """The unified entry for a forward process in both training and test. + + The method should accept three modes: "tensor", "predict" and "loss": + + - "tensor": Forward the whole network and return tensor or tuple of + tensor without any post-processing, same as a common nn.Module. + - "predict": Forward and return the predictions, which are fully + processed to a list of :obj:`SegDataSample`. + - "loss": Forward and return a dict of losses according to the given + inputs and data samples. + + Note that this method doesn't handle neither back propagation nor + optimizer updating, which are done in the :meth:`train_step`. + + Args: + inputs (torch.Tensor): The input tensor with shape (N, C, ...) in + general. + data_samples (list[:obj:`SegDataSample`]): The seg data samples. + It usually includes information such as `metainfo` and + `gt_sem_seg`. Default to None. + mode (str): Return what kind of value. Defaults to 'tensor'. + + Returns: + The return type depends on ``mode``. + + - If ``mode="tensor"``, return a tensor or a tuple of tensor. + - If ``mode="predict"``, return a list of :obj:`DetDataSample`. + - If ``mode="loss"``, return a dict of tensor. + """ + if mode == 'loss': + return self.loss(inputs, data_samples) + elif mode == 'predict': + return self.predict(inputs, data_samples) + elif mode == 'tensor': + return self._forward(inputs, data_samples) + else: + raise RuntimeError(f'Invalid mode "{mode}". ' + 'Only supports loss, predict and tensor mode') + + @abstractmethod + def loss(self, inputs: Tensor, data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples.""" + pass + + @abstractmethod + def predict(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing.""" + pass + + @abstractmethod + def _forward(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> Tuple[List[Tensor]]: + """Network forward process. + + Usually includes backbone, neck and head forward without any post- + processing. + """ + pass + + def postprocess_result(self, + seg_logits: Tensor, + data_samples: OptSampleList = None) -> SampleList: + """ Convert results list to `SegDataSample`. + Args: + seg_logits (Tensor): The segmentation results, seg_logits from + model of each input image. + data_samples (list[:obj:`SegDataSample`]): The seg data samples. + It usually includes information such as `metainfo` and + `gt_sem_seg`. Default to None. + Returns: + list[:obj:`SegDataSample`]: Segmentation results of the + input images. Each SegDataSample usually contain: + + - ``pred_sem_seg``(PixelData): Prediction of semantic segmentation. + - ``seg_logits``(PixelData): Predicted logits of semantic + segmentation before normalization. + """ + batch_size, C, H, W = seg_logits.shape + + if data_samples is None: + data_samples = [SegDataSample() for _ in range(batch_size)] + only_prediction = True + else: + only_prediction = False + + for i in range(batch_size): + if not only_prediction: + img_meta = data_samples[i].metainfo + # remove padding area + if 'img_padding_size' not in img_meta: + padding_size = img_meta.get('padding_size', [0] * 4) + else: + padding_size = img_meta['img_padding_size'] + padding_left, padding_right, padding_top, padding_bottom =\ + padding_size + # i_seg_logits shape is 1, C, H, W after remove padding + i_seg_logits = seg_logits[i:i + 1, :, + padding_top:H - padding_bottom, + padding_left:W - padding_right] + + flip = img_meta.get('flip', None) + if flip: + flip_direction = img_meta.get('flip_direction', None) + assert flip_direction in ['horizontal', 'vertical'] + if flip_direction == 'horizontal': + i_seg_logits = i_seg_logits.flip(dims=(3, )) + else: + i_seg_logits = i_seg_logits.flip(dims=(2, )) + + # resize as original shape + i_seg_logits = resize( + i_seg_logits, + size=img_meta['ori_shape'], + mode='bilinear', + align_corners=self.align_corners, + warning=False).squeeze(0) + else: + i_seg_logits = seg_logits[i] + + if C > 1: + i_seg_pred = i_seg_logits.argmax(dim=0, keepdim=True) + else: + i_seg_logits = i_seg_logits.sigmoid() + i_seg_pred = (i_seg_logits > + self.decode_head.threshold).to(i_seg_logits) + data_samples[i].set_data({ + 'seg_logits': + PixelData(**{'data': i_seg_logits}), + 'pred_sem_seg': + PixelData(**{'data': i_seg_pred}) + }) + + return data_samples diff --git a/Seg_All_In_One_MMSeg/mmseg/models/segmentors/cascade_encoder_decoder.py b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/cascade_encoder_decoder.py new file mode 100644 index 0000000..0184a35 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/cascade_encoder_decoder.py @@ -0,0 +1,138 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional + +from torch import Tensor, nn + +from mmseg.registry import MODELS +from mmseg.utils import (ConfigType, OptConfigType, OptMultiConfig, + OptSampleList, SampleList, add_prefix) +from .encoder_decoder import EncoderDecoder + + +@MODELS.register_module() +class CascadeEncoderDecoder(EncoderDecoder): + """Cascade Encoder Decoder segmentors. + + CascadeEncoderDecoder almost the same as EncoderDecoder, while decoders of + CascadeEncoderDecoder are cascaded. The output of previous decoder_head + will be the input of next decoder_head. + + Args: + + num_stages (int): How many stages will be cascaded. + backbone (ConfigType): The config for the backnone of segmentor. + decode_head (ConfigType): The config for the decode head of segmentor. + neck (OptConfigType): The config for the neck of segmentor. + Defaults to None. + auxiliary_head (OptConfigType): The config for the auxiliary head of + segmentor. Defaults to None. + train_cfg (OptConfigType): The config for training. Defaults to None. + test_cfg (OptConfigType): The config for testing. Defaults to None. + data_preprocessor (dict, optional): The pre-process config of + :class:`BaseDataPreprocessor`. + pretrained (str, optional): The path for pretrained model. + Defaults to None. + init_cfg (dict, optional): The weight initialized config for + :class:`BaseModule`. + """ + + def __init__(self, + num_stages: int, + backbone: ConfigType, + decode_head: ConfigType, + neck: OptConfigType = None, + auxiliary_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + pretrained: Optional[str] = None, + init_cfg: OptMultiConfig = None): + self.num_stages = num_stages + super().__init__( + backbone=backbone, + decode_head=decode_head, + neck=neck, + auxiliary_head=auxiliary_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + pretrained=pretrained, + init_cfg=init_cfg) + + def _init_decode_head(self, decode_head: ConfigType) -> None: + """Initialize ``decode_head``""" + assert isinstance(decode_head, list) + assert len(decode_head) == self.num_stages + self.decode_head = nn.ModuleList() + for i in range(self.num_stages): + self.decode_head.append(MODELS.build(decode_head[i])) + self.align_corners = self.decode_head[-1].align_corners + self.num_classes = self.decode_head[-1].num_classes + self.out_channels = self.decode_head[-1].out_channels + + def encode_decode(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Encode images with backbone and decode into a semantic segmentation + map of the same size as input.""" + x = self.extract_feat(inputs) + out = self.decode_head[0].forward(x) + for i in range(1, self.num_stages - 1): + out = self.decode_head[i].forward(x, out) + seg_logits_list = self.decode_head[-1].predict(x, out, batch_img_metas, + self.test_cfg) + + return seg_logits_list + + def _decode_head_forward_train(self, inputs: Tensor, + data_samples: SampleList) -> dict: + """Run forward function and calculate loss for decode head in + training.""" + losses = dict() + + loss_decode = self.decode_head[0].loss(inputs, data_samples, + self.train_cfg) + + losses.update(add_prefix(loss_decode, 'decode_0')) + # get batch_img_metas + batch_size = len(data_samples) + batch_img_metas = [] + for batch_index in range(batch_size): + metainfo = data_samples[batch_index].metainfo + batch_img_metas.append(metainfo) + + for i in range(1, self.num_stages): + # forward test again, maybe unnecessary for most methods. + if i == 1: + prev_outputs = self.decode_head[0].forward(inputs) + else: + prev_outputs = self.decode_head[i - 1].forward( + inputs, prev_outputs) + loss_decode = self.decode_head[i].loss(inputs, prev_outputs, + data_samples, + self.train_cfg) + losses.update(add_prefix(loss_decode, f'decode_{i}')) + + return losses + + def _forward(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> Tensor: + """Network forward process. + + Args: + inputs (Tensor): Inputs with shape (N, C, H, W). + data_samples (List[:obj:`SegDataSample`]): The seg data samples. + It usually includes information such as `metainfo` and + `gt_semantic_seg`. + + Returns: + Tensor: Forward output of model without any post-processes. + """ + x = self.extract_feat(inputs) + + out = self.decode_head[0].forward(x) + for i in range(1, self.num_stages): + # TODO support PointRend tensor mode + out = self.decode_head[i].forward(x, out) + + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/segmentors/depth_estimator.py b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/depth_estimator.py new file mode 100644 index 0000000..1020637 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/depth_estimator.py @@ -0,0 +1,392 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging +from typing import List, Optional + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmengine.logging import print_log +from mmengine.structures import PixelData +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample +from mmseg.utils import (ConfigType, OptConfigType, OptMultiConfig, + OptSampleList, SampleList, add_prefix) +from ..utils import resize +from .encoder_decoder import EncoderDecoder + + +@MODELS.register_module() +class DepthEstimator(EncoderDecoder): + """Encoder Decoder depth estimator. + + EncoderDecoder typically consists of backbone, decode_head, auxiliary_head. + Note that auxiliary_head is only used for deep supervision during training, + which could be dumped during inference. + + 1. The ``loss`` method is used to calculate the loss of model, + which includes two steps: (1) Extracts features to obtain the feature maps + (2) Call the decode head loss function to forward decode head model and + calculate losses. + + .. code:: text + + loss(): extract_feat() -> _decode_head_forward_train() -> _auxiliary_head_forward_train (optional) + _decode_head_forward_train(): decode_head.loss() + _auxiliary_head_forward_train(): auxiliary_head.loss (optional) + + 2. The ``predict`` method is used to predict depth estimation results, + which includes two steps: (1) Run inference function to obtain the list of + depth (2) Call post-processing function to obtain list of + ``SegDataSample`` including ``pred_depth_map``. + + .. code:: text + + predict(): inference() -> postprocess_result() + inference(): whole_inference()/slide_inference() + whole_inference()/slide_inference(): encoder_decoder() + encoder_decoder(): extract_feat() -> decode_head.predict() + + 3. The ``_forward`` method is used to output the tensor by running the model, + which includes two steps: (1) Extracts features to obtain the feature maps + (2)Call the decode head forward function to forward decode head model. + + .. code:: text + + _forward(): extract_feat() -> _decode_head.forward() + + Args: + + backbone (ConfigType): The config for the backnone of depth estimator. + decode_head (ConfigType): The config for the decode head of depth estimator. + neck (OptConfigType): The config for the neck of depth estimator. + Defaults to None. + auxiliary_head (OptConfigType): The config for the auxiliary head of + depth estimator. Defaults to None. + train_cfg (OptConfigType): The config for training. Defaults to None. + test_cfg (OptConfigType): The config for testing. Defaults to None. + data_preprocessor (dict, optional): The pre-process config of + :class:`BaseDataPreprocessor`. + pretrained (str, optional): The path for pretrained model. + Defaults to None. + init_cfg (dict, optional): The weight initialized config for + :class:`BaseModule`. + """ # noqa: E501 + + def __init__(self, + backbone: ConfigType, + decode_head: ConfigType, + neck: OptConfigType = None, + auxiliary_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + pretrained: Optional[str] = None, + init_cfg: OptMultiConfig = None): + super().__init__( + backbone=backbone, + decode_head=decode_head, + neck=neck, + auxiliary_head=auxiliary_head, + train_cfg=train_cfg, + test_cfg=test_cfg, + data_preprocessor=data_preprocessor, + pretrained=pretrained, + init_cfg=init_cfg) + + def extract_feat(self, + inputs: Tensor, + batch_img_metas: Optional[List[dict]] = None) -> Tensor: + """Extract features from images.""" + + if getattr(self.backbone, 'class_embed_select', False) and \ + isinstance(batch_img_metas, list) and \ + 'category_id' in batch_img_metas[0]: + cat_ids = [meta['category_id'] for meta in batch_img_metas] + cat_ids = torch.tensor(cat_ids).to(inputs.device) + inputs = (inputs, cat_ids) + + x = self.backbone(inputs) + if self.with_neck: + x = self.neck(x) + return x + + def encode_decode(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Encode images with backbone and decode into a depth map of the same + size as input.""" + x = self.extract_feat(inputs, batch_img_metas) + depth = self.decode_head.predict(x, batch_img_metas, self.test_cfg) + + return depth + + def _decode_head_forward_train(self, inputs: List[Tensor], + data_samples: SampleList) -> dict: + """Run forward function and calculate loss for decode head in + training.""" + losses = dict() + loss_decode = self.decode_head.loss(inputs, data_samples, + self.train_cfg) + + losses.update(add_prefix(loss_decode, 'decode')) + return losses + + def _auxiliary_head_forward_train(self, inputs: List[Tensor], + data_samples: SampleList) -> dict: + """Run forward function and calculate loss for auxiliary head in + training.""" + losses = dict() + if isinstance(self.auxiliary_head, nn.ModuleList): + for idx, aux_head in enumerate(self.auxiliary_head): + loss_aux = aux_head.loss(inputs, data_samples, self.train_cfg) + losses.update(add_prefix(loss_aux, f'aux_{idx}')) + else: + loss_aux = self.auxiliary_head.loss(inputs, data_samples, + self.train_cfg) + losses.update(add_prefix(loss_aux, 'aux')) + + return losses + + def loss(self, inputs: Tensor, data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + inputs (Tensor): Input images. + data_samples (list[:obj:`SegDataSample`]): The seg data samples. + It usually includes information such as `metainfo` and + `gt_depth_map`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + if data_samples is not None: + batch_img_metas = [ + data_sample.metainfo for data_sample in data_samples + ] + else: + batch_img_metas = [ + dict( + ori_shape=inputs.shape[2:], + img_shape=inputs.shape[2:], + pad_shape=inputs.shape[2:], + padding_size=[0, 0, 0, 0]) + ] * inputs.shape[0] + + x = self.extract_feat(inputs, batch_img_metas) + + losses = dict() + + loss_decode = self._decode_head_forward_train(x, data_samples) + losses.update(loss_decode) + + if self.with_auxiliary_head: + loss_aux = self._auxiliary_head_forward_train(x, data_samples) + losses.update(loss_aux) + + return losses + + def predict(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + inputs (Tensor): Inputs with shape (N, C, H, W). + data_samples (List[:obj:`SegDataSample`], optional): The seg data + samples. It usually includes information such as `metainfo` + and `gt_depth_map`. + + Returns: + list[:obj:`SegDataSample`]: Depth estimation results of the + input images. Each SegDataSample usually contain: + + - ``pred_depth_max``(PixelData): Prediction of depth estimation. + """ + if data_samples is not None: + batch_img_metas = [ + data_sample.metainfo for data_sample in data_samples + ] + else: + batch_img_metas = [ + dict( + ori_shape=inputs.shape[2:], + img_shape=inputs.shape[2:], + pad_shape=inputs.shape[2:], + padding_size=[0, 0, 0, 0]) + ] * inputs.shape[0] + + depth = self.inference(inputs, batch_img_metas) + + return self.postprocess_result(depth, data_samples) + + def _forward(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> Tensor: + """Network forward process. + + Args: + inputs (Tensor): Inputs with shape (N, C, H, W). + data_samples (List[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `metainfo` and `gt_depth_map`. + + Returns: + Tensor: Forward output of model without any post-processes. + """ + x = self.extract_feat(inputs) + return self.decode_head.forward(x) + + def slide_flip_inference(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Inference by sliding-window with overlap and flip. + + If h_crop > h_img or w_crop > w_img, the small patch will be used to + decode without padding. + + Args: + inputs (tensor): the tensor should have a shape NxCxHxW, + which contains all images in the batch. + batch_img_metas (List[dict]): List of image metainfo where each may + also contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', and 'pad_shape'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + + Returns: + Tensor: The depth estimation results. + """ + + h_stride, w_stride = self.test_cfg.stride + h_crop, w_crop = self.test_cfg.crop_size + batch_size, _, h_img, w_img = inputs.size() + out_channels = self.out_channels + h_grids = max(h_img - h_crop + h_stride - 1, 0) // h_stride + 1 + w_grids = max(w_img - w_crop + w_stride - 1, 0) // w_stride + 1 + preds = inputs.new_zeros((batch_size, out_channels, h_img, w_img)) + count_mat = inputs.new_zeros((batch_size, 1, h_img, w_img)) + for h_idx in range(h_grids): + for w_idx in range(w_grids): + y1 = h_idx * h_stride + x1 = w_idx * w_stride + y2 = min(y1 + h_crop, h_img) + x2 = min(x1 + w_crop, w_img) + y1 = max(y2 - h_crop, 0) + x1 = max(x2 - w_crop, 0) + crop_img = inputs[:, :, y1:y2, x1:x2] + # change the image shape to patch shape + batch_img_metas[0]['img_shape'] = crop_img.shape[2:] + # the output of encode_decode is depth tensor map + # with shape [N, C, H, W] + crop_depth_map = self.encode_decode(crop_img, batch_img_metas) + + # average out the original and flipped prediction + crop_depth_map_flip = self.encode_decode( + crop_img.flip(dims=(3, )), batch_img_metas) + crop_depth_map_flip = crop_depth_map_flip.flip(dims=(3, )) + crop_depth_map = (crop_depth_map + crop_depth_map_flip) / 2.0 + + preds += F.pad(crop_depth_map, + (int(x1), int(preds.shape[3] - x2), int(y1), + int(preds.shape[2] - y2))) + + count_mat[:, :, y1:y2, x1:x2] += 1 + assert (count_mat == 0).sum() == 0 + depth = preds / count_mat + + return depth + + def inference(self, inputs: Tensor, batch_img_metas: List[dict]) -> Tensor: + """Inference with slide/whole style. + + Args: + inputs (Tensor): The input image of shape (N, 3, H, W). + batch_img_metas (List[dict]): List of image metainfo where each may + also contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', 'pad_shape', and 'padding_size'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + + Returns: + Tensor: The depth estimation results. + """ + assert self.test_cfg.get('mode', 'whole') in ['slide', 'whole', + 'slide_flip'], \ + f'Only "slide", "slide_flip" or "whole" test mode are ' \ + f'supported, but got {self.test_cfg["mode"]}.' + ori_shape = batch_img_metas[0]['ori_shape'] + if not all(_['ori_shape'] == ori_shape for _ in batch_img_metas): + print_log( + 'Image shapes are different in the batch.', + logger='current', + level=logging.WARN) + if self.test_cfg.mode == 'slide': + depth_map = self.slide_inference(inputs, batch_img_metas) + if self.test_cfg.mode == 'slide_flip': + depth_map = self.slide_flip_inference(inputs, batch_img_metas) + else: + depth_map = self.whole_inference(inputs, batch_img_metas) + + return depth_map + + def postprocess_result(self, + depth: Tensor, + data_samples: OptSampleList = None) -> SampleList: + """ Convert results list to `SegDataSample`. + Args: + depth (Tensor): The depth estimation results. + data_samples (list[:obj:`SegDataSample`]): The seg data samples. + It usually includes information such as `metainfo` and + `gt_depth_map`. Default to None. + Returns: + list[:obj:`SegDataSample`]: Depth estomation results of the + input images. Each SegDataSample usually contain: + + - ``pred_depth_map``(PixelData): Prediction of depth estimation. + """ + batch_size, C, H, W = depth.shape + + if data_samples is None: + data_samples = [SegDataSample() for _ in range(batch_size)] + only_prediction = True + else: + only_prediction = False + + for i in range(batch_size): + if not only_prediction: + img_meta = data_samples[i].metainfo + # remove padding area + if 'img_padding_size' not in img_meta: + padding_size = img_meta.get('padding_size', [0] * 4) + else: + padding_size = img_meta['img_padding_size'] + padding_left, padding_right, padding_top, padding_bottom =\ + padding_size + # i_depth shape is 1, C, H, W after remove padding + i_depth = depth[i:i + 1, :, padding_top:H - padding_bottom, + padding_left:W - padding_right] + + flip = img_meta.get('flip', None) + if flip: + flip_direction = img_meta.get('flip_direction', None) + assert flip_direction in ['horizontal', 'vertical'] + if flip_direction == 'horizontal': + i_depth = i_depth.flip(dims=(3, )) + else: + i_depth = i_depth.flip(dims=(2, )) + + # resize as original shape + i_depth = resize( + i_depth, + size=img_meta['ori_shape'], + mode='bilinear', + align_corners=self.align_corners, + warning=False).squeeze(0) + else: + i_depth = depth[i] + + data_samples[i].set_data( + {'pred_depth_map': PixelData(**{'data': i_depth})}) + + return data_samples diff --git a/Seg_All_In_One_MMSeg/mmseg/models/segmentors/encoder_decoder.py b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/encoder_decoder.py new file mode 100644 index 0000000..fa4050e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/encoder_decoder.py @@ -0,0 +1,364 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import logging +from typing import List, Optional + +import torch.nn as nn +import torch.nn.functional as F +from mmengine.logging import print_log +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.utils import (ConfigType, OptConfigType, OptMultiConfig, + OptSampleList, SampleList, add_prefix) +from .base import BaseSegmentor + + +@MODELS.register_module() +class EncoderDecoder(BaseSegmentor): + """Encoder Decoder segmentors. + + EncoderDecoder typically consists of backbone, decode_head, auxiliary_head. + Note that auxiliary_head is only used for deep supervision during training, + which could be dumped during inference. + + 1. The ``loss`` method is used to calculate the loss of model, + which includes two steps: (1) Extracts features to obtain the feature maps + (2) Call the decode head loss function to forward decode head model and + calculate losses. + + .. code:: text + + loss(): extract_feat() -> _decode_head_forward_train() -> _auxiliary_head_forward_train (optional) + _decode_head_forward_train(): decode_head.loss() + _auxiliary_head_forward_train(): auxiliary_head.loss (optional) + + 2. The ``predict`` method is used to predict segmentation results, + which includes two steps: (1) Run inference function to obtain the list of + seg_logits (2) Call post-processing function to obtain list of + ``SegDataSample`` including ``pred_sem_seg`` and ``seg_logits``. + + .. code:: text + + predict(): inference() -> postprocess_result() + infercen(): whole_inference()/slide_inference() + whole_inference()/slide_inference(): encoder_decoder() + encoder_decoder(): extract_feat() -> decode_head.predict() + + 3. The ``_forward`` method is used to output the tensor by running the model, + which includes two steps: (1) Extracts features to obtain the feature maps + (2)Call the decode head forward function to forward decode head model. + + .. code:: text + + _forward(): extract_feat() -> _decode_head.forward() + + Args: + + backbone (ConfigType): The config for the backnone of segmentor. + decode_head (ConfigType): The config for the decode head of segmentor. + neck (OptConfigType): The config for the neck of segmentor. + Defaults to None. + auxiliary_head (OptConfigType): The config for the auxiliary head of + segmentor. Defaults to None. + train_cfg (OptConfigType): The config for training. Defaults to None. + test_cfg (OptConfigType): The config for testing. Defaults to None. + data_preprocessor (dict, optional): The pre-process config of + :class:`BaseDataPreprocessor`. + pretrained (str, optional): The path for pretrained model. + Defaults to None. + init_cfg (dict, optional): The weight initialized config for + :class:`BaseModule`. + """ # noqa: E501 + + def __init__(self, + backbone: ConfigType, + decode_head: ConfigType, + neck: OptConfigType = None, + auxiliary_head: OptConfigType = None, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + pretrained: Optional[str] = None, + init_cfg: OptMultiConfig = None): + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + if pretrained is not None: + assert backbone.get('pretrained') is None, \ + 'both backbone and segmentor set pretrained weight' + backbone.pretrained = pretrained + self.backbone = MODELS.build(backbone) + if neck is not None: + self.neck = MODELS.build(neck) + self._init_decode_head(decode_head) + self._init_auxiliary_head(auxiliary_head) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + assert self.with_decode_head + + def _init_decode_head(self, decode_head: ConfigType) -> None: + """Initialize ``decode_head``""" + self.decode_head = MODELS.build(decode_head) + self.align_corners = self.decode_head.align_corners + self.num_classes = self.decode_head.num_classes + self.out_channels = self.decode_head.out_channels + + def _init_auxiliary_head(self, auxiliary_head: ConfigType) -> None: + """Initialize ``auxiliary_head``""" + if auxiliary_head is not None: + if isinstance(auxiliary_head, list): + self.auxiliary_head = nn.ModuleList() + for head_cfg in auxiliary_head: + self.auxiliary_head.append(MODELS.build(head_cfg)) + else: + self.auxiliary_head = MODELS.build(auxiliary_head) + + def extract_feat(self, inputs: Tensor) -> List[Tensor]: + """Extract features from images.""" + x = self.backbone(inputs) + if self.with_neck: + x = self.neck(x) + return x + + def encode_decode(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Encode images with backbone and decode into a semantic segmentation + map of the same size as input.""" + x = self.extract_feat(inputs) + seg_logits = self.decode_head.predict(x, batch_img_metas, + self.test_cfg) + + return seg_logits + + def _decode_head_forward_train(self, inputs: List[Tensor], + data_samples: SampleList) -> dict: + """Run forward function and calculate loss for decode head in + training.""" + losses = dict() + loss_decode = self.decode_head.loss(inputs, data_samples, + self.train_cfg) + + losses.update(add_prefix(loss_decode, 'decode')) + return losses + + def _auxiliary_head_forward_train(self, inputs: List[Tensor], + data_samples: SampleList) -> dict: + """Run forward function and calculate loss for auxiliary head in + training.""" + losses = dict() + if isinstance(self.auxiliary_head, nn.ModuleList): + for idx, aux_head in enumerate(self.auxiliary_head): + loss_aux = aux_head.loss(inputs, data_samples, self.train_cfg) + losses.update(add_prefix(loss_aux, f'aux_{idx}')) + else: + loss_aux = self.auxiliary_head.loss(inputs, data_samples, + self.train_cfg) + losses.update(add_prefix(loss_aux, 'aux')) + + return losses + + def loss(self, inputs: Tensor, data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + inputs (Tensor): Input images. + data_samples (list[:obj:`SegDataSample`]): The seg data samples. + It usually includes information such as `metainfo` and + `gt_sem_seg`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + + x = self.extract_feat(inputs) + + losses = dict() + + loss_decode = self._decode_head_forward_train(x, data_samples) + losses.update(loss_decode) + + if self.with_auxiliary_head: + loss_aux = self._auxiliary_head_forward_train(x, data_samples) + losses.update(loss_aux) + + return losses + + def predict(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + inputs (Tensor): Inputs with shape (N, C, H, W). + data_samples (List[:obj:`SegDataSample`], optional): The seg data + samples. It usually includes information such as `metainfo` + and `gt_sem_seg`. + + Returns: + list[:obj:`SegDataSample`]: Segmentation results of the + input images. Each SegDataSample usually contain: + + - ``pred_sem_seg``(PixelData): Prediction of semantic segmentation. + - ``seg_logits``(PixelData): Predicted logits of semantic + segmentation before normalization. + """ + if data_samples is not None: + batch_img_metas = [ + data_sample.metainfo for data_sample in data_samples + ] + else: + batch_img_metas = [ + dict( + ori_shape=inputs.shape[2:], + img_shape=inputs.shape[2:], + pad_shape=inputs.shape[2:], + padding_size=[0, 0, 0, 0]) + ] * inputs.shape[0] + + seg_logits = self.inference(inputs, batch_img_metas) + + return self.postprocess_result(seg_logits, data_samples) + + def _forward(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> Tensor: + """Network forward process. + + Args: + inputs (Tensor): Inputs with shape (N, C, H, W). + data_samples (List[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `metainfo` and `gt_sem_seg`. + + Returns: + Tensor: Forward output of model without any post-processes. + """ + x = self.extract_feat(inputs) + return self.decode_head.forward(x) + + def slide_inference(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Inference by sliding-window with overlap. + + If h_crop > h_img or w_crop > w_img, the small patch will be used to + decode without padding. + + Args: + inputs (tensor): the tensor should have a shape NxCxHxW, + which contains all images in the batch. + batch_img_metas (List[dict]): List of image metainfo where each may + also contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', and 'pad_shape'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + + Returns: + Tensor: The segmentation results, seg_logits from model of each + input image. + """ + + h_stride, w_stride = self.test_cfg.stride + h_crop, w_crop = self.test_cfg.crop_size + batch_size, _, h_img, w_img = inputs.size() + out_channels = self.out_channels + h_grids = max(h_img - h_crop + h_stride - 1, 0) // h_stride + 1 + w_grids = max(w_img - w_crop + w_stride - 1, 0) // w_stride + 1 + preds = inputs.new_zeros((batch_size, out_channels, h_img, w_img)) + count_mat = inputs.new_zeros((batch_size, 1, h_img, w_img)) + for h_idx in range(h_grids): + for w_idx in range(w_grids): + y1 = h_idx * h_stride + x1 = w_idx * w_stride + y2 = min(y1 + h_crop, h_img) + x2 = min(x1 + w_crop, w_img) + y1 = max(y2 - h_crop, 0) + x1 = max(x2 - w_crop, 0) + crop_img = inputs[:, :, y1:y2, x1:x2] + # change the image shape to patch shape + batch_img_metas[0]['img_shape'] = crop_img.shape[2:] + # the output of encode_decode is seg logits tensor map + # with shape [N, C, H, W] + crop_seg_logit = self.encode_decode(crop_img, batch_img_metas) + preds += F.pad(crop_seg_logit, + (int(x1), int(preds.shape[3] - x2), int(y1), + int(preds.shape[2] - y2))) + + count_mat[:, :, y1:y2, x1:x2] += 1 + assert (count_mat == 0).sum() == 0 + seg_logits = preds / count_mat + + return seg_logits + + def whole_inference(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Inference with full image. + + Args: + inputs (Tensor): The tensor should have a shape NxCxHxW, which + contains all images in the batch. + batch_img_metas (List[dict]): List of image metainfo where each may + also contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', and 'pad_shape'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + + Returns: + Tensor: The segmentation results, seg_logits from model of each + input image. + """ + + seg_logits = self.encode_decode(inputs, batch_img_metas) + + return seg_logits + + def inference(self, inputs: Tensor, batch_img_metas: List[dict]) -> Tensor: + """Inference with slide/whole style. + + Args: + inputs (Tensor): The input image of shape (N, 3, H, W). + batch_img_metas (List[dict]): List of image metainfo where each may + also contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', 'pad_shape', and 'padding_size'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + + Returns: + Tensor: The segmentation results, seg_logits from model of each + input image. + """ + assert self.test_cfg.get('mode', 'whole') in ['slide', 'whole'], \ + f'Only "slide" or "whole" test mode are supported, but got ' \ + f'{self.test_cfg["mode"]}.' + ori_shape = batch_img_metas[0]['ori_shape'] + if not all(_['ori_shape'] == ori_shape for _ in batch_img_metas): + print_log( + 'Image shapes are different in the batch.', + logger='current', + level=logging.WARN) + if self.test_cfg.mode == 'slide': + seg_logit = self.slide_inference(inputs, batch_img_metas) + else: + seg_logit = self.whole_inference(inputs, batch_img_metas) + + return seg_logit + + def aug_test(self, inputs, batch_img_metas, rescale=True): + """Test with augmentations. + + Only rescale=True is supported. + """ + # aug_test rescale all imgs back to ori_shape for now + assert rescale + # to save memory, we get augmented seg logit inplace + seg_logit = self.inference(inputs[0], batch_img_metas[0], rescale) + for i in range(1, len(inputs)): + cur_seg_logit = self.inference(inputs[i], batch_img_metas[i], + rescale) + seg_logit += cur_seg_logit + seg_logit /= len(inputs) + seg_pred = seg_logit.argmax(dim=1) + # unravel batch dim + seg_pred = list(seg_pred) + return seg_pred diff --git a/Seg_All_In_One_MMSeg/mmseg/models/segmentors/multimodal_encoder_decoder.py b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/multimodal_encoder_decoder.py new file mode 100644 index 0000000..75aa8b9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/multimodal_encoder_decoder.py @@ -0,0 +1,350 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional + +import torch.nn.functional as F +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.utils import (ConfigType, OptConfigType, OptMultiConfig, + OptSampleList, SampleList, add_prefix) +from .base import BaseSegmentor + + +@MODELS.register_module() +class MultimodalEncoderDecoder(BaseSegmentor): + """Multimodal Encoder-Decoder segmentors. + + Multimodal segmentation architecture is used for open-vocabulary + semantic segmentation with combining the visual and language + pretrain models. It consists of a image_encoder (backbone) to extract + visual feature, a text encoder to extract text feature, and a decode + head to generate semantic maps. + Note that the deep supervision during training is implemented in decode head. + + 1. The ``loss`` method is used to calculate the loss of model, + which includes two steps: (1) Extracts features to obtain the feature maps + (2) Call the decode head loss function to forward decode head model and + calculate losses. + + .. code:: text + + loss(): extract_feat() -> _decode_head_forward_train() + _decode_head_forward_train(): decode_head.loss() + + 2. The ``predict`` method is used to predict segmentation results, + which includes two steps: (1) Run inference function to obtain the list of + seg_logits (2) Call post-processing function to obtain list of + ``SegDataSampel`` including ``pred_sem_seg`` and ``seg_logits``. + + .. code:: text + + predict(): inference() -> postprocess_result() + inference(): whole_inference()/slide_inference() + whole_inference()/slide_inference(): encoder_decoder() + encoder_decoder(): extract_feat() -> decode_head.predict() + + 3. The ``_forward`` method is used to output the tensor by running the model, + which includes two steps: (1) Extracts features to obtain the feature maps + (2)Call the decode head forward function to forward decode head model. + + .. code:: text + + _forward(): extract_feat() -> _decode_head.forward() + + Args: + + image_encoder (ConfigType): The config for the visual encoder of segmentor. + text_encoder ((ConfigType): The config for the text encoder of segmentor. + decode_head (ConfigType): The config for the decode head of segmentor. + train_cfg (OptConfigType): The config for training. Defaults to None. + test_cfg (OptConfigType): The config for testing. Defaults to None. + data_preprocessor (dict, optional): The pre-process config of + :class:`BaseDataPreprocessor`. + pretrained (str, optional): The path for pretrained model. + Defaults to None. + asymetric_input (bool): whether to use different size of input for image encoder + and decode head. Defaults to False. + encoder_resolution (float): resize scale of input images for image encoder. + Defaults to None. + init_cfg (dict, optional): The weight initialized config for + :class:`BaseModule`. + """ # noqa: E501 + + def __init__(self, + image_encoder: ConfigType, + text_encoder: ConfigType, + decode_head: ConfigType, + train_cfg: OptConfigType = None, + test_cfg: OptConfigType = None, + data_preprocessor: OptConfigType = None, + pretrained: Optional[str] = None, + asymetric_input: bool = True, + encoder_resolution: float = None, + init_cfg: OptMultiConfig = None): + super().__init__( + data_preprocessor=data_preprocessor, init_cfg=init_cfg) + if pretrained is not None: + image_encoder.init_cfg = dict( + type='Pretrained_Part', checkpoint=pretrained) + text_encoder.init_cfg = dict( + type='Pretrained_Part', checkpoint=pretrained) + decode_head.init_cfg = dict( + type='Pretrained_Part', checkpoint=pretrained) + + if asymetric_input: + assert encoder_resolution is not None, \ + 'if asymetric_input set True, ' \ + 'clip_resolution must be a certain value' + self.asymetric_input = asymetric_input + self.encoder_resolution = encoder_resolution + self.image_encoder = MODELS.build(image_encoder) + self.text_encoder = MODELS.build(text_encoder) + self._init_decode_head(decode_head) + + self.train_cfg = train_cfg + self.test_cfg = test_cfg + + assert self.with_decode_head + + def _init_decode_head(self, decode_head: ConfigType) -> None: + """Initialize ``decode_head``""" + self.decode_head = MODELS.build(decode_head) + self.align_corners = self.decode_head.align_corners + self.num_classes = self.decode_head.num_classes + self.out_channels = self.decode_head.out_channels + + def extract_feat(self, inputs: Tensor) -> List[Tensor]: + """Extract visual features from images.""" + x = self.image_encoder(inputs) + return x + + def encode_decode(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Encode the name of classes with text_encoder and encode images with + image_encoder. + + Then decode the class embedding and visual feature into a semantic + segmentation map of the same size as input. + """ + classifier_embeds = self.text_encoder() + clip_inputs = inputs + if self.asymetric_input: + clip_inputs = F.interpolate( + inputs, scale_factor=self.encoder_resolution, mode='bilinear') + x = self.image_encoder(clip_inputs) + seg_logits = self.decode_head.predict([inputs, x, classifier_embeds], + batch_img_metas, self.test_cfg) + + return seg_logits + + def _decode_head_forward_train(self, inputs: List[Tensor], + data_samples: SampleList) -> dict: + """Run forward function and calculate loss for decode head in + training.""" + losses = dict() + loss_decode = self.decode_head.loss(inputs, data_samples, + self.train_cfg) + + losses.update(add_prefix(loss_decode, 'decode')) + return losses + + def loss(self, inputs: Tensor, data_samples: SampleList) -> dict: + """Calculate losses from a batch of inputs and data samples. + + Args: + inputs (Tensor): Input images. + data_samples (list[:obj:`SegDataSample`]): The seg data samples. + It usually includes information such as `metainfo` and + `gt_sem_seg`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + classifier_embeds = self.text_encoder() + clip_inputs = inputs + if self.asymetric_input: + clip_inputs = F.interpolate( + inputs, scale_factor=self.encoder_resolution, mode='bilinear') + x = self.image_encoder(clip_inputs) + + losses = dict() + + loss_decode = self._decode_head_forward_train( + [inputs, x, classifier_embeds], data_samples) + losses.update(loss_decode) + + return losses + + def predict(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> SampleList: + """Predict results from a batch of inputs and data samples with post- + processing. + + Args: + inputs (Tensor): Inputs with shape (N, C, H, W). + data_samples (List[:obj:`SegDataSample`], optional): The seg data + samples. It usually includes information such as `metainfo` + and `gt_sem_seg`. + + Returns: + list[:obj:`SegDataSample`]: Segmentation results of the + input images. Each SegDataSample usually contain: + + - ``pred_sem_seg``(PixelData): Prediction of semantic segmentation. + - ``seg_logits``(PixelData): Predicted logits of semantic + segmentation before normalization. + """ + if data_samples is not None: + batch_img_metas = [ + data_sample.metainfo for data_sample in data_samples + ] + else: + batch_img_metas = [ + dict( + ori_shape=inputs.shape[2:], + img_shape=inputs.shape[2:], + pad_shape=inputs.shape[2:], + padding_size=[0, 0, 0, 0]) + ] * inputs.shape[0] + + seg_logits = self.inference(inputs, batch_img_metas) + + return self.postprocess_result(seg_logits, data_samples) + + def _forward(self, + inputs: Tensor, + data_samples: OptSampleList = None) -> Tensor: + """Network forward process. + + Args: + inputs (Tensor): Inputs with shape (N, C, H, W). + data_samples (List[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `metainfo` and `gt_sem_seg`. + + Returns: + Tensor: Forward output of model without any post-processes. + """ + x = self.extract_feat(inputs) + return self.decode_head.forward(x) + + def slide_inference(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Inference by sliding-window with overlap. + + If h_crop > h_img or w_crop > w_img, the small patch will be used to + decode without padding. + + Args: + inputs (tensor): the tensor should have a shape NxCxHxW, + which contains all images in the batch. + batch_img_metas (List[dict]): List of image metainfo where each may + also contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', and 'pad_shape'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + + Returns: + Tensor: The segmentation results, seg_logits from model of each + input image. + """ + + h_stride, w_stride = self.test_cfg.stride + h_crop, w_crop = self.test_cfg.crop_size + batch_size, _, h_img, w_img = inputs.size() + out_channels = self.out_channels + h_grids = max(h_img - h_crop + h_stride - 1, 0) // h_stride + 1 + w_grids = max(w_img - w_crop + w_stride - 1, 0) // w_stride + 1 + preds = inputs.new_zeros((batch_size, out_channels, h_img, w_img)) + count_mat = inputs.new_zeros((batch_size, 1, h_img, w_img)) + for h_idx in range(h_grids): + for w_idx in range(w_grids): + y1 = h_idx * h_stride + x1 = w_idx * w_stride + y2 = min(y1 + h_crop, h_img) + x2 = min(x1 + w_crop, w_img) + y1 = max(y2 - h_crop, 0) + x1 = max(x2 - w_crop, 0) + crop_img = inputs[:, :, y1:y2, x1:x2] + # change the image shape to patch shape + batch_img_metas[0]['img_shape'] = crop_img.shape[2:] + # the output of encode_decode is seg logits tensor map + # with shape [N, C, H, W] + crop_seg_logit = self.encode_decode(crop_img, batch_img_metas) + preds += F.pad(crop_seg_logit, + (int(x1), int(preds.shape[3] - x2), int(y1), + int(preds.shape[2] - y2))) + + count_mat[:, :, y1:y2, x1:x2] += 1 + assert (count_mat == 0).sum() == 0 + seg_logits = preds / count_mat + + return seg_logits + + def whole_inference(self, inputs: Tensor, + batch_img_metas: List[dict]) -> Tensor: + """Inference with full image. + + Args: + inputs (Tensor): The tensor should have a shape NxCxHxW, which + contains all images in the batch. + batch_img_metas (List[dict]): List of image metainfo where each may + also contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', and 'pad_shape'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + + Returns: + Tensor: The segmentation results, seg_logits from model of each + input image. + """ + + seg_logits = self.encode_decode(inputs, batch_img_metas) + + return seg_logits + + def inference(self, inputs: Tensor, batch_img_metas: List[dict]) -> Tensor: + """Inference with slide/whole style. + + Args: + inputs (Tensor): The input image of shape (N, 3, H, W). + batch_img_metas (List[dict]): List of image metainfo where each may + also contain: 'img_shape', 'scale_factor', 'flip', 'img_path', + 'ori_shape', 'pad_shape', and 'padding_size'. + For details on the values of these keys see + `mmseg/datasets/pipelines/formatting.py:PackSegInputs`. + + Returns: + Tensor: The segmentation results, seg_logits from model of each + input image. + """ + + assert self.test_cfg.mode in ['slide', 'whole'] + ori_shape = batch_img_metas[0]['ori_shape'] + assert all(_['ori_shape'] == ori_shape for _ in batch_img_metas) + if self.test_cfg.mode == 'slide': + seg_logit = self.slide_inference(inputs, batch_img_metas) + else: + seg_logit = self.whole_inference(inputs, batch_img_metas) + + return seg_logit + + def aug_test(self, inputs, batch_img_metas, rescale=True): + """Test with augmentations. + + Only rescale=True is supported. + """ + # aug_test rescale all imgs back to ori_shape for now + assert rescale + # to save memory, we get augmented seg logit inplace + seg_logit = self.inference(inputs[0], batch_img_metas[0], rescale) + for i in range(1, len(inputs)): + cur_seg_logit = self.inference(inputs[i], batch_img_metas[i], + rescale) + seg_logit += cur_seg_logit + seg_logit /= len(inputs) + seg_pred = seg_logit.argmax(dim=1) + # unravel batch dim + seg_pred = list(seg_pred) + return seg_pred diff --git a/Seg_All_In_One_MMSeg/mmseg/models/segmentors/seg_tta.py b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/seg_tta.py new file mode 100644 index 0000000..63ef61d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/segmentors/seg_tta.py @@ -0,0 +1,47 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +from mmengine.model import BaseTTAModel +from mmengine.structures import PixelData + +from mmseg.registry import MODELS +from mmseg.utils import SampleList + + +@MODELS.register_module() +class SegTTAModel(BaseTTAModel): + + def merge_preds(self, data_samples_list: List[SampleList]) -> SampleList: + """Merge predictions of enhanced data to one prediction. + + Args: + data_samples_list (List[SampleList]): List of predictions + of all enhanced data. + + Returns: + SampleList: Merged prediction. + """ + predictions = [] + for data_samples in data_samples_list: + seg_logits = data_samples[0].seg_logits.data + logits = torch.zeros(seg_logits.shape).to(seg_logits) + for data_sample in data_samples: + seg_logit = data_sample.seg_logits.data + if self.module.out_channels > 1: + logits += seg_logit.softmax(dim=0) + else: + logits += seg_logit.sigmoid() + logits /= len(data_samples) + if self.module.out_channels == 1: + seg_pred = (logits > self.module.decode_head.threshold + ).to(logits).squeeze(1) + else: + seg_pred = logits.argmax(dim=0) + data_sample.set_data({'pred_sem_seg': PixelData(data=seg_pred)}) + if hasattr(data_samples[0], 'gt_sem_seg'): + data_sample.set_data( + {'gt_sem_seg': data_samples[0].gt_sem_seg}) + data_sample.set_metainfo({'img_path': data_samples[0].img_path}) + predictions.append(data_sample) + return predictions diff --git a/Seg_All_In_One_MMSeg/mmseg/models/text_encoder/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/text_encoder/__init__.py new file mode 100644 index 0000000..199856d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/text_encoder/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .clip_text_encoder import CLIPTextEncoder + +__all__ = ['CLIPTextEncoder'] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/text_encoder/clip_text_encoder.py b/Seg_All_In_One_MMSeg/mmseg/models/text_encoder/clip_text_encoder.py new file mode 100644 index 0000000..1a18b86 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/text_encoder/clip_text_encoder.py @@ -0,0 +1,229 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import numpy as np +import torch +import torch.nn as nn +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import BaseTransformerLayer +from mmengine.model import BaseModule, ModuleList +from mmengine.runner.checkpoint import CheckpointLoader, load_state_dict +from torch.nn import functional as F + +from mmseg.registry import MODELS +from mmseg.utils import get_classes, get_predefined_templates, tokenizer + + +@MODELS.register_module() +class CLIPTextEncoder(BaseModule): + """A text encoder with transformer architecture to encode the label text. + + Modified from https://github.com/MendelXu/SAN/blob/main/san/model/clip_utils/classifier.py # noqa:E501 + Copyright (c) 2023 MendelXu. + Licensed under the MIT License + + Args: + dataset_name: (str|None): The name of the dataset to which + the data belongs. + vocabulary: (List[str]|None): The list of class names. Default: None. + templates: (List[str]|None): The prompt template used for labels. + Default: None. + total_vocab_size: (int): Number of all words used by the pre-trained + model. Default: 49408 (CLIP). + context_length: (int): The max length of prompt text. + Default: 77 (CLIP). + embed_dims: (int): Width of transformer model. Default: 512. + num_layers: (int): Depth of transformer. Default: 12, + num_heads: (int): Number of attention heads in transformer. + Default: 8, + mlp_ratio: (int) Ratio of mlp hidden dim to embedding dim in + transformer. Default: 4, + output_dims: (int) Dim of output text embeddings. Default: 512, + cache_feature: (bool) Whether to save class embeddings in cache. + Default: True, + cat_bg: (bool) Whether to add background embedding. Default: True. + norm_cfg (dict|None): Config for norm layer. Default: dict(type='LN') + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__(self, + dataset_name: str = None, + vocabulary: List[str] = None, + templates: str = 'vild', + total_vocab_size: int = 49408, + context_length: int = 77, + embed_dims: int = 512, + num_layers: int = 12, + num_heads: int = 8, + mlp_ratio: int = 4, + output_dims: int = 512, + cache_feature: bool = True, + cat_bg: bool = True, + norm_cfg: dict = dict(type='LN'), + init_cfg: dict = None): + super().__init__(init_cfg) + if isinstance(templates, List): + self.templates = templates + else: + self.templates = get_predefined_templates(templates) + + assert dataset_name is not None or vocabulary is not None, \ + "text_encoder required either 'dataset_name' or 'vocabulary'" + assert dataset_name is None or vocabulary is None, \ + "there is conflict between 'dataset_name' and 'vocabulary'" + self.dataset_name = dataset_name + self.vocabulary = vocabulary + self.num_pos = context_length + self.token_embedding = nn.Embedding(total_vocab_size, embed_dims) + self.positional_embedding = nn.Parameter( + torch.empty(context_length, embed_dims)) + self.text_projection = nn.Parameter( + torch.empty(embed_dims, output_dims)) + self.logit_scale = nn.Parameter(torch.ones([]) * np.log(1 / 0.07)) + self.transformer = ModuleList() + self.register_buffer( + 'attn_mask', self.build_attention_mask(), persistent=False) + for i in range(num_layers): + self.transformer.append( + BaseTransformerLayer( + attn_cfgs=dict( + type='MultiheadAttention', + embed_dims=embed_dims, + num_heads=num_heads, + batch_first=False, + bias=True), + ffn_cfgs=dict( + type='FFN', + embed_dims=embed_dims, + feedforward_channels=mlp_ratio * embed_dims, + act_cfg=dict(type='QuickGELU')), + operation_order=('norm', 'self_attn', 'norm', 'ffn'))) + self.ln_final = build_norm_layer( + norm_cfg, embed_dims, postfix='_final')[1] + + self.cache_feature = cache_feature + if self.cache_feature: + self.cache = {} + + self._freeze() + + self.cat_bg = cat_bg + if self.cat_bg: + self.bg_embed = nn.Parameter( + torch.randn(1, self.text_projection.shape[1])) + + @property + def ln_final(self): + return getattr(self, self.final_name) + + def build_attention_mask(self): + """lazily create causal attention mask, with full attention between the + tokens. + + pytorch uses additive attention mask; fill with -inf + """ + mask = torch.empty(self.num_pos, self.num_pos) + mask.fill_(float('-inf')) + mask.triu_(1) # zero out the lower diagonal + return mask + + def _freeze(self): + for param in self.parameters(): + param.requires_grad = False + + def init_weights(self): + if self.cat_bg: + nn.init.normal_( + self.bg_embed, + std=self.bg_embed.shape[1]**-0.5, + ) + if isinstance(self.init_cfg, dict) and \ + self.init_cfg.get('type') == 'Pretrained_Part': + checkpoint = CheckpointLoader.load_checkpoint( + self.init_cfg['checkpoint'], logger=None, map_location='cpu') + + state_dict = checkpoint.copy() + para_prefix = 'text_encoder' + prefix_len = len(para_prefix) + 1 + for k, v in checkpoint.items(): + state_dict.pop(k) + if para_prefix in k: + state_dict[k[prefix_len:]] = v + + load_state_dict(self, state_dict, strict=False, logger=None) + + else: + super().init_weights() + + @torch.no_grad() + def encode_text(self, text, normalize=False): + """encode class token.""" + + embed_device = self.token_embedding.weight.device + x = self.token_embedding( + text.to(embed_device)) # [batch_size, n_ctx, d_model] + x = x + self.positional_embedding + x = x.permute(1, 0, 2) # NLD -> LND + for block in self.transformer: + x = block(query=x, attn_masks=self.attn_mask) + x = x.permute(1, 0, 2) # LND -> NLD + x = self.ln_final(x) # [batch_size, n_ctx, transformer.width] + # take features from the eot embedding + # (eot_token is the highest number in each sequence) + x = x[torch.arange(x.shape[0]), + text.argmax(dim=-1)] @ self.text_projection + return F.normalize(x, dim=-1) if normalize else x + + def template_encode(self, vocabulary): + """Prompt engineering.""" + text_embed_bucket = [] + for template in self.templates: + text_inputs = tokenizer.tokenize( + [template.format(noun) for noun in vocabulary]) + text_embed = self.encode_text(text_inputs, normalize=True) + text_embed_bucket.append(text_embed) + text_embed = torch.stack(text_embed_bucket).mean(dim=0) + text_embed = text_embed / text_embed.norm(dim=-1, keepdim=True) + return text_embed + + def forward(self): + """Forward function.""" + if self.dataset_name is None: # encoding vocabulary directly + class_names = self.vocabulary + if self.cache_feature: + new_classes = [ + word for word in class_names if word not in self.cache + ] + if len(new_classes) > 0: + class_embeds = self.template_encode(new_classes) + self.cache.update(dict(zip(new_classes, class_embeds))) + class_embeds = torch.stack( + [self.cache[word] for word in class_names]) + else: + class_embeds = self.template_encode(class_names) + + else: # encoding the classes of the dataset + class_names = get_classes(self.dataset_name) + if class_names[0] == 'background': + class_names = class_names[1:] + if self.cache_feature: + if self.dataset_name not in self.cache: + class_embeds = self.template_encode(class_names) + self.cache[self.dataset_name] = class_embeds + else: + class_embeds = self.cache[self.dataset_name] + else: + class_embeds = self.template_encode(class_names) + + if self.cat_bg: + class_embeds = torch.cat([class_embeds, self.bg_embed]) + class_embeds = F.normalize(class_embeds, p=2, dim=-1) + return self.logit_scale.exp() * class_embeds + + +@MODELS.register_module() +class QuickGELU(nn.Module): + # From https://github.com/openai/CLIP/blob/main/clip/model.py + def forward(self, x: torch.Tensor): + return x * torch.sigmoid(1.702 * x) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/__init__.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/__init__.py new file mode 100644 index 0000000..c0751b1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .basic_block import BasicBlock, Bottleneck +from .embed import PatchEmbed +from .encoding import Encoding +from .inverted_residual import InvertedResidual, InvertedResidualV3 +from .make_divisible import make_divisible +from .point_sample import get_uncertain_point_coords_with_randomness +from .ppm import DAPPM, PAPPM +from .res_layer import ResLayer +from .se_layer import SELayer +from .self_attention_block import SelfAttentionBlock +from .shape_convert import (nchw2nlc2nchw, nchw_to_nlc, nlc2nchw2nlc, + nlc_to_nchw) +from .up_conv_block import UpConvBlock + +# isort: off +from .wrappers import Upsample, resize +from .san_layers import MLP, LayerNorm2d, cross_attn_layer + +__all__ = [ + 'ResLayer', 'SelfAttentionBlock', 'make_divisible', 'InvertedResidual', + 'UpConvBlock', 'InvertedResidualV3', 'SELayer', 'PatchEmbed', + 'nchw_to_nlc', 'nlc_to_nchw', 'nchw2nlc2nchw', 'nlc2nchw2nlc', 'Encoding', + 'Upsample', 'resize', 'DAPPM', 'PAPPM', 'BasicBlock', 'Bottleneck', + 'cross_attn_layer', 'LayerNorm2d', 'MLP', + 'get_uncertain_point_coords_with_randomness' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/basic_block.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/basic_block.py new file mode 100644 index 0000000..4e1ad81 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/basic_block.py @@ -0,0 +1,143 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional + +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule +from torch import Tensor + +from mmseg.registry import MODELS +from mmseg.utils import OptConfigType + + +class BasicBlock(BaseModule): + """Basic block from `ResNet `_. + + Args: + in_channels (int): Input channels. + channels (int): Output channels. + stride (int): Stride of the first block. Default: 1. + downsample (nn.Module, optional): Downsample operation on identity. + Default: None. + norm_cfg (dict, optional): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict, optional): Config dict for activation layer in + ConvModule. Default: dict(type='ReLU', inplace=True). + act_cfg_out (dict, optional): Config dict for activation layer at the + last of the block. Default: None. + init_cfg (dict, optional): Initialization config dict. Default: None. + """ + + expansion = 1 + + def __init__(self, + in_channels: int, + channels: int, + stride: int = 1, + downsample: nn.Module = None, + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + act_cfg_out: OptConfigType = dict(type='ReLU', inplace=True), + init_cfg: OptConfigType = None): + super().__init__(init_cfg) + self.conv1 = ConvModule( + in_channels, + channels, + kernel_size=3, + stride=stride, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.conv2 = ConvModule( + channels, + channels, + kernel_size=3, + padding=1, + norm_cfg=norm_cfg, + act_cfg=None) + self.downsample = downsample + if act_cfg_out: + self.act = MODELS.build(act_cfg_out) + + def forward(self, x: Tensor) -> Tensor: + residual = x + out = self.conv1(x) + out = self.conv2(out) + + if self.downsample: + residual = self.downsample(x) + + out += residual + + if hasattr(self, 'act'): + out = self.act(out) + + return out + + +class Bottleneck(BaseModule): + """Bottleneck block from `ResNet `_. + + Args: + in_channels (int): Input channels. + channels (int): Output channels. + stride (int): Stride of the first block. Default: 1. + downsample (nn.Module, optional): Downsample operation on identity. + Default: None. + norm_cfg (dict, optional): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict, optional): Config dict for activation layer in + ConvModule. Default: dict(type='ReLU', inplace=True). + act_cfg_out (dict, optional): Config dict for activation layer at + the last of the block. Default: None. + init_cfg (dict, optional): Initialization config dict. Default: None. + """ + + expansion = 2 + + def __init__(self, + in_channels: int, + channels: int, + stride: int = 1, + downsample: Optional[nn.Module] = None, + norm_cfg: OptConfigType = dict(type='BN'), + act_cfg: OptConfigType = dict(type='ReLU', inplace=True), + act_cfg_out: OptConfigType = None, + init_cfg: OptConfigType = None): + super().__init__(init_cfg) + self.conv1 = ConvModule( + in_channels, channels, 1, norm_cfg=norm_cfg, act_cfg=act_cfg) + self.conv2 = ConvModule( + channels, + channels, + 3, + stride, + 1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.conv3 = ConvModule( + channels, + channels * self.expansion, + 1, + norm_cfg=norm_cfg, + act_cfg=None) + if act_cfg_out: + self.act = MODELS.build(act_cfg_out) + self.downsample = downsample + + def forward(self, x: Tensor) -> Tensor: + residual = x + + out = self.conv1(x) + out = self.conv2(out) + out = self.conv3(out) + + if self.downsample: + residual = self.downsample(x) + + out += residual + + if hasattr(self, 'act'): + out = self.act(out) + + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/embed.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/embed.py new file mode 100644 index 0000000..aef0a40 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/embed.py @@ -0,0 +1,330 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +from typing import Sequence + +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.model import BaseModule +from mmengine.utils import to_2tuple + + +class AdaptivePadding(nn.Module): + """Applies padding to input (if needed) so that input can get fully covered + by filter you specified. It support two modes "same" and "corner". The + "same" mode is same with "SAME" padding mode in TensorFlow, pad zero around + input. The "corner" mode would pad zero to bottom right. + + Args: + kernel_size (int | tuple): Size of the kernel: + stride (int | tuple): Stride of the filter. Default: 1: + dilation (int | tuple): Spacing between kernel elements. + Default: 1. + padding (str): Support "same" and "corner", "corner" mode + would pad zero to bottom right, and "same" mode would + pad zero around input. Default: "corner". + Example: + >>> kernel_size = 16 + >>> stride = 16 + >>> dilation = 1 + >>> input = torch.rand(1, 1, 15, 17) + >>> adap_pad = AdaptivePadding( + >>> kernel_size=kernel_size, + >>> stride=stride, + >>> dilation=dilation, + >>> padding="corner") + >>> out = adap_pad(input) + >>> assert (out.shape[2], out.shape[3]) == (16, 32) + >>> input = torch.rand(1, 1, 16, 17) + >>> out = adap_pad(input) + >>> assert (out.shape[2], out.shape[3]) == (16, 32) + """ + + def __init__(self, kernel_size=1, stride=1, dilation=1, padding='corner'): + + super().__init__() + + assert padding in ('same', 'corner') + + kernel_size = to_2tuple(kernel_size) + stride = to_2tuple(stride) + dilation = to_2tuple(dilation) + + self.padding = padding + self.kernel_size = kernel_size + self.stride = stride + self.dilation = dilation + + def get_pad_shape(self, input_shape): + input_h, input_w = input_shape + kernel_h, kernel_w = self.kernel_size + stride_h, stride_w = self.stride + output_h = math.ceil(input_h / stride_h) + output_w = math.ceil(input_w / stride_w) + pad_h = max((output_h - 1) * stride_h + + (kernel_h - 1) * self.dilation[0] + 1 - input_h, 0) + pad_w = max((output_w - 1) * stride_w + + (kernel_w - 1) * self.dilation[1] + 1 - input_w, 0) + return pad_h, pad_w + + def forward(self, x): + pad_h, pad_w = self.get_pad_shape(x.size()[-2:]) + if pad_h > 0 or pad_w > 0: + if self.padding == 'corner': + x = F.pad(x, [0, pad_w, 0, pad_h]) + elif self.padding == 'same': + x = F.pad(x, [ + pad_w // 2, pad_w - pad_w // 2, pad_h // 2, + pad_h - pad_h // 2 + ]) + return x + + +class PatchEmbed(BaseModule): + """Image to Patch Embedding. + + We use a conv layer to implement PatchEmbed. + + Args: + in_channels (int): The num of input channels. Default: 3 + embed_dims (int): The dimensions of embedding. Default: 768 + conv_type (str): The config dict for embedding + conv layer type selection. Default: "Conv2d". + kernel_size (int): The kernel_size of embedding conv. Default: 16. + stride (int, optional): The slide stride of embedding conv. + Default: None (Would be set as `kernel_size`). + padding (int | tuple | string ): The padding length of + embedding conv. When it is a string, it means the mode + of adaptive padding, support "same" and "corner" now. + Default: "corner". + dilation (int): The dilation rate of embedding conv. Default: 1. + bias (bool): Bias of embed conv. Default: True. + norm_cfg (dict, optional): Config dict for normalization layer. + Default: None. + input_size (int | tuple | None): The size of input, which will be + used to calculate the out size. Only work when `dynamic_size` + is False. Default: None. + init_cfg (`mmengine.ConfigDict`, optional): The Config for + initialization. Default: None. + """ + + def __init__(self, + in_channels=3, + embed_dims=768, + conv_type='Conv2d', + kernel_size=16, + stride=None, + padding='corner', + dilation=1, + bias=True, + norm_cfg=None, + input_size=None, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.embed_dims = embed_dims + if stride is None: + stride = kernel_size + + kernel_size = to_2tuple(kernel_size) + stride = to_2tuple(stride) + dilation = to_2tuple(dilation) + + if isinstance(padding, str): + self.adap_padding = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + # disable the padding of conv + padding = 0 + else: + self.adap_padding = None + padding = to_2tuple(padding) + + self.projection = build_conv_layer( + dict(type=conv_type), + in_channels=in_channels, + out_channels=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + if norm_cfg is not None: + self.norm = build_norm_layer(norm_cfg, embed_dims)[1] + else: + self.norm = None + + if input_size: + input_size = to_2tuple(input_size) + # `init_out_size` would be used outside to + # calculate the num_patches + # when `use_abs_pos_embed` outside + self.init_input_size = input_size + if self.adap_padding: + pad_h, pad_w = self.adap_padding.get_pad_shape(input_size) + input_h, input_w = input_size + input_h = input_h + pad_h + input_w = input_w + pad_w + input_size = (input_h, input_w) + + # https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html + h_out = (input_size[0] + 2 * padding[0] - dilation[0] * + (kernel_size[0] - 1) - 1) // stride[0] + 1 + w_out = (input_size[1] + 2 * padding[1] - dilation[1] * + (kernel_size[1] - 1) - 1) // stride[1] + 1 + self.init_out_size = (h_out, w_out) + else: + self.init_input_size = None + self.init_out_size = None + + def forward(self, x): + """ + Args: + x (Tensor): Has shape (B, C, H, W). In most case, C is 3. + + Returns: + tuple: Contains merged results and its spatial shape. + + - x (Tensor): Has shape (B, out_h * out_w, embed_dims) + - out_size (tuple[int]): Spatial shape of x, arrange as + (out_h, out_w). + """ + + if self.adap_padding: + x = self.adap_padding(x) + + x = self.projection(x) + out_size = (x.shape[2], x.shape[3]) + x = x.flatten(2).transpose(1, 2) + if self.norm is not None: + x = self.norm(x) + return x, out_size + + +class PatchMerging(BaseModule): + """Merge patch feature map. + + This layer groups feature map by kernel_size, and applies norm and linear + layers to the grouped feature map. Our implementation uses `nn.Unfold` to + merge patch, which is about 25% faster than original implementation. + Instead, we need to modify pretrained models for compatibility. + + Args: + in_channels (int): The num of input channels. + out_channels (int): The num of output channels. + kernel_size (int | tuple, optional): the kernel size in the unfold + layer. Defaults to 2. + stride (int | tuple, optional): the stride of the sliding blocks in the + unfold layer. Default: None. (Would be set as `kernel_size`) + padding (int | tuple | string ): The padding length of + embedding conv. When it is a string, it means the mode + of adaptive padding, support "same" and "corner" now. + Default: "corner". + dilation (int | tuple, optional): dilation parameter in the unfold + layer. Default: 1. + bias (bool, optional): Whether to add bias in linear layer or not. + Defaults: False. + norm_cfg (dict, optional): Config dict for normalization layer. + Default: dict(type='LN'). + init_cfg (dict, optional): The extra config for initialization. + Default: None. + """ + + def __init__(self, + in_channels, + out_channels, + kernel_size=2, + stride=None, + padding='corner', + dilation=1, + bias=False, + norm_cfg=dict(type='LN'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.in_channels = in_channels + self.out_channels = out_channels + if stride: + stride = stride + else: + stride = kernel_size + + kernel_size = to_2tuple(kernel_size) + stride = to_2tuple(stride) + dilation = to_2tuple(dilation) + + if isinstance(padding, str): + self.adap_padding = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + # disable the padding of unfold + padding = 0 + else: + self.adap_padding = None + + padding = to_2tuple(padding) + self.sampler = nn.Unfold( + kernel_size=kernel_size, + dilation=dilation, + padding=padding, + stride=stride) + + sample_dim = kernel_size[0] * kernel_size[1] * in_channels + + if norm_cfg is not None: + self.norm = build_norm_layer(norm_cfg, sample_dim)[1] + else: + self.norm = None + + self.reduction = nn.Linear(sample_dim, out_channels, bias=bias) + + def forward(self, x, input_size): + """ + Args: + x (Tensor): Has shape (B, H*W, C_in). + input_size (tuple[int]): The spatial shape of x, arrange as (H, W). + Default: None. + + Returns: + tuple: Contains merged results and its spatial shape. + + - x (Tensor): Has shape (B, Merged_H * Merged_W, C_out) + - out_size (tuple[int]): Spatial shape of x, arrange as + (Merged_H, Merged_W). + """ + B, L, C = x.shape + assert isinstance(input_size, Sequence), f'Expect ' \ + f'input_size is ' \ + f'`Sequence` ' \ + f'but get {input_size}' + + H, W = input_size + assert L == H * W, 'input feature has wrong size' + + x = x.view(B, H, W, C).permute([0, 3, 1, 2]) # B, C, H, W + # Use nn.Unfold to merge patch. About 25% faster than original method, + # but need to modify pretrained model for compatibility + + if self.adap_padding: + x = self.adap_padding(x) + H, W = x.shape[-2:] + + x = self.sampler(x) + # if kernel_size=2 and stride=2, x should has shape (B, 4*C, H/2*W/2) + + out_h = (H + 2 * self.sampler.padding[0] - self.sampler.dilation[0] * + (self.sampler.kernel_size[0] - 1) - + 1) // self.sampler.stride[0] + 1 + out_w = (W + 2 * self.sampler.padding[1] - self.sampler.dilation[1] * + (self.sampler.kernel_size[1] - 1) - + 1) // self.sampler.stride[1] + 1 + + output_size = (out_h, out_w) + x = x.transpose(1, 2) # B, H/2*W/2, 4*C + x = self.norm(x) if self.norm else x + x = self.reduction(x) + return x, output_size diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/encoding.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/encoding.py new file mode 100644 index 0000000..ee4f057 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/encoding.py @@ -0,0 +1,75 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import nn +from torch.nn import functional as F + + +class Encoding(nn.Module): + """Encoding Layer: a learnable residual encoder. + + Input is of shape (batch_size, channels, height, width). + Output is of shape (batch_size, num_codes, channels). + + Args: + channels: dimension of the features or feature channels + num_codes: number of code words + """ + + def __init__(self, channels, num_codes): + super().__init__() + # init codewords and smoothing factor + self.channels, self.num_codes = channels, num_codes + std = 1. / ((num_codes * channels)**0.5) + # [num_codes, channels] + self.codewords = nn.Parameter( + torch.empty(num_codes, channels, + dtype=torch.float).uniform_(-std, std), + requires_grad=True) + # [num_codes] + self.scale = nn.Parameter( + torch.empty(num_codes, dtype=torch.float).uniform_(-1, 0), + requires_grad=True) + + @staticmethod + def scaled_l2(x, codewords, scale): + num_codes, channels = codewords.size() + batch_size = x.size(0) + reshaped_scale = scale.view((1, 1, num_codes)) + expanded_x = x.unsqueeze(2).expand( + (batch_size, x.size(1), num_codes, channels)) + reshaped_codewords = codewords.view((1, 1, num_codes, channels)) + + scaled_l2_norm = reshaped_scale * ( + expanded_x - reshaped_codewords).pow(2).sum(dim=3) + return scaled_l2_norm + + @staticmethod + def aggregate(assignment_weights, x, codewords): + num_codes, channels = codewords.size() + reshaped_codewords = codewords.view((1, 1, num_codes, channels)) + batch_size = x.size(0) + + expanded_x = x.unsqueeze(2).expand( + (batch_size, x.size(1), num_codes, channels)) + encoded_feat = (assignment_weights.unsqueeze(3) * + (expanded_x - reshaped_codewords)).sum(dim=1) + return encoded_feat + + def forward(self, x): + assert x.dim() == 4 and x.size(1) == self.channels + # [batch_size, channels, height, width] + batch_size = x.size(0) + # [batch_size, height x width, channels] + x = x.view(batch_size, self.channels, -1).transpose(1, 2).contiguous() + # assignment_weights: [batch_size, channels, num_codes] + assignment_weights = F.softmax( + self.scaled_l2(x, self.codewords, self.scale), dim=2) + # aggregate + encoded_feat = self.aggregate(assignment_weights, x, self.codewords) + return encoded_feat + + def __repr__(self): + repr_str = self.__class__.__name__ + repr_str += f'(Nx{self.channels}xHxW =>Nx{self.num_codes}' \ + f'x{self.channels})' + return repr_str diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/inverted_residual.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/inverted_residual.py new file mode 100644 index 0000000..56190b3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/inverted_residual.py @@ -0,0 +1,213 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.cnn import ConvModule +from torch import nn +from torch.utils import checkpoint as cp + +from .se_layer import SELayer + + +class InvertedResidual(nn.Module): + """InvertedResidual block for MobileNetV2. + + Args: + in_channels (int): The input channels of the InvertedResidual block. + out_channels (int): The output channels of the InvertedResidual block. + stride (int): Stride of the middle (first) 3x3 convolution. + expand_ratio (int): Adjusts number of channels of the hidden layer + in InvertedResidual by this amount. + dilation (int): Dilation rate of depthwise conv. Default: 1 + conv_cfg (dict): Config dict for convolution layer. + Default: None, which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU6'). + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + + Returns: + Tensor: The output tensor. + """ + + def __init__(self, + in_channels, + out_channels, + stride, + expand_ratio, + dilation=1, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU6'), + with_cp=False, + **kwargs): + super().__init__() + self.stride = stride + assert stride in [1, 2], f'stride must in [1, 2]. ' \ + f'But received {stride}.' + self.with_cp = with_cp + self.use_res_connect = self.stride == 1 and in_channels == out_channels + hidden_dim = int(round(in_channels * expand_ratio)) + + layers = [] + if expand_ratio != 1: + layers.append( + ConvModule( + in_channels=in_channels, + out_channels=hidden_dim, + kernel_size=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **kwargs)) + layers.extend([ + ConvModule( + in_channels=hidden_dim, + out_channels=hidden_dim, + kernel_size=3, + stride=stride, + padding=dilation, + dilation=dilation, + groups=hidden_dim, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **kwargs), + ConvModule( + in_channels=hidden_dim, + out_channels=out_channels, + kernel_size=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None, + **kwargs) + ]) + self.conv = nn.Sequential(*layers) + + def forward(self, x): + + def _inner_forward(x): + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + return out + + +class InvertedResidualV3(nn.Module): + """Inverted Residual Block for MobileNetV3. + + Args: + in_channels (int): The input channels of this Module. + out_channels (int): The output channels of this Module. + mid_channels (int): The input channels of the depthwise convolution. + kernel_size (int): The kernel size of the depthwise convolution. + Default: 3. + stride (int): The stride of the depthwise convolution. Default: 1. + se_cfg (dict): Config dict for se layer. Default: None, which means no + se layer. + with_expand_conv (bool): Use expand conv or not. If set False, + mid_channels must be the same with in_channels. Default: True. + conv_cfg (dict): Config dict for convolution layer. Default: None, + which means using conv2d. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU'). + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + + Returns: + Tensor: The output tensor. + """ + + def __init__(self, + in_channels, + out_channels, + mid_channels, + kernel_size=3, + stride=1, + se_cfg=None, + with_expand_conv=True, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + with_cp=False): + super().__init__() + self.with_res_shortcut = (stride == 1 and in_channels == out_channels) + assert stride in [1, 2] + self.with_cp = with_cp + self.with_se = se_cfg is not None + self.with_expand_conv = with_expand_conv + + if self.with_se: + assert isinstance(se_cfg, dict) + if not self.with_expand_conv: + assert mid_channels == in_channels + + if self.with_expand_conv: + self.expand_conv = ConvModule( + in_channels=in_channels, + out_channels=mid_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.depthwise_conv = ConvModule( + in_channels=mid_channels, + out_channels=mid_channels, + kernel_size=kernel_size, + stride=stride, + padding=kernel_size // 2, + groups=mid_channels, + conv_cfg=dict( + type='Conv2dAdaptivePadding') if stride == 2 else conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + if self.with_se: + self.se = SELayer(**se_cfg) + + self.linear_conv = ConvModule( + in_channels=mid_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=None) + + def forward(self, x): + + def _inner_forward(x): + out = x + + if self.with_expand_conv: + out = self.expand_conv(out) + + out = self.depthwise_conv(out) + + if self.with_se: + out = self.se(out) + + out = self.linear_conv(out) + + if self.with_res_shortcut: + return x + out + else: + return out + + if self.with_cp and x.requires_grad: + out = cp.checkpoint(_inner_forward, x) + else: + out = _inner_forward(x) + + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/make_divisible.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/make_divisible.py new file mode 100644 index 0000000..ed42c2e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/make_divisible.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +def make_divisible(value, divisor, min_value=None, min_ratio=0.9): + """Make divisible function. + + This function rounds the channel number to the nearest value that can be + divisible by the divisor. It is taken from the original tf repo. It ensures + that all layers have a channel number that is divisible by divisor. It can + be seen here: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py # noqa + + Args: + value (int): The original channel number. + divisor (int): The divisor to fully divide the channel number. + min_value (int): The minimum value of the output channel. + Default: None, means that the minimum value equal to the divisor. + min_ratio (float): The minimum ratio of the rounded channel number to + the original channel number. Default: 0.9. + + Returns: + int: The modified output channel number. + """ + + if min_value is None: + min_value = divisor + new_value = max(min_value, int(value + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than (1-min_ratio). + if new_value < min_ratio * value: + new_value += divisor + return new_value diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/point_sample.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/point_sample.py new file mode 100644 index 0000000..1afc957 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/point_sample.py @@ -0,0 +1,88 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.ops import point_sample +from torch import Tensor + + +def get_uncertainty(mask_preds: Tensor, labels: Tensor) -> Tensor: + """Estimate uncertainty based on pred logits. + + We estimate uncertainty as L1 distance between 0.0 and the logits + prediction in 'mask_preds' for the foreground class in `classes`. + + Args: + mask_preds (Tensor): mask predication logits, shape (num_rois, + num_classes, mask_height, mask_width). + + labels (Tensor): Either predicted or ground truth label for + each predicted mask, of length num_rois. + + Returns: + scores (Tensor): Uncertainty scores with the most uncertain + locations having the highest uncertainty score, + shape (num_rois, 1, mask_height, mask_width) + """ + if mask_preds.shape[1] == 1: + gt_class_logits = mask_preds.clone() + else: + inds = torch.arange(mask_preds.shape[0], device=mask_preds.device) + gt_class_logits = mask_preds[inds, labels].unsqueeze(1) + return -torch.abs(gt_class_logits) + + +def get_uncertain_point_coords_with_randomness( + mask_preds: Tensor, labels: Tensor, num_points: int, + oversample_ratio: float, importance_sample_ratio: float) -> Tensor: + """Get ``num_points`` most uncertain points with random points during + train. + + Sample points in [0, 1] x [0, 1] coordinate space based on their + uncertainty. The uncertainties are calculated for each point using + 'get_uncertainty()' function that takes point's logit prediction as + input. + + Args: + mask_preds (Tensor): A tensor of shape (num_rois, num_classes, + mask_height, mask_width) for class-specific or class-agnostic + prediction. + labels (Tensor): The ground truth class for each instance. + num_points (int): The number of points to sample. + oversample_ratio (float): Oversampling parameter. + importance_sample_ratio (float): Ratio of points that are sampled + via importnace sampling. + + Returns: + point_coords (Tensor): A tensor of shape (num_rois, num_points, 2) + that contains the coordinates sampled points. + """ + assert oversample_ratio >= 1 + assert 0 <= importance_sample_ratio <= 1 + batch_size = mask_preds.shape[0] + num_sampled = int(num_points * oversample_ratio) + point_coords = torch.rand( + batch_size, num_sampled, 2, device=mask_preds.device) + point_logits = point_sample(mask_preds, point_coords) + # It is crucial to calculate uncertainty based on the sampled + # prediction value for the points. Calculating uncertainties of the + # coarse predictions first and sampling them for points leads to + # incorrect results. To illustrate this: assume uncertainty func( + # logits)=-abs(logits), a sampled point between two coarse + # predictions with -1 and 1 logits has 0 logits, and therefore 0 + # uncertainty value. However, if we calculate uncertainties for the + # coarse predictions first, both will have -1 uncertainty, + # and sampled point will get -1 uncertainty. + point_uncertainties = get_uncertainty(point_logits, labels) + num_uncertain_points = int(importance_sample_ratio * num_points) + num_random_points = num_points - num_uncertain_points + idx = torch.topk( + point_uncertainties[:, 0, :], k=num_uncertain_points, dim=1)[1] + shift = num_sampled * torch.arange( + batch_size, dtype=torch.long, device=mask_preds.device) + idx += shift[:, None] + point_coords = point_coords.view(-1, 2)[idx.view(-1), :].view( + batch_size, num_uncertain_points, 2) + if num_random_points > 0: + rand_roi_coords = torch.rand( + batch_size, num_random_points, 2, device=mask_preds.device) + point_coords = torch.cat((point_coords, rand_roi_coords), dim=1) + return point_coords diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/ppm.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/ppm.py new file mode 100644 index 0000000..5fe6ff2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/ppm.py @@ -0,0 +1,193 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule +from mmengine.model import BaseModule, ModuleList, Sequential +from torch import Tensor + + +class DAPPM(BaseModule): + """DAPPM module in `DDRNet `_. + + Args: + in_channels (int): Input channels. + branch_channels (int): Branch channels. + out_channels (int): Output channels. + num_scales (int): Number of scales. + kernel_sizes (list[int]): Kernel sizes of each scale. + strides (list[int]): Strides of each scale. + paddings (list[int]): Paddings of each scale. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict): Config dict for activation layer in ConvModule. + Default: dict(type='ReLU', inplace=True). + conv_cfg (dict): Config dict for convolution layer in ConvModule. + Default: dict(order=('norm', 'act', 'conv'), bias=False). + upsample_mode (str): Upsample mode. Default: 'bilinear'. + """ + + def __init__(self, + in_channels: int, + branch_channels: int, + out_channels: int, + num_scales: int, + kernel_sizes: List[int] = [5, 9, 17], + strides: List[int] = [2, 4, 8], + paddings: List[int] = [2, 4, 8], + norm_cfg: Dict = dict(type='BN', momentum=0.1), + act_cfg: Dict = dict(type='ReLU', inplace=True), + conv_cfg: Dict = dict( + order=('norm', 'act', 'conv'), bias=False), + upsample_mode: str = 'bilinear'): + super().__init__() + + self.num_scales = num_scales + self.unsample_mode = upsample_mode + self.in_channels = in_channels + self.branch_channels = branch_channels + self.out_channels = out_channels + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.conv_cfg = conv_cfg + + self.scales = ModuleList([ + ConvModule( + in_channels, + branch_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **conv_cfg) + ]) + for i in range(1, num_scales - 1): + self.scales.append( + Sequential(*[ + nn.AvgPool2d( + kernel_size=kernel_sizes[i - 1], + stride=strides[i - 1], + padding=paddings[i - 1]), + ConvModule( + in_channels, + branch_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **conv_cfg) + ])) + self.scales.append( + Sequential(*[ + nn.AdaptiveAvgPool2d((1, 1)), + ConvModule( + in_channels, + branch_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **conv_cfg) + ])) + self.processes = ModuleList() + for i in range(num_scales - 1): + self.processes.append( + ConvModule( + branch_channels, + branch_channels, + kernel_size=3, + padding=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **conv_cfg)) + + self.compression = ConvModule( + branch_channels * num_scales, + out_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **conv_cfg) + + self.shortcut = ConvModule( + in_channels, + out_channels, + kernel_size=1, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + **conv_cfg) + + def forward(self, inputs: Tensor): + feats = [] + feats.append(self.scales[0](inputs)) + + for i in range(1, self.num_scales): + feat_up = F.interpolate( + self.scales[i](inputs), + size=inputs.shape[2:], + mode=self.unsample_mode) + feats.append(self.processes[i - 1](feat_up + feats[i - 1])) + + return self.compression(torch.cat(feats, + dim=1)) + self.shortcut(inputs) + + +class PAPPM(DAPPM): + """PAPPM module in `PIDNet `_. + + Args: + in_channels (int): Input channels. + branch_channels (int): Branch channels. + out_channels (int): Output channels. + num_scales (int): Number of scales. + kernel_sizes (list[int]): Kernel sizes of each scale. + strides (list[int]): Strides of each scale. + paddings (list[int]): Paddings of each scale. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN', momentum=0.1). + act_cfg (dict): Config dict for activation layer in ConvModule. + Default: dict(type='ReLU', inplace=True). + conv_cfg (dict): Config dict for convolution layer in ConvModule. + Default: dict(order=('norm', 'act', 'conv'), bias=False). + upsample_mode (str): Upsample mode. Default: 'bilinear'. + """ + + def __init__(self, + in_channels: int, + branch_channels: int, + out_channels: int, + num_scales: int, + kernel_sizes: List[int] = [5, 9, 17], + strides: List[int] = [2, 4, 8], + paddings: List[int] = [2, 4, 8], + norm_cfg: Dict = dict(type='BN', momentum=0.1), + act_cfg: Dict = dict(type='ReLU', inplace=True), + conv_cfg: Dict = dict( + order=('norm', 'act', 'conv'), bias=False), + upsample_mode: str = 'bilinear'): + super().__init__(in_channels, branch_channels, out_channels, + num_scales, kernel_sizes, strides, paddings, norm_cfg, + act_cfg, conv_cfg, upsample_mode) + + self.processes = ConvModule( + self.branch_channels * (self.num_scales - 1), + self.branch_channels * (self.num_scales - 1), + kernel_size=3, + padding=1, + groups=self.num_scales - 1, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + **self.conv_cfg) + + def forward(self, inputs: Tensor): + x_ = self.scales[0](inputs) + feats = [] + for i in range(1, self.num_scales): + feat_up = F.interpolate( + self.scales[i](inputs), + size=inputs.shape[2:], + mode=self.unsample_mode, + align_corners=False) + feats.append(feat_up + x_) + scale_out = self.processes(torch.cat(feats, dim=1)) + return self.compression(torch.cat([x_, scale_out], + dim=1)) + self.shortcut(inputs) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/res_layer.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/res_layer.py new file mode 100644 index 0000000..3dd7a6f --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/res_layer.py @@ -0,0 +1,96 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.cnn import build_conv_layer, build_norm_layer +from mmengine.model import Sequential +from torch import nn as nn + + +class ResLayer(Sequential): + """ResLayer to build ResNet style backbone. + + Args: + block (nn.Module): block used to build ResLayer. + inplanes (int): inplanes of block. + planes (int): planes of block. + num_blocks (int): number of blocks. + stride (int): stride of the first block. Default: 1 + avg_down (bool): Use AvgPool instead of stride conv when + downsampling in the bottleneck. Default: False + conv_cfg (dict): dictionary to construct and config conv layer. + Default: None + norm_cfg (dict): dictionary to construct and config norm layer. + Default: dict(type='BN') + multi_grid (int | None): Multi grid dilation rates of last + stage. Default: None + contract_dilation (bool): Whether contract first dilation of each layer + Default: False + """ + + def __init__(self, + block, + inplanes, + planes, + num_blocks, + stride=1, + dilation=1, + avg_down=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + multi_grid=None, + contract_dilation=False, + **kwargs): + self.block = block + + downsample = None + if stride != 1 or inplanes != planes * block.expansion: + downsample = [] + conv_stride = stride + if avg_down: + conv_stride = 1 + downsample.append( + nn.AvgPool2d( + kernel_size=stride, + stride=stride, + ceil_mode=True, + count_include_pad=False)) + downsample.extend([ + build_conv_layer( + conv_cfg, + inplanes, + planes * block.expansion, + kernel_size=1, + stride=conv_stride, + bias=False), + build_norm_layer(norm_cfg, planes * block.expansion)[1] + ]) + downsample = nn.Sequential(*downsample) + + layers = [] + if multi_grid is None: + if dilation > 1 and contract_dilation: + first_dilation = dilation // 2 + else: + first_dilation = dilation + else: + first_dilation = multi_grid[0] + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=stride, + dilation=first_dilation, + downsample=downsample, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs)) + inplanes = planes * block.expansion + for i in range(1, num_blocks): + layers.append( + block( + inplanes=inplanes, + planes=planes, + stride=1, + dilation=dilation if multi_grid is None else multi_grid[i], + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + **kwargs)) + super().__init__(*layers) diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/san_layers.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/san_layers.py new file mode 100644 index 0000000..2267686 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/san_layers.py @@ -0,0 +1,418 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Modified from https://github.com/MendelXu/SAN/blob/main/san/model/attn_helper.py # noqa: E501 +# Copyright (c) 2023 MendelXu. +# Licensed under the MIT License + +import warnings +from typing import Optional + +import torch +from mmcv.cnn.bricks.transformer import BaseTransformerLayer +from torch import Tensor, nn +from torch.nn import functional as F + + +def cross_attn_with_self_bias( + query: Tensor, + key: Tensor, + value: Tensor, + embed_dim_to_check: int, + num_heads: int, + in_proj_weight: Tensor, + in_proj_bias: Tensor, + bias_k: Optional[Tensor], + bias_v: Optional[Tensor], + add_zero_attn: bool, + dropout_p: float, + out_proj_weight: Tensor, + out_proj_bias: Tensor, + training: bool = True, + key_padding_mask: Optional[Tensor] = None, + need_weights: bool = True, + attn_mask: Optional[Tensor] = None, + use_separate_proj_weight: bool = False, + q_proj_weight: Optional[Tensor] = None, + k_proj_weight: Optional[Tensor] = None, + v_proj_weight: Optional[Tensor] = None, + static_k: Optional[Tensor] = None, + static_v: Optional[Tensor] = None, +): + """Forward function of multi-head attention. Modified from + multi_head_attention_forward in + https://github.com/pytorch/pytorch/blob/main/torch/nn/functional.py. + + Args: + query, key, value: map a query and a set of key-value pairs to an output. + See "Attention Is All You Need" for more details. + embed_dim_to_check: total dimension of the model. + num_heads: parallel attention heads. + in_proj_weight, in_proj_bias: input projection weight and bias. + bias_k, bias_v: bias of the key and value sequences to be added at dim=0. + add_zero_attn: add a new batch of zeros to the key and + value sequences at dim=1. + dropout_p: probability of an element to be zeroed. + out_proj_weight, out_proj_bias: the output projection weight and bias. + training: apply dropout if is ``True``. + key_padding_mask: if provided, specified padding elements in the key will + be ignored by the attention. This is an binary mask. When the value is True, + the corresponding value on the attention layer will be filled with -inf. + need_weights: output attn_output_weights. + Default: `True` + Note: `needs_weight` defaults to `True`, but should be set to `False` + For best performance when attention weights are not needed. + *Setting needs_weights to `True` + leads to a significant performance degradation.* + attn_mask: 2D mask that prevents attention to certain positions. A 2D mask will be broadcasted for all + the batches while a 3D mask allows to specify a different mask for the entries of each batch. + use_separate_proj_weight: the function accept the proj. weights for query, key, + and value in different forms. If false, in_proj_weight will be used, which is + a combination of q_proj_weight, k_proj_weight, v_proj_weight. + q_proj_weight, k_proj_weight, v_proj_weight, in_proj_bias: input projection weight and bias. + static_k, static_v: static key and value used for attention operators. + """ # noqa: E501 + tgt_len, bsz, embed_dim = query.size() + assert embed_dim == embed_dim_to_check + # allow MHA to have different sizes for the feature dimension + assert key.size(0) == value.size(0) and key.size(1) == value.size(1) + + head_dim = embed_dim // num_heads + assert head_dim * num_heads == embed_dim, \ + 'embed_dim must be divisible by num_heads' + scaling = float(head_dim)**-0.5 + + if not use_separate_proj_weight: + if (query is key or torch.equal( + query, key)) and (key is value or torch.equal(key, value)): + # self-attention + raise NotImplementedError('self-attention is not implemented') + + elif key is value or torch.equal(key, value): + # encoder-decoder attention + # This is inline in_proj function + # with in_proj_weight and in_proj_bias + _b = in_proj_bias + _start = 0 + _end = embed_dim + _w = in_proj_weight[_start:_end, :] + if _b is not None: + _b = _b[_start:_end] + q = F.linear(query, _w, _b) + + if key is None: + assert value is None + k = None + v = None + q_k = None + q_v = None + else: + # This is inline in_proj function with + # in_proj_weight and in_proj_bias + _b = in_proj_bias + _start = embed_dim + _end = None + _w = in_proj_weight[_start:, :] + if _b is not None: + _b = _b[_start:] + k, v = F.linear(key, _w, _b).chunk(2, dim=-1) + q_k, q_v = F.linear(query, _w, _b).chunk(2, dim=-1) + else: + # This is inline in_proj function with + # in_proj_weight and in_proj_bias + _b = in_proj_bias + _start = 0 + _end = embed_dim + _w = in_proj_weight[_start:_end, :] + if _b is not None: + _b = _b[_start:_end] + q = F.linear(query, _w, _b) + + # This is inline in_proj function with + # in_proj_weight and in_proj_bias + _b = in_proj_bias + _start = embed_dim + _end = embed_dim * 2 + _w = in_proj_weight[_start:_end, :] + if _b is not None: + _b = _b[_start:_end] + k = F.linear(key, _w, _b) + q_k = F.linear(query, _w, _b) + # This is inline in_proj function with + # in_proj_weight and in_proj_bias + _b = in_proj_bias + _start = embed_dim * 2 + _end = None + _w = in_proj_weight[_start:, :] + if _b is not None: + _b = _b[_start:] + v = F.linear(value, _w, _b) + q_v = F.linear(query, _w, _b) + else: + q_proj_weight_non_opt = \ + torch.jit._unwrap_optional(q_proj_weight) + len1, len2 = q_proj_weight_non_opt.size() + assert len1 == embed_dim and len2 == query.size(-1) + + k_proj_weight_non_opt = \ + torch.jit._unwrap_optional(k_proj_weight) + len1, len2 = k_proj_weight_non_opt.size() + assert len1 == embed_dim and len2 == key.size(-1) + + v_proj_weight_non_opt = \ + torch.jit._unwrap_optional(v_proj_weight) + len1, len2 = v_proj_weight_non_opt.size() + assert len1 == embed_dim and len2 == value.size(-1) + + if in_proj_bias is not None: + q = F.linear(query, q_proj_weight_non_opt, + in_proj_bias[0:embed_dim]) + k = F.linear(key, k_proj_weight_non_opt, + in_proj_bias[embed_dim:(embed_dim * 2)]) + v = F.linear(value, v_proj_weight_non_opt, + in_proj_bias[(embed_dim * 2):]) + else: + q = F.linear(query, q_proj_weight_non_opt, in_proj_bias) + k = F.linear(key, k_proj_weight_non_opt, in_proj_bias) + v = F.linear(value, v_proj_weight_non_opt, in_proj_bias) + q = q * scaling + + if attn_mask is not None: + assert ( + attn_mask.dtype == torch.float32 + or attn_mask.dtype == torch.float64 + or attn_mask.dtype == torch.float16 + or attn_mask.dtype == torch.uint8 or attn_mask.dtype == torch.bool + ), 'Only float, byte, and bool types are supported for ' \ + 'attn_mask, not {}'.format(attn_mask.dtype) + if attn_mask.dtype == torch.uint8: + warnings.warn('Byte tensor for attn_mask in nn.MultiheadAttention ' + 'is deprecated. Use bool tensor instead.') + attn_mask = attn_mask.to(torch.bool) + + if attn_mask.dim() == 2: + attn_mask = attn_mask.unsqueeze(0) + if list(attn_mask.size()) != [1, query.size(0), key.size(0)]: + raise RuntimeError( + 'The size of the 2D attn_mask is not correct.') + elif attn_mask.dim() == 3: + if list(attn_mask.size()) != [ + bsz * num_heads, + query.size(0), key.size(0) + ]: + raise RuntimeError( + 'The size of the 3D attn_mask is not correct.') + else: + raise RuntimeError( + "attn_mask's dimension {} is not supported".format( + attn_mask.dim())) + # attn_mask's dim is 3 now. + + # convert ByteTensor key_padding_mask to bool + if key_padding_mask is not None and key_padding_mask.dtype == torch.uint8: + warnings.warn( + 'Byte tensor for key_padding_mask in nn.MultiheadAttention ' + 'is deprecated. Use bool tensor instead.') + key_padding_mask = key_padding_mask.to(torch.bool) + + if bias_k is not None and bias_v is not None: + if static_k is None and static_v is None: + k = torch.cat([k, bias_k.repeat(1, bsz, 1)]) + v = torch.cat([v, bias_v.repeat(1, bsz, 1)]) + if attn_mask is not None: + attn_mask = F.pad(attn_mask, (0, 1)) + if key_padding_mask is not None: + key_padding_mask = F.pad(key_padding_mask, (0, 1)) + else: + assert static_k is None, 'bias cannot be added to static key.' + assert static_v is None, 'bias cannot be added to static value.' + else: + assert bias_k is None + assert bias_v is None + + q = q.contiguous().view(tgt_len, bsz * num_heads, head_dim).transpose(0, 1) + if k is not None: + k = k.contiguous().view(-1, bsz * num_heads, head_dim).transpose(0, 1) + q_k = q_k.contiguous().view(tgt_len, bsz * num_heads, + head_dim).transpose(0, 1) + if v is not None: + v = v.contiguous().view(-1, bsz * num_heads, head_dim).transpose(0, 1) + q_v = q_v.contiguous().view(tgt_len, bsz * num_heads, + head_dim).transpose(0, 1) + + if static_k is not None: + assert static_k.size(0) == bsz * num_heads + assert static_k.size(2) == head_dim + k = static_k + + if static_v is not None: + assert static_v.size(0) == bsz * num_heads + assert static_v.size(2) == head_dim + v = static_v + + src_len = k.size(1) + + if key_padding_mask is not None: + assert key_padding_mask.size(0) == bsz + assert key_padding_mask.size(1) == src_len + + if add_zero_attn: + src_len += 1 + k = torch.cat( + [ + k, + torch.zeros( + (k.size(0), 1) + k.size()[2:], + dtype=k.dtype, + device=k.device), + ], + dim=1, + ) + v = torch.cat( + [ + v, + torch.zeros( + (v.size(0), 1) + v.size()[2:], + dtype=v.dtype, + device=v.device), + ], + dim=1, + ) + if attn_mask is not None: + attn_mask = F.pad(attn_mask, (0, 1)) + if key_padding_mask is not None: + key_padding_mask = F.pad(key_padding_mask, (0, 1)) + + attn_output_weights = torch.bmm(q, k.transpose(1, 2)) + assert list( + attn_output_weights.size()) == [bsz * num_heads, tgt_len, src_len] + + if attn_mask is not None: + if attn_mask.dtype == torch.bool: + attn_output_weights.masked_fill_(attn_mask, float('-inf')) + else: + attn_output_weights += attn_mask + + if key_padding_mask is not None: + attn_output_weights = attn_output_weights.view(bsz, num_heads, tgt_len, + src_len) + attn_output_weights = attn_output_weights.masked_fill( + key_padding_mask.unsqueeze(1).unsqueeze(2), + float('-inf'), + ) + attn_output_weights = attn_output_weights.view(bsz * num_heads, + tgt_len, src_len) + # attn_out_weights: [bsz * num_heads, tgt_len, src_len] + # ->[bsz * num_heads, tgt_len, src_len+1] + self_weight = (q * q_k).sum( + dim=-1, keepdim=True) # [bsz * num_heads, tgt_len, 1] + total_attn_output_weights = torch.cat([attn_output_weights, self_weight], + dim=-1) + total_attn_output_weights = F.softmax(total_attn_output_weights, dim=-1) + total_attn_output_weights = F.dropout( + total_attn_output_weights, p=dropout_p, training=training) + attn_output_weights = \ + total_attn_output_weights[:, :, : -1] + # [bsz * num_heads, tgt_len, src_len] + self_weight = \ + total_attn_output_weights[:, :, -1:] # [bsz * num_heads, tgt_len, 1] + + attn_output = torch.bmm(attn_output_weights, + v) # [bsz * num_heads, tgt_len, head_dim] + attn_output = (attn_output + self_weight * q_v + ) # [bsz * num_heads, tgt_len, head_dim] + assert list(attn_output.size()) == [bsz * num_heads, tgt_len, head_dim] + attn_output = attn_output.transpose(0, 1).contiguous().view( + tgt_len, bsz, embed_dim) + attn_output = F.linear(attn_output, out_proj_weight, out_proj_bias) + + if need_weights: + # average attention weights over heads + attn_output_weights = attn_output_weights.view(bsz, num_heads, tgt_len, + src_len) + return attn_output, attn_output_weights # .sum(dim=1) / num_heads + else: + return attn_output, None + + +def cross_attn_layer(tf_layer: BaseTransformerLayer, x, mem, attn_bias): + """Implementation of transformer layer with cross attention. The cross + attention shares the embedding weights with self-attention of tf_layer. + Args: + tf_layer: (TransformerEncoderLayer): The Module of transformer layer. + x (Tensor): query [K,N,C] + mem (Tensor): key and value [L,N,C] + attn_bias (Tensor): attention bias [N*num_head,K,L] + + Return: + x (Tensor): cross attention output [K,N,C] + """ + self_attn_layer = tf_layer.attentions[0].attn + attn_layer_paras = { + 'embed_dim_to_check': self_attn_layer.embed_dim, + 'num_heads': self_attn_layer.num_heads, + 'in_proj_weight': self_attn_layer.in_proj_weight, + 'in_proj_bias': self_attn_layer.in_proj_bias, + 'bias_k': self_attn_layer.bias_k, + 'bias_v': self_attn_layer.bias_v, + 'add_zero_attn': self_attn_layer.add_zero_attn, + 'dropout_p': self_attn_layer.dropout, + 'out_proj_weight': self_attn_layer.out_proj.weight, + 'out_proj_bias': self_attn_layer.out_proj.bias, + 'training': self_attn_layer.training + } + + q_x = tf_layer.norms[0](x) + k_x = v_x = tf_layer.norms[0](mem) + x = x + cross_attn_with_self_bias( + q_x, + k_x, + v_x, + attn_mask=attn_bias, + need_weights=False, + **attn_layer_paras)[0] + x = tf_layer.ffns[0](tf_layer.norms[1](x), identity=x) + return x + + +class LayerNorm2d(nn.Module): + """A LayerNorm variant, popularized by Transformers, that performs point- + wise mean and variance normalization over the channel dimension for inputs + that have shape (batch_size, channels, height, width). + + https://github.com/facebookresearch/ConvNeXt/blob/d1fa8f6fef0a165b27399986cc2bdacc92777e40/models/convnext.py#L119 # noqa B950 + """ + + def __init__(self, normalized_shape, eps=1e-6): + super().__init__() + self.weight = nn.Parameter(torch.ones(normalized_shape)) + self.bias = nn.Parameter(torch.zeros(normalized_shape)) + self.eps = eps + self.normalized_shape = (normalized_shape, ) + + def forward(self, x: torch.Tensor): + u = x.mean(1, keepdim=True) + s = (x - u).pow(2).mean(1, keepdim=True) + x = (x - u) / torch.sqrt(s + self.eps) + x = self.weight[:, None, None] * x + self.bias[:, None, None] + return x + + +class MLP(nn.Module): + """Very simple multi-layer perceptron (also called FFN)""" + + def __init__(self, + input_dim, + hidden_dim, + output_dim, + num_layers, + affine_func=nn.Linear): + super().__init__() + self.num_layers = num_layers + h = [hidden_dim] * (num_layers - 1) + self.layers = nn.ModuleList( + affine_func(n, k) + for n, k in zip([input_dim] + h, h + [output_dim])) + + def forward(self, x: torch.Tensor): + for i, layer in enumerate(self.layers): + x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x) + return x diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/se_layer.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/se_layer.py new file mode 100644 index 0000000..0ff632c --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/se_layer.py @@ -0,0 +1,58 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.utils import is_tuple_of + +from .make_divisible import make_divisible + + +class SELayer(nn.Module): + """Squeeze-and-Excitation Module. + + Args: + channels (int): The input (and output) channels of the SE layer. + ratio (int): Squeeze ratio in SELayer, the intermediate channel will be + ``int(channels/ratio)``. Default: 16. + conv_cfg (None or dict): Config dict for convolution layer. + Default: None, which means using conv2d. + act_cfg (dict or Sequence[dict]): Config dict for activation layer. + If act_cfg is a dict, two activation layers will be configured + by this dict. If act_cfg is a sequence of dicts, the first + activation layer will be configured by the first dict and the + second activation layer will be configured by the second dict. + Default: (dict(type='ReLU'), dict(type='HSigmoid', bias=3.0, + divisor=6.0)). + """ + + def __init__(self, + channels, + ratio=16, + conv_cfg=None, + act_cfg=(dict(type='ReLU'), + dict(type='HSigmoid', bias=3.0, divisor=6.0))): + super().__init__() + if isinstance(act_cfg, dict): + act_cfg = (act_cfg, act_cfg) + assert len(act_cfg) == 2 + assert is_tuple_of(act_cfg, dict) + self.global_avgpool = nn.AdaptiveAvgPool2d(1) + self.conv1 = ConvModule( + in_channels=channels, + out_channels=make_divisible(channels // ratio, 8), + kernel_size=1, + stride=1, + conv_cfg=conv_cfg, + act_cfg=act_cfg[0]) + self.conv2 = ConvModule( + in_channels=make_divisible(channels // ratio, 8), + out_channels=channels, + kernel_size=1, + stride=1, + conv_cfg=conv_cfg, + act_cfg=act_cfg[1]) + + def forward(self, x): + out = self.global_avgpool(x) + out = self.conv1(out) + out = self.conv2(out) + return x * out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/self_attention_block.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/self_attention_block.py new file mode 100644 index 0000000..5bb6e82 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/self_attention_block.py @@ -0,0 +1,161 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.cnn import ConvModule +from mmengine.model.weight_init import constant_init +from torch import nn as nn +from torch.nn import functional as F + + +class SelfAttentionBlock(nn.Module): + """General self-attention block/non-local block. + + Please refer to https://arxiv.org/abs/1706.03762 for details about key, + query and value. + + Args: + key_in_channels (int): Input channels of key feature. + query_in_channels (int): Input channels of query feature. + channels (int): Output channels of key/query transform. + out_channels (int): Output channels. + share_key_query (bool): Whether share projection weight between key + and query projection. + query_downsample (nn.Module): Query downsample module. + key_downsample (nn.Module): Key downsample module. + key_query_num_convs (int): Number of convs for key/query projection. + value_num_convs (int): Number of convs for value projection. + matmul_norm (bool): Whether normalize attention map with sqrt of + channels + with_out (bool): Whether use out projection. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict|None): Config of activation layers. + """ + + def __init__(self, key_in_channels, query_in_channels, channels, + out_channels, share_key_query, query_downsample, + key_downsample, key_query_num_convs, value_out_num_convs, + key_query_norm, value_out_norm, matmul_norm, with_out, + conv_cfg, norm_cfg, act_cfg): + super().__init__() + if share_key_query: + assert key_in_channels == query_in_channels + self.key_in_channels = key_in_channels + self.query_in_channels = query_in_channels + self.out_channels = out_channels + self.channels = channels + self.share_key_query = share_key_query + self.conv_cfg = conv_cfg + self.norm_cfg = norm_cfg + self.act_cfg = act_cfg + self.key_project = self.build_project( + key_in_channels, + channels, + num_convs=key_query_num_convs, + use_conv_module=key_query_norm, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + if share_key_query: + self.query_project = self.key_project + else: + self.query_project = self.build_project( + query_in_channels, + channels, + num_convs=key_query_num_convs, + use_conv_module=key_query_norm, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.value_project = self.build_project( + key_in_channels, + channels if with_out else out_channels, + num_convs=value_out_num_convs, + use_conv_module=value_out_norm, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + if with_out: + self.out_project = self.build_project( + channels, + out_channels, + num_convs=value_out_num_convs, + use_conv_module=value_out_norm, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + else: + self.out_project = None + + self.query_downsample = query_downsample + self.key_downsample = key_downsample + self.matmul_norm = matmul_norm + + self.init_weights() + + def init_weights(self): + """Initialize weight of later layer.""" + if self.out_project is not None: + if not isinstance(self.out_project, ConvModule): + constant_init(self.out_project, 0) + + def build_project(self, in_channels, channels, num_convs, use_conv_module, + conv_cfg, norm_cfg, act_cfg): + """Build projection layer for key/query/value/out.""" + if use_conv_module: + convs = [ + ConvModule( + in_channels, + channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + ] + for _ in range(num_convs - 1): + convs.append( + ConvModule( + channels, + channels, + 1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg)) + else: + convs = [nn.Conv2d(in_channels, channels, 1)] + for _ in range(num_convs - 1): + convs.append(nn.Conv2d(channels, channels, 1)) + if len(convs) > 1: + convs = nn.Sequential(*convs) + else: + convs = convs[0] + return convs + + def forward(self, query_feats, key_feats): + """Forward function.""" + batch_size = query_feats.size(0) + query = self.query_project(query_feats) + if self.query_downsample is not None: + query = self.query_downsample(query) + query = query.reshape(*query.shape[:2], -1) + query = query.permute(0, 2, 1).contiguous() + + key = self.key_project(key_feats) + value = self.value_project(key_feats) + if self.key_downsample is not None: + key = self.key_downsample(key) + value = self.key_downsample(value) + key = key.reshape(*key.shape[:2], -1) + value = value.reshape(*value.shape[:2], -1) + value = value.permute(0, 2, 1).contiguous() + + sim_map = torch.matmul(query, key) + if self.matmul_norm: + sim_map = (self.channels**-.5) * sim_map + sim_map = F.softmax(sim_map, dim=-1) + + context = torch.matmul(sim_map, value) + context = context.permute(0, 2, 1).contiguous() + context = context.reshape(batch_size, -1, *query_feats.shape[2:]) + if self.out_project is not None: + context = self.out_project(context) + return context diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/shape_convert.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/shape_convert.py new file mode 100644 index 0000000..cce1e22 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/shape_convert.py @@ -0,0 +1,107 @@ +# Copyright (c) OpenMMLab. All rights reserved. +def nlc_to_nchw(x, hw_shape): + """Convert [N, L, C] shape tensor to [N, C, H, W] shape tensor. + + Args: + x (Tensor): The input tensor of shape [N, L, C] before conversion. + hw_shape (Sequence[int]): The height and width of output feature map. + + Returns: + Tensor: The output tensor of shape [N, C, H, W] after conversion. + """ + H, W = hw_shape + assert len(x.shape) == 3 + B, L, C = x.shape + assert L == H * W, 'The seq_len doesn\'t match H, W' + return x.transpose(1, 2).reshape(B, C, H, W) + + +def nchw_to_nlc(x): + """Flatten [N, C, H, W] shape tensor to [N, L, C] shape tensor. + + Args: + x (Tensor): The input tensor of shape [N, C, H, W] before conversion. + + Returns: + Tensor: The output tensor of shape [N, L, C] after conversion. + """ + assert len(x.shape) == 4 + return x.flatten(2).transpose(1, 2).contiguous() + + +def nchw2nlc2nchw(module, x, contiguous=False, **kwargs): + """Flatten [N, C, H, W] shape tensor `x` to [N, L, C] shape tensor. Use the + reshaped tensor as the input of `module`, and the convert the output of + `module`, whose shape is. + + [N, L, C], to [N, C, H, W]. + + Args: + module (Callable): A callable object the takes a tensor + with shape [N, L, C] as input. + x (Tensor): The input tensor of shape [N, C, H, W]. + contiguous: + contiguous (Bool): Whether to make the tensor contiguous + after each shape transform. + + Returns: + Tensor: The output tensor of shape [N, C, H, W]. + + Example: + >>> import torch + >>> import torch.nn as nn + >>> norm = nn.LayerNorm(4) + >>> feature_map = torch.rand(4, 4, 5, 5) + >>> output = nchw2nlc2nchw(norm, feature_map) + """ + B, C, H, W = x.shape + if not contiguous: + x = x.flatten(2).transpose(1, 2) + x = module(x, **kwargs) + x = x.transpose(1, 2).reshape(B, C, H, W) + else: + x = x.flatten(2).transpose(1, 2).contiguous() + x = module(x, **kwargs) + x = x.transpose(1, 2).reshape(B, C, H, W).contiguous() + return x + + +def nlc2nchw2nlc(module, x, hw_shape, contiguous=False, **kwargs): + """Convert [N, L, C] shape tensor `x` to [N, C, H, W] shape tensor. Use the + reshaped tensor as the input of `module`, and convert the output of + `module`, whose shape is. + + [N, C, H, W], to [N, L, C]. + + Args: + module (Callable): A callable object the takes a tensor + with shape [N, C, H, W] as input. + x (Tensor): The input tensor of shape [N, L, C]. + hw_shape: (Sequence[int]): The height and width of the + feature map with shape [N, C, H, W]. + contiguous (Bool): Whether to make the tensor contiguous + after each shape transform. + + Returns: + Tensor: The output tensor of shape [N, L, C]. + + Example: + >>> import torch + >>> import torch.nn as nn + >>> conv = nn.Conv2d(16, 16, 3, 1, 1) + >>> feature_map = torch.rand(4, 25, 16) + >>> output = nlc2nchw2nlc(conv, feature_map, (5, 5)) + """ + H, W = hw_shape + assert len(x.shape) == 3 + B, L, C = x.shape + assert L == H * W, 'The seq_len doesn\'t match H, W' + if not contiguous: + x = x.transpose(1, 2).reshape(B, C, H, W) + x = module(x, **kwargs) + x = x.flatten(2).transpose(1, 2) + else: + x = x.transpose(1, 2).reshape(B, C, H, W).contiguous() + x = module(x, **kwargs) + x = x.flatten(2).transpose(1, 2).contiguous() + return x diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/up_conv_block.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/up_conv_block.py new file mode 100644 index 0000000..4fa3b59 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/up_conv_block.py @@ -0,0 +1,102 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule, build_upsample_layer + + +class UpConvBlock(nn.Module): + """Upsample convolution block in decoder for UNet. + + This upsample convolution block consists of one upsample module + followed by one convolution block. The upsample module expands the + high-level low-resolution feature map and the convolution block fuses + the upsampled high-level low-resolution feature map and the low-level + high-resolution feature map from encoder. + + Args: + conv_block (nn.Sequential): Sequential of convolutional layers. + in_channels (int): Number of input channels of the high-level + skip_channels (int): Number of input channels of the low-level + high-resolution feature map from encoder. + out_channels (int): Number of output channels. + num_convs (int): Number of convolutional layers in the conv_block. + Default: 2. + stride (int): Stride of convolutional layer in conv_block. Default: 1. + dilation (int): Dilation rate of convolutional layer in conv_block. + Default: 1. + with_cp (bool): Use checkpoint or not. Using checkpoint will save some + memory while slowing down the training speed. Default: False. + conv_cfg (dict | None): Config dict for convolution layer. + Default: None. + norm_cfg (dict | None): Config dict for normalization layer. + Default: dict(type='BN'). + act_cfg (dict | None): Config dict for activation layer in ConvModule. + Default: dict(type='ReLU'). + upsample_cfg (dict): The upsample config of the upsample module in + decoder. Default: dict(type='InterpConv'). If the size of + high-level feature map is the same as that of skip feature map + (low-level feature map from encoder), it does not need upsample the + high-level feature map and the upsample_cfg is None. + dcn (bool): Use deformable convolution in convolutional layer or not. + Default: None. + plugins (dict): plugins for convolutional layers. Default: None. + """ + + def __init__(self, + conv_block, + in_channels, + skip_channels, + out_channels, + num_convs=2, + stride=1, + dilation=1, + with_cp=False, + conv_cfg=None, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + upsample_cfg=dict(type='InterpConv'), + dcn=None, + plugins=None): + super().__init__() + assert dcn is None, 'Not implemented yet.' + assert plugins is None, 'Not implemented yet.' + + self.conv_block = conv_block( + in_channels=2 * skip_channels, + out_channels=out_channels, + num_convs=num_convs, + stride=stride, + dilation=dilation, + with_cp=with_cp, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + dcn=None, + plugins=None) + if upsample_cfg is not None: + self.upsample = build_upsample_layer( + cfg=upsample_cfg, + in_channels=in_channels, + out_channels=skip_channels, + with_cp=with_cp, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + else: + self.upsample = ConvModule( + in_channels, + skip_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + + def forward(self, skip, x): + """Forward function.""" + + x = self.upsample(x) + out = torch.cat([skip, x], dim=1) + out = self.conv_block(out) + + return out diff --git a/Seg_All_In_One_MMSeg/mmseg/models/utils/wrappers.py b/Seg_All_In_One_MMSeg/mmseg/models/utils/wrappers.py new file mode 100644 index 0000000..abbd0c0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/models/utils/wrappers.py @@ -0,0 +1,51 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch.nn as nn +import torch.nn.functional as F + + +def resize(input, + size=None, + scale_factor=None, + mode='nearest', + align_corners=None, + warning=True): + if warning: + if size is not None and align_corners: + input_h, input_w = tuple(int(x) for x in input.shape[2:]) + output_h, output_w = tuple(int(x) for x in size) + if output_h > input_h or output_w > output_h: + if ((output_h > 1 and output_w > 1 and input_h > 1 + and input_w > 1) and (output_h - 1) % (input_h - 1) + and (output_w - 1) % (input_w - 1)): + warnings.warn( + f'When align_corners={align_corners}, ' + 'the output would more aligned if ' + f'input size {(input_h, input_w)} is `x+1` and ' + f'out size {(output_h, output_w)} is `nx+1`') + return F.interpolate(input, size, scale_factor, mode, align_corners) + + +class Upsample(nn.Module): + + def __init__(self, + size=None, + scale_factor=None, + mode='nearest', + align_corners=None): + super().__init__() + self.size = size + if isinstance(scale_factor, tuple): + self.scale_factor = tuple(float(factor) for factor in scale_factor) + else: + self.scale_factor = float(scale_factor) if scale_factor else None + self.mode = mode + self.align_corners = align_corners + + def forward(self, x): + if not self.size: + size = [int(t * self.scale_factor) for t in x.shape[-2:]] + else: + size = self.size + return resize(x, size, None, self.mode, self.align_corners) diff --git a/Seg_All_In_One_MMSeg/mmseg/registry/__init__.py b/Seg_All_In_One_MMSeg/mmseg/registry/__init__.py new file mode 100644 index 0000000..ee514d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/registry/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .registry import (DATA_SAMPLERS, DATASETS, EVALUATOR, HOOKS, INFERENCERS, + LOG_PROCESSORS, LOOPS, METRICS, MODEL_WRAPPERS, MODELS, + OPTIM_WRAPPER_CONSTRUCTORS, OPTIM_WRAPPERS, OPTIMIZERS, + PARAM_SCHEDULERS, RUNNER_CONSTRUCTORS, RUNNERS, + TASK_UTILS, TRANSFORMS, VISBACKENDS, VISUALIZERS, + WEIGHT_INITIALIZERS) + +__all__ = [ + 'HOOKS', 'DATASETS', 'DATA_SAMPLERS', 'TRANSFORMS', 'MODELS', + 'WEIGHT_INITIALIZERS', 'OPTIMIZERS', 'OPTIM_WRAPPER_CONSTRUCTORS', + 'TASK_UTILS', 'PARAM_SCHEDULERS', 'METRICS', 'MODEL_WRAPPERS', + 'VISBACKENDS', 'VISUALIZERS', 'RUNNERS', 'RUNNER_CONSTRUCTORS', 'LOOPS', + 'EVALUATOR', 'LOG_PROCESSORS', 'OPTIM_WRAPPERS', 'INFERENCERS' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/registry/registry.py b/Seg_All_In_One_MMSeg/mmseg/registry/registry.py new file mode 100644 index 0000000..37b6a77 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/registry/registry.py @@ -0,0 +1,118 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""MMSegmentation provides 21 registry nodes to support using modules across +projects. Each node is a child of the root registry in MMEngine. + +More details can be found at +https://mmengine.readthedocs.io/en/latest/advanced_tutorials/registry.html. +""" + +from mmengine.registry import DATA_SAMPLERS as MMENGINE_DATA_SAMPLERS +from mmengine.registry import DATASETS as MMENGINE_DATASETS +from mmengine.registry import EVALUATOR as MMENGINE_EVALUATOR +from mmengine.registry import HOOKS as MMENGINE_HOOKS +from mmengine.registry import INFERENCERS as MMENGINE_INFERENCERS +from mmengine.registry import LOG_PROCESSORS as MMENGINE_LOG_PROCESSORS +from mmengine.registry import LOOPS as MMENGINE_LOOPS +from mmengine.registry import METRICS as MMENGINE_METRICS +from mmengine.registry import MODEL_WRAPPERS as MMENGINE_MODEL_WRAPPERS +from mmengine.registry import MODELS as MMENGINE_MODELS +from mmengine.registry import \ + OPTIM_WRAPPER_CONSTRUCTORS as MMENGINE_OPTIM_WRAPPER_CONSTRUCTORS +from mmengine.registry import OPTIM_WRAPPERS as MMENGINE_OPTIM_WRAPPERS +from mmengine.registry import OPTIMIZERS as MMENGINE_OPTIMIZERS +from mmengine.registry import PARAM_SCHEDULERS as MMENGINE_PARAM_SCHEDULERS +from mmengine.registry import \ + RUNNER_CONSTRUCTORS as MMENGINE_RUNNER_CONSTRUCTORS +from mmengine.registry import RUNNERS as MMENGINE_RUNNERS +from mmengine.registry import TASK_UTILS as MMENGINE_TASK_UTILS +from mmengine.registry import TRANSFORMS as MMENGINE_TRANSFORMS +from mmengine.registry import VISBACKENDS as MMENGINE_VISBACKENDS +from mmengine.registry import VISUALIZERS as MMENGINE_VISUALIZERS +from mmengine.registry import \ + WEIGHT_INITIALIZERS as MMENGINE_WEIGHT_INITIALIZERS +from mmengine.registry import Registry + +# manage all kinds of runners like `EpochBasedRunner` and `IterBasedRunner` +RUNNERS = Registry('runner', parent=MMENGINE_RUNNERS) +# manage runner constructors that define how to initialize runners +RUNNER_CONSTRUCTORS = Registry( + 'runner constructor', parent=MMENGINE_RUNNER_CONSTRUCTORS) +# manage all kinds of loops like `EpochBasedTrainLoop` +LOOPS = Registry('loop', parent=MMENGINE_LOOPS) +# manage all kinds of hooks like `CheckpointHook` +HOOKS = Registry( + 'hook', parent=MMENGINE_HOOKS, locations=['mmseg.engine.hooks']) + +# manage data-related modules +DATASETS = Registry( + 'dataset', parent=MMENGINE_DATASETS, locations=['mmseg.datasets']) +DATA_SAMPLERS = Registry('data sampler', parent=MMENGINE_DATA_SAMPLERS) +TRANSFORMS = Registry( + 'transform', + parent=MMENGINE_TRANSFORMS, + locations=['mmseg.datasets.transforms']) + +# mangage all kinds of modules inheriting `nn.Module` +MODELS = Registry('model', parent=MMENGINE_MODELS, locations=['mmseg.models']) +# mangage all kinds of model wrappers like 'MMDistributedDataParallel' +MODEL_WRAPPERS = Registry( + 'model_wrapper', + parent=MMENGINE_MODEL_WRAPPERS, + locations=['mmseg.models']) +# mangage all kinds of weight initialization modules like `Uniform` +WEIGHT_INITIALIZERS = Registry( + 'weight initializer', + parent=MMENGINE_WEIGHT_INITIALIZERS, + locations=['mmseg.models']) + +# mangage all kinds of optimizers like `SGD` and `Adam` +OPTIMIZERS = Registry( + 'optimizer', + parent=MMENGINE_OPTIMIZERS, + locations=['mmseg.engine.optimizers']) +# manage optimizer wrapper +OPTIM_WRAPPERS = Registry( + 'optim_wrapper', + parent=MMENGINE_OPTIM_WRAPPERS, + locations=['mmseg.engine.optimizers']) +# manage constructors that customize the optimization hyperparameters. +OPTIM_WRAPPER_CONSTRUCTORS = Registry( + 'optimizer wrapper constructor', + parent=MMENGINE_OPTIM_WRAPPER_CONSTRUCTORS, + locations=['mmseg.engine.optimizers']) +# mangage all kinds of parameter schedulers like `MultiStepLR` +PARAM_SCHEDULERS = Registry( + 'parameter scheduler', + parent=MMENGINE_PARAM_SCHEDULERS, + locations=['mmseg.engine.schedulers']) + +# manage all kinds of metrics +METRICS = Registry( + 'metric', parent=MMENGINE_METRICS, locations=['mmseg.evaluation']) +# manage evaluator +EVALUATOR = Registry( + 'evaluator', parent=MMENGINE_EVALUATOR, locations=['mmseg.evaluation']) + +# manage task-specific modules like ohem pixel sampler +TASK_UTILS = Registry( + 'task util', parent=MMENGINE_TASK_UTILS, locations=['mmseg.models']) + +# manage visualizer +VISUALIZERS = Registry( + 'visualizer', + parent=MMENGINE_VISUALIZERS, + locations=['mmseg.visualization']) +# manage visualizer backend +VISBACKENDS = Registry( + 'vis_backend', + parent=MMENGINE_VISBACKENDS, + locations=['mmseg.visualization']) + +# manage logprocessor +LOG_PROCESSORS = Registry( + 'log_processor', + parent=MMENGINE_LOG_PROCESSORS, + locations=['mmseg.visualization']) + +# manage inferencer +INFERENCERS = Registry('inferencer', parent=MMENGINE_INFERENCERS) diff --git a/Seg_All_In_One_MMSeg/mmseg/structures/__init__.py b/Seg_All_In_One_MMSeg/mmseg/structures/__init__.py new file mode 100644 index 0000000..63d118d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/structures/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .sampler import BasePixelSampler, OHEMPixelSampler, build_pixel_sampler +from .seg_data_sample import SegDataSample + +__all__ = [ + 'SegDataSample', 'BasePixelSampler', 'OHEMPixelSampler', + 'build_pixel_sampler' +] diff --git a/Seg_All_In_One_MMSeg/mmseg/structures/sampler/__init__.py b/Seg_All_In_One_MMSeg/mmseg/structures/sampler/__init__.py new file mode 100644 index 0000000..91d762d --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/structures/sampler/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .base_pixel_sampler import BasePixelSampler +from .builder import build_pixel_sampler +from .ohem_pixel_sampler import OHEMPixelSampler + +__all__ = ['build_pixel_sampler', 'BasePixelSampler', 'OHEMPixelSampler'] diff --git a/Seg_All_In_One_MMSeg/mmseg/structures/sampler/base_pixel_sampler.py b/Seg_All_In_One_MMSeg/mmseg/structures/sampler/base_pixel_sampler.py new file mode 100644 index 0000000..03672cd --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/structures/sampler/base_pixel_sampler.py @@ -0,0 +1,13 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from abc import ABCMeta, abstractmethod + + +class BasePixelSampler(metaclass=ABCMeta): + """Base class of pixel sampler.""" + + def __init__(self, **kwargs): + pass + + @abstractmethod + def sample(self, seg_logit, seg_label): + """Placeholder for sample function.""" diff --git a/Seg_All_In_One_MMSeg/mmseg/structures/sampler/builder.py b/Seg_All_In_One_MMSeg/mmseg/structures/sampler/builder.py new file mode 100644 index 0000000..48e1479 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/structures/sampler/builder.py @@ -0,0 +1,14 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +from mmseg.registry import TASK_UTILS + +PIXEL_SAMPLERS = TASK_UTILS + + +def build_pixel_sampler(cfg, **default_args): + """Build pixel sampler for segmentation map.""" + warnings.warn( + '``build_pixel_sampler`` would be deprecated soon, please use ' + '``mmseg.registry.TASK_UTILS.build()`` ') + return TASK_UTILS.build(cfg, default_args=default_args) diff --git a/Seg_All_In_One_MMSeg/mmseg/structures/sampler/ohem_pixel_sampler.py b/Seg_All_In_One_MMSeg/mmseg/structures/sampler/ohem_pixel_sampler.py new file mode 100644 index 0000000..a974273 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/structures/sampler/ohem_pixel_sampler.py @@ -0,0 +1,85 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from .base_pixel_sampler import BasePixelSampler +from .builder import PIXEL_SAMPLERS + + +@PIXEL_SAMPLERS.register_module() +class OHEMPixelSampler(BasePixelSampler): + """Online Hard Example Mining Sampler for segmentation. + + Args: + context (nn.Module): The context of sampler, subclass of + :obj:`BaseDecodeHead`. + thresh (float, optional): The threshold for hard example selection. + Below which, are prediction with low confidence. If not + specified, the hard examples will be pixels of top ``min_kept`` + loss. Default: None. + min_kept (int, optional): The minimum number of predictions to keep. + Default: 100000. + """ + + def __init__(self, context, thresh=None, min_kept=100000): + super().__init__() + self.context = context + assert min_kept > 1 + self.thresh = thresh + self.min_kept = min_kept + + def sample(self, seg_logit, seg_label): + """Sample pixels that have high loss or with low prediction confidence. + + Args: + seg_logit (torch.Tensor): segmentation logits, shape (N, C, H, W) + seg_label (torch.Tensor): segmentation label, shape (N, 1, H, W) + + Returns: + torch.Tensor: segmentation weight, shape (N, H, W) + """ + with torch.no_grad(): + assert seg_logit.shape[2:] == seg_label.shape[2:] + assert seg_label.shape[1] == 1 + seg_label = seg_label.squeeze(1).long() + batch_kept = self.min_kept * seg_label.size(0) + valid_mask = seg_label != self.context.ignore_index + seg_weight = seg_logit.new_zeros(size=seg_label.size()) + valid_seg_weight = seg_weight[valid_mask] + if self.thresh is not None: + seg_prob = F.softmax(seg_logit, dim=1) + + tmp_seg_label = seg_label.clone().unsqueeze(1) + tmp_seg_label[tmp_seg_label == self.context.ignore_index] = 0 + seg_prob = seg_prob.gather(1, tmp_seg_label).squeeze(1) + sort_prob, sort_indices = seg_prob[valid_mask].sort() + + if sort_prob.numel() > 0: + min_threshold = sort_prob[min(batch_kept, + sort_prob.numel() - 1)] + else: + min_threshold = 0.0 + threshold = max(min_threshold, self.thresh) + valid_seg_weight[seg_prob[valid_mask] < threshold] = 1. + else: + if not isinstance(self.context.loss_decode, nn.ModuleList): + losses_decode = [self.context.loss_decode] + else: + losses_decode = self.context.loss_decode + losses = 0.0 + for loss_module in losses_decode: + losses += loss_module( + seg_logit, + seg_label, + weight=None, + ignore_index=self.context.ignore_index, + reduction_override='none') + + # faster than topk according to https://github.com/pytorch/pytorch/issues/22812 # noqa + _, sort_indices = losses[valid_mask].sort(descending=True) + valid_seg_weight[sort_indices[:batch_kept]] = 1. + + seg_weight[valid_mask] = valid_seg_weight + + return seg_weight diff --git a/Seg_All_In_One_MMSeg/mmseg/structures/seg_data_sample.py b/Seg_All_In_One_MMSeg/mmseg/structures/seg_data_sample.py new file mode 100644 index 0000000..ce68b54 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/structures/seg_data_sample.py @@ -0,0 +1,92 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.structures import BaseDataElement, PixelData + + +class SegDataSample(BaseDataElement): + """A data structure interface of MMSegmentation. They are used as + interfaces between different components. + + The attributes in ``SegDataSample`` are divided into several parts: + + - ``gt_sem_seg``(PixelData): Ground truth of semantic segmentation. + - ``pred_sem_seg``(PixelData): Prediction of semantic segmentation. + - ``seg_logits``(PixelData): Predicted logits of semantic segmentation. + + Examples: + >>> import torch + >>> import numpy as np + >>> from mmengine.structures import PixelData + >>> from mmseg.structures import SegDataSample + + >>> data_sample = SegDataSample() + >>> img_meta = dict(img_shape=(4, 4, 3), + ... pad_shape=(4, 4, 3)) + >>> gt_segmentations = PixelData(metainfo=img_meta) + >>> gt_segmentations.data = torch.randint(0, 2, (1, 4, 4)) + >>> data_sample.gt_sem_seg = gt_segmentations + >>> assert 'img_shape' in data_sample.gt_sem_seg.metainfo_keys() + >>> data_sample.gt_sem_seg.shape + (4, 4) + >>> print(data_sample) + + ) at 0x1c2aae44d60> + + >>> data_sample = SegDataSample() + >>> gt_sem_seg_data = dict(sem_seg=torch.rand(1, 4, 4)) + >>> gt_sem_seg = PixelData(**gt_sem_seg_data) + >>> data_sample.gt_sem_seg = gt_sem_seg + >>> assert 'gt_sem_seg' in data_sample + >>> assert 'sem_seg' in data_sample.gt_sem_seg + """ + + @property + def gt_sem_seg(self) -> PixelData: + return self._gt_sem_seg + + @gt_sem_seg.setter + def gt_sem_seg(self, value: PixelData) -> None: + self.set_field(value, '_gt_sem_seg', dtype=PixelData) + + @gt_sem_seg.deleter + def gt_sem_seg(self) -> None: + del self._gt_sem_seg + + @property + def pred_sem_seg(self) -> PixelData: + return self._pred_sem_seg + + @pred_sem_seg.setter + def pred_sem_seg(self, value: PixelData) -> None: + self.set_field(value, '_pred_sem_seg', dtype=PixelData) + + @pred_sem_seg.deleter + def pred_sem_seg(self) -> None: + del self._pred_sem_seg + + @property + def seg_logits(self) -> PixelData: + return self._seg_logits + + @seg_logits.setter + def seg_logits(self, value: PixelData) -> None: + self.set_field(value, '_seg_logits', dtype=PixelData) + + @seg_logits.deleter + def seg_logits(self) -> None: + del self._seg_logits diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/__init__.py b/Seg_All_In_One_MMSeg/mmseg/utils/__init__.py new file mode 100644 index 0000000..0a2af58 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/__init__.py @@ -0,0 +1,70 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# yapf: disable +from .class_names import (ade_classes, ade_palette, bdd100k_classes, + bdd100k_palette, cityscapes_classes, + cityscapes_palette, cocostuff_classes, + cocostuff_palette, dataset_aliases, get_classes, + get_palette, isaid_classes, isaid_palette, + loveda_classes, loveda_palette, potsdam_classes, + potsdam_palette, stare_classes, stare_palette, + synapse_classes, synapse_palette, vaihingen_classes, + vaihingen_palette, voc_classes, voc_palette) +# yapf: enable +from .collect_env import collect_env +from .get_templates import get_predefined_templates +from .io import datafrombytes +from .misc import add_prefix, stack_batch +from .set_env import register_all_modules +from .tokenizer import tokenize +from .typing_utils import (ConfigType, ForwardResults, MultiConfig, + OptConfigType, OptMultiConfig, OptSampleList, + SampleList, TensorDict, TensorList) + +# isort: off +from .mask_classification import MatchMasks, seg_data_to_instance_data + +__all__ = [ + 'collect_env', + 'register_all_modules', + 'stack_batch', + 'add_prefix', + 'ConfigType', + 'OptConfigType', + 'MultiConfig', + 'OptMultiConfig', + 'SampleList', + 'OptSampleList', + 'TensorDict', + 'TensorList', + 'ForwardResults', + 'cityscapes_classes', + 'ade_classes', + 'voc_classes', + 'cocostuff_classes', + 'loveda_classes', + 'potsdam_classes', + 'vaihingen_classes', + 'isaid_classes', + 'stare_classes', + 'cityscapes_palette', + 'ade_palette', + 'voc_palette', + 'cocostuff_palette', + 'loveda_palette', + 'potsdam_palette', + 'vaihingen_palette', + 'isaid_palette', + 'stare_palette', + 'dataset_aliases', + 'get_classes', + 'get_palette', + 'datafrombytes', + 'synapse_palette', + 'synapse_classes', + 'get_predefined_templates', + 'tokenize', + 'seg_data_to_instance_data', + 'MatchMasks', + 'bdd100k_classes', + 'bdd100k_palette', +] diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/bpe_simple_vocab_16e6.txt.gz b/Seg_All_In_One_MMSeg/mmseg/utils/bpe_simple_vocab_16e6.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..7b5088a527f720063f044eb928eee315f63b2fc0 GIT binary patch literal 1356917 zcmV(nK=QvIiwFQl6I)*Z19ZK~vMkwgB)E^SaG(|_PzuTJUep5J0`N~DK81(h@F{(W zxN)TxRpd|fvY8l2bh9uNNe~1;Qsm*`zuHufsU3f;?gfyU@7){Weg+%V)YQIRE$xrC zeq4t3M~}HKs~`QZ|GE9oU+wSve|WU(*3Z-Ti~r@T|LxKj(`7Gim(u>Z7Onkry|nhf z{Z_R9$DcocaOtO_C?efA9V8_G0Eg*J8Fm z+xYK;eN$i5_?`6oQ_=8WTL0%e=^%gKm6r4`|-ox)!xtl zyS;qJALFq1Z|v_Y`?JA*_sK_{?a#`WKW}=D(f)F>zZm>OZ9y*c5i7M`I{VFM(PPQd z8@>1zn|`&**-T$V;DjxnW z?d1PrKW0ncVr{XY>GiM0idQ}CS!VkQ|NWbGc8z^V-+x_gqjfNRQC57H9mUCiw(>V= z>=U&#Pup_5MfwSQPW!%f=1H)h+x++N^Vmnd#eDIVTjM6+g!oVUD(tN_pjLOqz?A7SNq@B_P>Wd`j5Z*(|;>I|L*c;e>mFzY>V5t82B;!h<@sH zoa~it>+E1$zOw!DuUWCdjbF6`yI!VMe8YJwu2|m7)D}-a1Ec?Qu{X8fwI#NfJ1*1g zKk?$b>s#$e9=;5_FYsjFU-KB1IKz0Y-ahY&S6;bnc1IR}@!8k3$3|`bANVyb=W4$; z*yY;&C3~!Q`pai)k5cg}`a0so-pb6ApJ?`D*kKYuZ zzp+O@Y%|h=4Kt0?c|A~MR`D&J8@oTV9?>kpFz9TT_L$|Q`!m>w9G5+QQ0K0~<6>j_%bTsyuI=%?!=uRb z;HH&0Etg6+^cg1wSNi4mvR|{u||`YjttMNZ749c5Qcw z)0O_qc5hiOlT|Z>ukfP%ro{htqDFAKhUQCglW0lbWJ-umcD&Y>{shgP|-tfJszdMY_oPG znVNrq7n=C?mqCuzE?{~2<1dR(zl9%$wYu5L9k{WbOzPwow1-=jiNYzd7n3cT53N;M zeDE4$WBgzpa-}$nb{&UU)gC4{{YRUR&k#GS&|#2;*kFa%i!QnCC$IhH$KOAzH>-X1 z&%5@ku7{551_KR4VORGm8<9TZJ8mPlZ~^Ji!VlR0)nTG#&*r(2HEVG_UcX2K7HyC=FWfmz-hzb#v1K90d^+JL3e9Fd{x6lua`$(;EI)cX*>E2 zEzSTNZccY&{4@JDPWk0_xGh@|7}~PI|EJe^*?&f~aN(u1Grh?u9yomi?9$;@?tv#N z!%ht_wz$5GrTmQ_FqrUORjU&LOM7{m9Zowkl=_SETI{~&ZEX9JF0~s6Tj$>2=#Srg z{-*f!Grur)5M<+Er4;nF+vzg?J&(m`Z}7xzxw{!kF9!2>mG#~c?A{P=PJV4H+}QCY z7P-LS%((Qn(}%tChCjOcG5=2Ci&Njy{;XUuk6%14R8Tc4(pJd2_)V~@(fO^1fwir) z&v~}3KeKtXZ~0OVM{UE@H&b8#neVm7?z z6{{b{*Y5gmp^Ys5gezztmi_ZXuCtzT-68;=aL4ZcLZyc_b_=w0W_C86FuQzu+%m_V zHJz;;@Qx8lsZe0!so%m4epP(iUui(piJNC1Z?^d!M|tRx2aNbtC6XPknY}z82@xUe zmqO&BJsrN}OvX7!S0(%SJ0^Je%y)OAaBMiu`nqkO*ZkD*0=88iwPIJ97`H9=Jj&)b~(xj&pLn2w0*zABH3>!BiZ5pTy>g64w1D*hrJ+7?gH%SATuVbAR025iw?d%ZtB`n0{O9fGhE z>Hqq`PBvj9WK!<>6I;Ozt}kc&i{Ebr5YIS^D~i;$)l;^G+#8qZ`N9j<7H3(+szivC;i=_fQI7sX_DdE^gu2lCfVzTBef2PD=aZob-k_6i;F)C>Ii&DPMZ6yp5a_qN7w zZ1#8Q30j2@c61|Yp7A6-)`9-oqU}n5S=hp*Ksnv1YXB4W(`Jz$Fus>Y(NsB{{uZk+ zFw|Ctt#?^)q`y-r0!T)!^i6l;jKxW21WwLQnSHgNi`5dZ+eLqWtVK3p_Jxgw_O=8) zqTTYrpskT<0Xz^tZ8oLxL*FSO#AZw>3N9lv1?S7Izb zvN6w5b=}$uSLEW1l^sCVB{4FMTch-f$2 z*rinBiqA=Z*6W=gy}5{4cF`QoB;R9~=H3R1ppdX!)8Aih7xVG7XYI3b><}tcv}!}~ zlt0>wN^yQEIA>%ehc`5D)f_df$5tcyZD7k_fc)fb1jv994es&Iq1Eq^g7|&h`%|L0 zGjY1a0lv;i4(@^4*zrhb^UJue3I@wy$)8HM@JA^pfdJ1v%DJo;DE}PJl);T_}HuJVjERQ9R+8=R&AYCiRt|3 zd0W#Ln9jJiz9z}QweZ`Br5fK?Y zcAcP+^iC}LloN_`4braO-N&_WhnzKD)35jqECX`H6}0Ob2*MZ0(JzqimT-G3ciOj* zFa44W@FnSBG3|an;tbk@Pi?Ht4kO0U$nL2-Ae|&jjCHUFT(=?NKC#+?P21t&dhnEN zOTukfz?s|-Z7Oo6i?s#fVs?&R5w6#5No0(Siz0flyKO(;_1w+f{oq`@*|K+L@_c_B z(Lia_lKxuTal>_4j-ncH!F#`~LxA|o%wPa4Mq~VwHp$L(N>}@^pSQ4-TZWbPSQq9V zVf5F0Y%RDkf9uu?S%Zf_woaZ-mkFI)k)slmL~U@oglAguU-t83<>!P>kWdwiZ_j?m znH&*l%QCwwEgK7B%j_mgRY|u%P)j@Jb{z(lX}99*gT<>YJIH*i{9FtP;Y0}2`Qh6m zd+$f>>c~tEUJUkaCZ>}?gJL~+nSYF5PDYMclOnDX(avP=IgYv^i$%7(p8N^}aZ4%B z+S})irQC!&Ic!7Ph5BaU&0@!(wd~1_xJ3j}G%{Nd$HJGzR>jiWAu{F{&UOP(!49j1 zsTQSfi*0T%D-rSW771Ax|I%V@mS_B~-Vid)tN*1EQ-*j*rq?5#gq*qQ`Zm5zcWuTM zH6a{+w_0UBlN;Qw`+?Cm%!2mF#Wp4z5rv>M0!wC5b2ya>$+U{caM$L04uqZUCR(GxGqB>#HwtSwq5$C{R`<+G9H$MQ|DNM5yINk{!Q_S3{2c=_dsOZil;fJ6YS8!RRmDBSclDfw2y1M;JmgbDlTs zQIH3KVDHxmJ>6A_P_TSOE_1azJrm2ed)?YymJ?Ep$jY&KiCIPalDwJHQZZX})q)hy zlz!j-_1eY`;5- zmkwWWCmOYYLH>s0!*|3fx?#}6Q=g;zP4aw!HVbIIbnUOhz^V7H_}J_1%%N%My^ z{kpp|^!R@7B-A37w>JW={7NAZ9GI5%aksr*J?D2Rn?ApP8us%11 zEp^{v*D)m*J8+di@KDWr$$7#D?|>fcdRvgT!Zwr$66R621|=%9{u5zP9LE9NpAJ8{ z^%0rv3SNM>^lg}lab5|Y^mb|o;hqSpnn(uh3DO0`{X^A4S@;4Zv(=e|Wkk-}XMA}> z$qdi1qE2ZK#2^a(z|TO9f}ke~ak%Yjcy@>FHP>sFw~pmGU=1xVGy_{}5v~@>?2#kg zA_6HFi~8&)$VCS#Nn_$rHY)3mroIDOYJ-_lw+P=5AVf6Y(4CgHa$y?2BkMWGVaHaC zc=&!$qyS1mD|FP(AY5CE0~}uV1$Sac2~@&-dx4$%amguD)3R!jKKb4&g!uZfJ7SLH zR^p`ZC2?XHDWm`%#xj`Ov<^V6@!;-yV8T2rIkbnRir7uq{Sm(?=+AH3tHr~I{Q@%`cnVM^0&Vvf9 zT^RuyJmAW-%2js-{K6F)z5b*K5nY zwST0z=~BnD+0x>EDZ*ZZ4WSzgq#hn>Bo{1V$Xhrk|N69GprK4rv>e@&En-16{RT!@ zE?UKpq7s4h&`;$K#^oOM>?_d6^mWaV>`h>x^awO{4IO!LTn98 zCM)fY&}@b)UeEYV`f;-_w!!-7RUukGng31vB68^V>_E*;Zu=T32U$C~Lgp3f;@&DJ znIDSgnBkj~c(GO$pel;vwdcYEc4Jfm_-Gxk!dv^2pa`x?-HR?@j&f&*LSC&*4;XhB zaVspXO1VRS3bqnZSSP-qGt&+rH()X{1QR>Y8Kd?V7LYW+el}DwZ(;wP+K}tO6$l&-kU`CL>6UNqN3TK;o-E9?`f_c$z3w~zg)W;V z(#xLh+;(FwF)?E6s2m^^Awt=%oh*Ttg3H+ulOEFwmpe*dzb;NtY^;l z=34|e9%fG}iBuU$s(e9P#hnd+B9ck{Y~C#cS`Z+0ww4{f6bHK#f$tidWMAo^{20k% zroaVYupL3!RfURB#gO<8@Ra}2G2UZBjJa-#N=T=0ueQ#Dy*kj5&E2GTdm#Q!Clyx6 zxw690QSlq$-;@oF2%XKD-GZVEb_y1KYy(!}ad!{#!{Ucw6&Z5$K`v>D>w{8kdl7{c zngN^@FsA@iR&XJAye|>c$P4Bn>JiR3Nxt9T0U09e`V)S_eP>aT%bdP`dQ%`?fj!U; zx#+jw0PxF7^wU+uP@OWJxwKpnL+*06V~#Z8Zfa0Y=K(B*R)2Vfj}>5DE}lT&B1LEU zy(8A}2U3Jq1aRRd%81OKf+@{gAyfp){7}5p;_LmA9@^@KDSFE6w#=o#Wuh4&4ETTz z)XF|(=h9ILb|uMuX{SF3xiC5-9i1G`2_NDGg!0h`lU+p@t!G1T3~CC>m9vA5mQn3| z)D5glI|q-xO;1w8vxvu{8}fF@K_DUMQb83!7dbkC{o#c9@g_-=QHMplqC_?2xp0~G z)q;$T?~|S%SZ44U2q1j^Nuhs%O#q>6l?z)5neBEw5t&>vaQKFIJ1kJ&qO$R7voj~N zvpMi%=-jZtbwT{B_NNTRfA|tdM99pXNUGg0nC5UabFk)X{?Hq#0YIr|9MNzvI1Jr! zs(LU1_iq`?PW5lQo;WXG3*}0IQ21b90%DNDR9iXuNZyAiB*oIeOSf(e*CU|8-p!-< zXRLqXO4sm4ulTZ|!KJ>T`e-5ayzYq+r)B6O)&=Ht^1E6-k9m=vJ_D{N5fvF&s(c(O z6BDiUlQPYjx5pA zP2}0{7XK-H14z|?Q&J^ueBewsNf)f0XEEY`rw;Ig~iN0`35+)7HY43aQ@f09Giac z>mwz(Z#e^9(_P1F9^jp)#NTc$81L)sP6;q;H%ELD5UgaMG=uSDip*zB{L z-uD#IGiQHzWh|#smEOxNq35T9Rs3geifck@IkwWtzl-fEz8Lm%ZhDd9PBlm`n3eHQpo43sQdxfQldR*^aCaS z>B%EKb42sd1CRNPU$1R219XZygbVB5pQ`ElYMja%C0UV<5VlS8)pLHUg7WajVA>ps@XDsU_b3h2-{4^dfEp}k&^L2PUpnV@*3EY9KC=2 zp*JOR7)&|YzI_$Hc-vXx^*@JyThT|03=cF`e`IvcrLtLKWXk&v!Pg3aCc{XI68fB1 zngbdaRQ>!!w{hsu?{?XP=Y4E}KU<)Cs#jQkIrG+Rv7U%IV>!j&8iLIH25&I077X-L z)DqLv*b9MhA6&LDj3kgG#LT>`ZGuPns6r!$XO98ehW#+Jox+nkW*Ba>WP&>7&dq5@ zD}vsW-Paz)$li~W97bdWJO!DgJ!IqyEePm`kN#{&nx6C2APPIwG_IzOTg2Ks8?vyk z;b%G^;$+HRszK26HRE3>_z$vj&s>aWZlMe|!vc{oFXfS2k^Mh)*2i^rH zmIo6gPd`tj`d%CBO(;A0+;`<;YK5|I>kh`rmOPYd8L7##in4jH0+yZ4LG&^Z8u*fk zt6+u)+2Ce9wU~fl+T_4fr)S8m8D(w-`=a2x zq6lrTsT*DI2LN0)d^#vSD1s+&C+-jlR(*Z(q8*0%lsEsoHtOxlS>$P})QT!;qrH}zo zd#g1P9uS}7>4FXS<~O89CAM*t;ShMhocOM@`c3iaoAmAqszJhH1GcA>(Vn=rC*I_O zd(+y9-sW^7WRZY0EL=O%AI+a`TW(QXS+Wc30V0y+(G33PSrA+uC+r!mXt>_h(2D=> zk1VvKil%{R*=e$k!o9Y|$c}M5(T&|LE>Q$ZX!Rd#x339g@w^r7hi2BjB);W}Yz??a za7}UK=Z!-TxZDGVKAk<3O4}N=a*veSpb0br38&0A-gH~rh^&In-C-wa=Ml-Q*y+*3 z)h-)n)>{10?1BsGrSA%V0241hPhKMSr+Ly72}uegR1H2(4lC7iQziYP;$*qSw#=+5 zl^$BNbZ-})Ki#jvPE>b=y__$t?fj36Pd{UwB3WG9ZRDSoqI0CSm(C1Kr5pm_g&j|K zAoHc{~pv$zw9YV_Dt=AF<>>;<9eiOB#ZK^h4LLu!XlZYA3;=penOdMwV4UPo>6s z@&hi@k=cvu927Rv$37Ct*)JUUQVZH^-6jg6JtEh`okonl=(?zwfy0n0&bIeag|juT zRNpE>?s;du1t?V2*=WP8xag2)zxC#lw8YxU@d=*IlpF?*FC~uAzN-qMn)R-%GLxV7Po%!vj=M* zP;3=mvU?h7KeA`~RE=i7nS#;`W#gr&+BVZEbFp!cD#uMzJ zm>o=r+8uemnnU2XOQ)Ow26;vH(cE!9m<&0z4&JOB)Z9=%48(sdFxpR#MyT z?K(8hqWHckpr{e!uBoNj-)ubvr9ld0+$=N#m9~b-nTH@^@SWSv8qfuPQ2Ze6M4K67 zn+K3xzEI`KDLHExAX6hqMy6&frF;UE)xA?KIJQs7@I(KNAT3EsU1W}!V}NMmIOd#z z1-2LpO$D|@0g#<^6%f4Vkq-jvHLz*D4_C?E`%Urbr|G~XLOo03xfAuvl1%aW@6x8W zfvmOfD3RIOSLy`gr2r1N3yLk4WqpmqrauSrQbA)&^_+yfZP~G~wC0;_?rt+5UyIvZ z`A%%dDGs(fx=~c)#4U|pOtk0Nn$#IC?cYUD}Z6 zLNjy&Ck1vuI!FIdeEz5Og!kN)ht}nu*yB?6Q$BgaoFVdKB4{$%#hp8`LCOsVP&tn| z!Sb35$#;QrZII`>S@ol3&U zzItaflL(Ri`8a`dqJ|$7$P@U}v6nS28mRLq*&@8JiRNXddMYReHHjR@jZ!50i4Szh zAA>Od6JYrDB~=*MP#Q;s)dECI5vxU|n~nYcNbG{PcOJc|V@3ujnnicy)-L9mp*A8X zRwG6O2avvY4sYzr7fT%JdWh(?3F6O=hkxQtiz8 zDYTy0+m_a=7c#Y;vJ6C*`Q{h5R*WW|W6-kJISGgau2vqYOzjVA=6(jR{GCw~ba7_r zK!AW5jiUt0?w9iDj@-+{#{9lmf(+Dqc1zgsr90EXCxl|#>dG_JU3-$8+0c6R5A@5r z$085QQ{z#rTW368fg2x|_`QL@60XB`Awqnxk2;6S%iz|4o@D{28*?Zo?w0orEb`sa zuHVc@oEb}vp|!JZN3_f`2t%{uypDMThA@!m=<(ER%7p;it`x^Zzn};C55lJ#g*(6n z+}bRkK;;|~ZqTn3kxPfF=O%t&gr@_ch3M&_Gl0*^wBC8mP0T3nFaf**0dtdu;sFch z&EW(dzwaogZV7Qj_GSr~vhgw$@`!zlsdvP7quch~k2?T6S$Ik@kYDc0WqZ6p=jfHa z(-e#)E`?x?^++JFzi3iYLpY2xz!E)`OoGeq4Ly^)A4|{w^k4Dl(c#vwxfENWv~^{T zVMt0Z8RW&&Do923J&EhtSpW*xsh}ai4&d(PNeb2igqoBv`N>ytvRZv^4HU3S5^Ca* zB@G@5O!-+X#a+0;ovZK(sO5)>yk)9;Eq-Y9Nwld%eca7*Vve{4Y0aHp%jn&NrB z_CX?^dM}q<))S(To3w6h-vP74js-xoOP5^nRf2 zBt?L=PqfiJcDYnh*xuChcyv@8xuv4MrOu7?5W`7*%ME{-rGDlAK}c{|c4hBUtvb$I z5$HSVZ62!s&TnHhaoKL!2|)$0y*y^xv1m!@bAfcN*}`12G$Vj=0}|x*Sn{5qZDkkA z-=``Ic~HyLl~%PB`?^xR$Wr|$yW(q|!-M>iw1`8XH{h7C?Kz0HH9Fzs9$e&B1tQu7 zUs7-$RR}o2|E)xO{4(#VYdq$Ni<(uC)uTs;L29CqdqF+Pagb}VlYhdUO%~jXB$S46 zGFepq7P{M#EX}KV(DXuY<#JN=g2vJm#d|Y{PD}uo3q81>f3F11vdBQX*ERy)vO^rU z40F^X!tZtmLiWo2H1(? z`Vu@;W}93c^0c>%c1=3%e_s4K{|R<@Bm@xi(!yGt->Da+=^2Y=N#?AE%sN5ciezFZ zP+A|h8jqebXy*Lg(U&Xh??O?d1K*}JRc3{-%7Xzr)^=>Vw0>r*{w$&J48nq81tD2g)~SQRB$3gCS{-0n(LXpf6@jzXnY8iEdyB^)Ftw&Viw zy;UO?*g6^7-;lu`dinp|%~2gSI&V1mr8*COnYfm_Zh|5v%bWvPe4KjziVU03( z*n5akEHEMUG2}jOil*AO#Np0G!|inG!G{w2+&m|GI&z0lxyJ3Cc)|p3ok1&@iHO!A_lzAT*nM3o(}}IwUrg-T@!v4(>3Jxs=fqZ|N;i9LTcE!Z0@K1NC)o zg_{q$-YXS@OMTnuAF^g${Z_f^9j8h$m!)VMJ4z#r?N(yJw+pM7pa{&lpB$xFD)+n5 zn#ZNxPlW|EtRQ!8_e79Aj20#&Y zQI}zew}K}uS~cB)L;+qVWEXE(RQ5^wl^dC6aZW|j`O(q~$qnF(MIb*NXL8OQ>WX}N z=}bg;zs-_wXKgI;d&`4ZScF};amw6^t{2f2pyMracoeVIPt=w}q}1)ZQ1oju(G zDN|jYK_HHDz?#c_r_Nmqn}Ox)l%WY@NF2u~BK{{w@P1hQ@Q408)TEs$uDjcbLvuys z1JE3@%!E~P-!nwffXpm)otNyhz8KZB$=HB05vdl6Le3+C;tSf7V%gB$Is|M{$urgx zn;#`{-j7QJ?v#?*H1vl0I+Muw#~K1!^wwrDc1#qRai#ZYEV1BhonQGBc*+B1M3in< zDrj;-N9H>VwpwyKbD2gysH)h9E_Qt~)@w*Zc$?gqpq%ux&o<8zpbWH6r%0oQ+@3I8 zzuY5wCbFuVH55w@ilTI~zqAgh0E*LRe&Su1W`5Envwl#bR==GDWFP9fOchbHniIl2 zshI6(wJvj)Uetr2_J?{+YaPeG(p2SB7{#Mm;px7C*cj*;DT4m>mq zoD9hl{f2*nIT`vQNNr2Chz&C(XZ^dnQxKXC8VJdG=9FD+Q8vM3+(i_V4DkbfqtN!8 z+4izi04>p=vdV*OyXBPj9SH>%`ANw$dNEhwOPd?(^+03{L6Atlct(F!eExsZZyn*L z<+ajZ)FJ6a0+k~A6n+Nmg#gIE(?UhKel@I9TA}h|3Zcffi~0nOrofBBvi_7)rgJ*O zs(lN+ril)=pM;MVj;@2!pk(0NGGlt6*CH=gv_0RZ=ufJcBio^PsyIo)YJ17?cg_EX z?l$R&k$@drbj8;Cm#>c~3$7u!k8WEWk7pLHKxF7GGj8JSZ~Y7?!2g5a9m+*gL$gPt z>?n7jirZ9P5^timlUnA!g2WHFglg#$VrFY?*|GfD0JFS*qbqiugiT(k1IOk^& zW!SREndk_qNHHfedt82t_f-PHfF(O zaJ}jiZAdaLUT|h`Mu&-LYReR(2c2tf1A_U+xC+ksijT&jc9Htwu8Hn;pTomex$te?_L59TCKn&0x8kRgkK2UYJ&OK@-*4NQ*A)XG4j_7a-bEs`mnuty$fV&$H-{rcvPsZTi(mpBX;8}|m&`e7afd|x z4`jy!SRx**PpQfF)>kLYr5~u34XChMPv`&p#rM7|dzMtJ5PpPsW~eTBVCf*p9Cd7z zqgfgVHsS7}@jYwtZgU;1IYuzPWgxBg)E0D1iZXZoDBL$u3*PINm--B)Le+O8$0<@e zC~yy#=)F$zdB61OBEg=wA8EhndA_!`d4Vi)?vvYmm6$NZdBixZK=}@+#f`-p)FS(v zD4GH3ve0oRDG8oAKlbl|Yn*sHNC-Ke(oQaEFD;_K(9nQHb#<0R`Q*|k%IgRH=X+=8VH(aA#>{}pkT zozPoI*kwjG8`^11(n*XKZ(t1`_qt|*U`1pnP5%Y|7bN*O%?LY(HqUE<;{vB|2y;|Z z1THnNLMxoz77sgV(c1KF*Q-Xp&?uAI|rvJ*%PDv?X3}>sNpKwcE`a=ZpfRhKM-&c!~{JcWOJX+D$WP0RfN6QWl z#v<}eusYB$u$LTtO7u`(-pcwOSrNEAX-~=|D!!lFAAXPN4fnXR%MA69GU_c6H6~&~ zd*T*RFf;CLBF!W4qrB&Hw zTa5#a84bwj2DfpInDvf9;O-dM_PM#yP32KX`IAnz*>1hs2e`-}fzcmoy_Qrt@0Y}5 z4jMzUD#C{|422Vrnr$|{c-d09+6g|bR0^obV9l&)k5}8GE5*j*lHi$q zishH3|B!0q7%~#i0B)#+)~T+Fes=JcoQKwCQDXRj3Pi_RoAt(%RP%a*$0WUJ%_9anaz>UvJqxxyV%h+ z%Vur?q6}!$tjnxQ1rp$g3W`&Ex%Ow%j5q{6u`e+3=g=4Sda@U;EdBh@US{QnTR&!& zd=qkV35A9g-=q1&*<)bG`u#~-)beTtVg~zUXOg1JG-(NqBWo#sRTO{Ca|%({gJFdz z8AZp@)X{$;W{FZpG-tT=RrpA4k*ukuP^CYs!0!I_90dD}hRiuSeYzOSh&~VLIjpUV z`zPtb7(a@@l1O3ld6MG4)1zdz4IE1700Go0!9RWa1^ZTzGfIIYD+i^ePC^K49XUIo zzMgV;{0AELF584tUqz(~Q29R!&Ca0$^tv2#%1v#w4(1Wr-nc<4gHLV zCd1=2>;&9Dv^1U6dy8Ecz!D#eeXF*M_-bUoKWU{C^L^V`5 zFrpt|$1L{yj;l~02&(+TATW7wjvScND|KR3m;qjLYoq>>G)PY6ybj4|mr2Kdt64)v zN8SlrU?!TPH@R@si(WbtvrLmpY}+2;jeO(Fwt$*;;1IbjoBE&+jIU${x1$C+9O_%j zaJDd@6j*ynUj=cjzwb-8un8d$uu#3N$BVJ#>1!3PYY#ChITX-NydVg8B<2s+b%x9C z2zyR>z~2*s$gr+*%d{m&5(si(tKvH3Uqnx|g`i*iJ;=S!B^&3UI-thKV8hIz>X)X) zr6#aJ<-^2hq)?s1H&)3a@FX+ODiR;x(Ekxm4D_bBh`3q#HSM?Wfl^dtI2 zw1IPcSg zeoR71rg@j!*@05u+Dnw?pe6!`k5CbmFI8vEg=AcLpvqBHlw&$689#ZPStqO0X@GK+ zy!Io<_6ccEmmk*D4}Sn*2)YkGP|_r-i6#zibC0s z=`sNANH&#P<3k`%d(29ruT%PmIVMbV!_K8R;w3n>d=mPKiE}NP2zyrThf*{FCq+fB zgOap$PnaBNWI6*ItplFx5yX37tpw?^rac7YhaED&;O9nHH&9WXf;`7PF>(Y_o`QOm-lQh@y`JW-Az_ z>ETYpM@Dw+CpqINte`O@6=H)T(B`RF_2pQaT=2PO@MX(lUo{U@uzODPYfkKeg@Seh zi0-7EKsoHhOel37Hh+&bZHV&(@2Y6db*8u48f3Q1THnz8naSxXHqjb63e^L&!t8~# zuO$i2o08X3Lj;7RTJCNEvnqGK4*kSZpxa1L^1wpU&X9aLE-AO|L32WjV00Vr=i)Y| zyJ!VvjOsp0zANNAyPhmKx2@&lqJ+PU=N+Z<_RO8SDHTXBQDY7zhNz^5Mz?~%qeEvb z9$-WU&jImg=F#uOJo@7|fBLV*r@uZIYRk;-f`t5lxh}-W*W62%7;Rq2N@w$S5*+MA ztY!P!O@1lcl#_wM&0R3*o;V7F{@|6a0o%LsDOF)|6jX-bdz!D7h|@v6&r?4Kdtyl+ zMK@_vqLEI5YqVwdvmZKP`<{lVlNrEXKGmTdf%4#D4P!RJtoRDqOZm!_R8_p^vdvjj z-%}P3dyg$d*Mag~a+eU9qO1`r5pnAPyFpriwgOq+C@~WekiQ+&WgvSU0)Uevt!9*e z2W}8P8PmCYDl+@^5jFLz;y>uK(ILbWf`-xwq0Gh1!U;P;_oxbfbrKMU7%Lo|ZG@gqst)oU)*w$$Dw@q)kwA>>ZQ&ZoDy>WksAB1`EUJ)k%wQwvXU z2T^~TH43XAzEDd{`GaP`v#3NvuMdbBdd)B}98l}`*`~XqUmFg zC0GFw;fw6&@#PsMV#}vz2d|>mtZa}K!}8XU3yq1Y_U%o1mh=%}JEcRL98w7Feb05z zB*?s6t?qmOCo?9G#%C|fZG6suD zW5>9qXzX6Hde1T^SD>;763jVW=@?AVLqjCZOPDMs!k`|AxFdy-R_J!>Fnz?Z?q}{N zxnbH8Zgf)Er3_053T1eVq}N-UMx!PNknN&)O>c1HgSi{Ok+9abA{A7fyg4sc4Hi4y zYlOuF>C~Ejjm{wMCsf$F&_oW+Z;1G@!P8S=3`18PD2`pjxr_>-MKXA(!tuG95B-Wlpk_ znsa8>0y3CVG0qdaL<=m{@f6b{I9sS!Oa}0>>qw44H{v&+e@_+oF5Z2_{35TRQgtz` z)siFE)Ty5eL{hB7q(%!Pe3vzS?OIkA-6)yAu0eiB{xj>NtJL3>_NOHeRn@sE3)}v% z_@V7*6^Cn{a)B`S2;(+OhTj>3Z#vL$i&G*aZ$TFb-8t#{n-9Z20jhqe)ox*`znItG(O~jppDa>lc3{Brvd`m4uKuLLtemUnHN2Vt zgrDpU`ge7p^kYYuIS?eM3dd1HVH{2gT0-7DHZkW}p4gA|!Syv%tJAeACLjQ6JX^UHLIg2v-6LX{@5ja@r zmr;{qpXbc)p{Uh1_2)VbOY& zvdu$w5*Br4^YDlv#cgm#KrEc%tfwhHjD@4Mr3g6!kNfE3uEl84C3ggM zdh!Rg&xRhQzD1hpwjLuy6V8dNbS6Rv9Fdy4VDSzm>g|!pdZJB`ZY$0gqJghG8eorC zlz?%G`t4kuMhdq@2Y*r@_Xs*lk(57#@b*?>oHK=#Zo*Gd+XDefk@3hmp{X-QZksy% zeF$q+bI#?qqMHdzI434|AR=T=dj(G59`l)7@aZrPEryenc@)UDm-~qycRkP4X$g}Y zBbCr96p?k7(y)*yQti>!(dN1-CmU*;c6A&=GnTGOS`4E7CT$f#sADcZKm(7xYIslq zmce=(@@%j<1Qi#(X;c-S7H|fIrF2F^b?lzPNP8jkqX_(1ESd0(CIBI1H8bKAG^^hL zSf$=UbsZ2TRjD}Ix={>|Cf$ABPfTw>*D9iA+R$kR(-vMGO(h5Q0OMDl;Kd<}%7O`j z%sqP8i#%D(kt8T0@2nPHbhphf_j&A0MZ6i@X6P=gPP>!wc;|rBCMJ5YK21pj^|cqZ zM$uO06|Pa1WHn&lX}> z3%=IJ~FU1?lU-rZ;TmF%(xHFOz*>whAO z-fZ_m>ch;%9Woq4t3uP~Z_ceKSG9IwV@QcEPera@EexE6#6(^>A z+X)#QKYRE5w);yezlge#wHRm7RWwk@}dF)D@8$pq7wGirJ0@v0-Y6GJ#Cf zk$uYko~qo^^pA<;0?3QE>SkCGRCGa>!yqy9e@%o(URP&gu~H{3=zCLYRqzZ}U6UL; z-^Xy&4oSv4z>SpLK9FaO0hvM%t;4lBB&C#8ax-U)W2SnT%_j6w_A@NjOEV^1>A8xS zKIl81y|T$weE{sDV1D4o8|7Sp)??~kO%DiMzT+gb)nXk?h`JXW{jtBFPW(HxIys0h zbyP&3remeE>I~EXPmrn`$mWsiy|YBfquF~q&EiCy>lggX=Cq%G`klJ8e;Xd*FA=P# z$tiswY=GNu5t z8#32tm8|TUr|wEMS`}90nx?8E72Z03KiH0f%XD3M^)smqh;-j-NbOG^75Yrpo1+5A309>6tQT;>qc96l;tG z&0AeVBBK7@_loa*j|%56De`!u%SnoY66_M6fP&eERx8+! zQbz4?=P~97y`xrN1!na`0)TtB8DY4uq9mx(#*ll7h9(LK*EbEdfTBm9n&_TZe ztm@~=jP4=&a#ZrbXtX&5A)jlAVF0-HxiXr@`UNM~o}-c~kv-wNfOK}wDY)qKpb!*f z=mGa|5er2!)3h^@O1?hw)1XXJiX1hi?!9Ee{!)$P2#8O*5K;I8!r&465GC9ivFQix zNKjK+nd%QIoI(*goWPyBLIhnfe&W()FyDRl%Pbrl``DK=>BV9mO8sINMdfD}LAc*r zalOrw#ggoyu#a(*N}z@`yChU1x1&l#?Q3+}x9?b5QB&>KYIwqX%$&`PQB|AE^-M_z z+LTn_o-(=62-JM^R+u>yO)4nTjeW-di=yER?tubbI(@AqtyT5@%1}xtuCm z6P20d4|cEx#0cfoNFDn@=*@PP1;GU&Q6#6iTgqMGCmSfgZpTUgwhr;dK2Y$`2MS1x zf$4ToeeY2Yh%VyUqn++~QW}AI54r0hdwL}5uKVv7|1lZfxww)IGYY3dV^I=6C5I2W z;nVCun>mIwLuu<0S<&NEG?eanD2vGHvm+KK^{UXVJT*Hb%J%?|gjR};dX2YyL|K`9 zuZYx+sthSxjac-FJ_tXeXqBEwEfJ~7osDfmBckNQp0f-1J4Lrf``IIpT3BPa;}D`$ zF(ox7utuTmLY(#OlUiXXa#Ky-<+$)NL`yFFofvu<$1{`(mUP%cyO={M{iUdNVC`Exxs-t3+Ws_N3j|F& ziEY$>-rA>q)_T_F`57*k)O!BU;?v*Qok|C9cCeUbL@{Sz2Q{l|J5GiFEC3sd?PM;bry&W)~* z0KkZq{l?iVd$9QJ$1sMOYX4|CMm#)}_2n&2!5qApYf@9w?SbD6Y%pcNW13~-$KgC- zGMK`9yPTStizR7LyWAAI#AiRxaP@3-%#yC<3jRc{2C(B&T@`G~j%x$~$@I_+QORL$ zsi&S4anw~3a@Kuf^ zTW;Pi6dh`5q;n8l%-AD$oz5eNce2OJW6GJ@rrmKduwD|iZ4T&;Ey}^usr=He{TvvK zr4r=@7uXIhs+D4IhtzX`pXKCLkdHB&78rmy^_tDaS4pvqA=snu~%v zPD@CGX8ny=$Mpkn~yxv#}KBXWPr&zWNpSO6sen(qb4) zln@q#9a~J*N+0is60kv|O)E8EMHhNFgKzT!urib+38~gm<&7y2y*5uF-ln^-uap;+ zcsqnzh|k;7b4^|?<3t3EDE$WTTb((CSjjuVEP6e z?iio*IyA*oldn?+MK=9O`vI_22p2k7v&;+(5XHJ9vQtxA#1{SZ7VIo^0Yf9hPhSHL zaadXCpzTo5!9W;45dYCR=Tiv;c|V|Fu8UjrQ4dKr>J6T*=_!RJTsrzJNt%fMvOKqO z;_M{|m8sBJE+~;o!KzX8pU3*tP!I7l!pau0V8Guw+7Emy%_ask8~S3uFFyTsx=*d8 zRa2TylfD|pUG?ksiiE*$B?faqPU<9W0zVKnv6vktJkGxk0=R0TMc%5KUc4#AFWgup zGKoL;;U51QHAW6K^h51YM+$d7|CYU;N=nQ6M9SqPB5}5pRDcLl?BCgg+n?uHcV>yVYmzZaZka{x%X`5F`O=2FAUq=@Zm{e1e z+4l@zVIH*&@X)UMILFdgwa^k@M5P%~81w*lt1UG8;4n*t8qPl``}=SLpJeeclp-a3%cuY<~Flg`dc z?N;=AVXt1GaR#hkXZ!s~0@G*$IBgOnCWS6GZ2g!L?YCpyntVja z1l(ksXJ6Xw)Z9n`7g^w063D5O$ZcWKE+RJpJXa&WU>oXlSPbGwJ)=D8{9jHj#R&Wt zNHhB)lRn4LQM`GK*}|t?Esz`)f`K=X`Nr)Dnu#e~SE^gTP*jf5Q~0AUP6os(Liy{Z zmvTZ_G8B}LZrRk6hQM6%;9l=Vj-(h$An+<54i7zVBBj9*R+kDW)GSw2M))`%ySZFZ z9@{2cc_e<(h2`fbh$&+fH(VYeTZ-rjq>K-`MhppfuGef~ZeY(Hs4~ifBz%M$n zGG!Z)^QVa{!5>PCL1vztR&k1`6&`mBYQbL)-0_7aGC&HBb9T`gOw=VD&yaYl-~8#X ziqHSUR{rokTx-N-Q>+UWgKz^wJO`>*?!4cVNGmL|o4b4s5`W?s7btH(IRs98zwfDE zhvXQ&itX~deY9}=BhOLHzj;S*95It!&=k@+Hdz9|)buiRe1Kdus7tHGp}wk0T2^8OIV{WXgrq4Er!^*ZFHR5Z*?^tuns zB|F&dRSf)b3OS$9=Uw7sl++xkPZ8LO`Fuu5 znk1?Op2Ij9^Id$HGR;8u!gioA0V6!l#ZzcTXUp zI0oPMNBQ$5kbR0((Yp8h+`Xj12Sjc-#*L~8&uz`sD7(=$N zJ!{fBH%B`8Ox?6HW7~%(zOo9Lt&%v+Ok9qIZBAUgCX}!BK|<*TLGZ zy7+@H^zhy=|BfcjXH1`$0^^xFM(v36ADXnDDCoM~k;Pm}h?B7+f7>IA<3@Kahg$Kg2co18dA)st1>XR`x2&H6Y%F6gE`e)}V9%{7AdMaQ=E94DS8bZ&7Qd zNQ^66wa6YT-VRK%7a$Ym7Boy;?ilM^2898$I9nE2HZ9f563CU(rL!Bxt{lpDDSV>B zr*<=ROH`>7E}w=bKKriSt1*>5X@6_s5fz5?R#~O72NZs{i=0w%aB~{0}3nfh?LzLlnG@m`UFYHmCLZ2M?Q5IQSVD0hg0^VoJ4AblQ z3Z({UN1?O6X&NUHpq5Pqj@xs%h}W%2C1C6c76oQMb}hV4zle6%!2Fy*3|3EHij|g$wPDfh~K=wa>Klk zNjCM-$D1d7ofbLFGUjiTjQ`n5pE zwgQmtOU?RQ8Vg@lt`Aq1MOlKp*#T89{hKKf2Y(lABprrCl!|~;cn^?^{=}kgd|BS2 zE$W8S=_ra2X!0lp?U3p3i2 zRR|<+W<3wp697BTVg7G;M^A2y09{cwwVWG^on5zVvbSDqbnMWCffoA=MBK_sb zF`*;YPU8!HIqSVLRa>IS+2MK*S?&tF!j0M~$vZ4XdZCmofIy!{V7VQl3LYgSynbHn zqPS7w{Eqksjd!k~W%pJA-rA1Yg4gSLs0Zjsk5-5M3yd~L;v8~}tjs?-da)spm7{$R z58gn_awoy@ABnlBewi8`)8d$1T!Z|zzV)iBiMU^XXs19!Lt@%8KT~+gnghbcwtRVI z(~3VhD^x)Ui#=^GY;abfGp7gzyw$d&XT#XZ#apt&>_fg~AF`4!3QV)@j^g$%x4p0m zEBqP9wS5G&j1>4T_kP(oW+5tjw;H0ImcCL4kuo_Q;;vo;RDr{7=n<<>E=Zs8fNIhb zWB@P}e+@Z9(lnA&a9ZLb)_z=-Hc;|qqm2q zES|5`GZ2nwE&JoIiqHRsAF3++Kg#JLsjwzh7XObp>|>r-=!Rd}uZA&`84&=O*#4X| zm33k7{yQwrY|c-v^N8Ej1ekJofYp2Ca`zTVU%%vvVaK*N)W%_6rL9HfR6WLofPcvV zF2Zp4jFI=0eO3vriAtv{CuDD+vj(w&ep`>Ij{p-16-dg@NL@ZO6EX^%y2P05qOUwPupx@f7X1A!& z80wTwTgUXQk|PXRc=p4tCktdEw&r<@PUw_@3!yt@G|`;BQh1l5sF(~oQTUtfW<$bG zXDOARq=0MP@F<@QnGy}DO3wx65M6y`DFX#A!H_YJJR$n4)bE-;C+lwA%QS#4c=~gm zja21H?q~%{oh>ALs9=ly#n+_O#QfQcO#w^;;-fWYl>6(01_d8}QCBnl)^~0Kq&;vOmk0BwrdG97O#doXK06`#xmTM#~d!lCqfQOoj#T z75I#~5});(Qyk2Y0}QgPlJ7k`4D-SUKMijB z?pqk{q!8|g8*#k9Jf+=pX*r5K$S`-EuXR2_MU&GKN7~OshJwKS8V$~Dhz75CJIP!% zEyfxanIMEFr*g`Os|EoV^>BH#)aBNkw37*0%k%WLPe0>9*^*L2)&rQ-S=D)~(kzIa z#lt)<2rL;5`>o1oVq{tXkMR+OFt~Wzh%OH}Cg)|jWF6dxAWF)FJM)OIeByZfT{bK1 z7$5hrolvY&z*=?p_cR8Q|Tf-%%(83VhlTBodN8je@KhXNLnUxT<3{;&k$gYr360Vgx#N+IzFO8|LavqN3k@ z)JqWeH#Camu#W6x9hIaEW*g&o2tu;r@SHSvEscBTro|N;`gj0@Ob>$Zx$grcMA!i* zcX3206sp6<`(0*uVeUJROH6vtevW6A+8g$_knhuSbR^p{+Dn$bAF?R)4G_NB_rNCE zV&HH=@l0k#4^P6}4& z3mX+mOu14iHquDQu3%dni^G8_9Pe@ow|0e zE%bo(j(6+VocUp@f&KmP#f+i^-rLpb>#RtfwQC9Ixm{aMauI9Ugb?Ol1ddAXHSbe| zow<}kDf%3*Doh)Ds-exE$)Vc#o68lgnGQLT3S~#-r4dH;srY~&|7-LwF@U1R>0NcR znSCELB(+3T>a%!b+r1=-TR=915|=8im9VZjd|To) z_?jeP0l{xVKx>S_0U(; zoDh0nRqxWgprBXoWP|zoa@0fGqY&#ragKWJk;Kdj?ks;RYS0MhSCK};q*q|y`vOmN^X8C@8pbZl~kI9^(x^58t082p;%Xo5=|-W6Rok1d4laY7bhW*^T?t`4N|EB zkh*4K#w+5M0{kBbo`!PkB=nCcH(2n<>B`(r0vxslLE#!x>zbr|=XrcM%Z@CQfE9L?Xa~3x$vp5UIKGG-ltC>5pzp1GkzbA2( zq^2nd!xEuEG`Twi7I+0Eq`@v$-K9EK!NL%$QzfExXkBP$n>tHWw3W_cd7?{0-v;|M19fK1$2Pbpq&>tOH$*@l-PB)zMsS`7G zux0*KgJu6_j3F54@Kl;1m~KFKSwnGRu%rdGSGO~Ta(`Th0W}Jo>X74zVF94Fi7S{u zmI%HO-*Df8lyNL;^v&U>P7Yhi*9Q43W$|N8HDD z>e8(-;KHZ1=edTt_fS>bg<`47YX_E|f%6|qFsOpXz&DeH07&f|))CS-s+s4r06`mx z^IOVL#j?CU$Q8;hK6xjB@qk%_BB5!&3w5=ZzQ{ard(vM`p_+KDf+W$`7Wj`jYHn3@_us|q6-1I1mh>BaWHhElt&dE4#R{(r1s4x{%BUrUlyN!FWYePFS3sd z^-QIqd?*8aQ3EidkP=CI7nwdAUQmX2s}mpO7y=A*a{N%F^6+q(+< za0+0$`SaiA>=4t49gpNnSV;GSSOZ|qxaoGiSft4v0h~vY-?Ig!=Or5&vs1?~91YA6 z5Ue@rVwqtY%}k-OVvmV|X;R&ySf0bSbfe7e1kk#HaK)r;UN6%*o0#Ns&H!=K?opi% z5hpJe%s~@XsX4t5^rz;3=TKX* z3?jAP52z6NAxJZ;e0TGy@%cnz#Z|>2wZSy(Ga8wxo_s^>sS!TC+9|g>;*A^U)hcMA zq@FppHxwIRM=(S7+|AmQ!iaMdGCDVv^rt$?Ke;xUm-KB@ZtiFxBv#9!ORFKqHm+=n z%GKFAb*^37;>)o0^DqB@8UUCjWzt1{a|`($!eI!rNy`mGO=j;ADMKK7FFuGx$ckZz zjW**tL;uF1L-!v+8n6u!%LNl1F4P4;W!_xwbmcYR z=Eo(~3i~7*!W7X5U%`2C_-|J0Y(fWdOm(f(05zo7F*CHpO@hC4z=<=l5}t~?{{s8Y z1xqlusX7f*voPmcW{IRo|Dd+0=tAv9@8-S}r}=u-rcW_citmz#5IxJB*FJEZGOUy$IG7<$cbLV=HHpr21OJc?ltsfHZ=A57^$P=m7aI!?dE38Q0sZQd4={&1{7(}0V93<8#<3VVbDxuNok5*6%h3e3g%9MuPZxZx1cOmSou|-dZ z`e5dE5^wz(F4A_}Bgx{JJPbVdTvPKuz3fyjV4;SZdInQ(;;4fG5VfZz&dZJ-obMJs z83mYx&UddlYTm8jKqlvU1-#_`{@Y%-XU{5^oPBUgD z{r;k3^72jKNXuCPvy=*rMN@kc@Q5?%0qJ@c@<-S$Qi|DENUl`OdR(#C&n%L=!y79d z`7PqJZy<=<;XO}UfugmDT?he^ftjF2R!afRiA0>y4XLX$D*zC)?58R$h;ne-?LOLZ zwBW#dP=k1Q>dr@}ps&OmB6D-IW$60sr{(|SH^ryFbJb0#v_`{bRmq!FREIC?5|*Zy zF4P{jSHS}d9_(5W-jXMKLv46Eq2rXL5@SY_P{~zK9%K&HU$p-t3GGOlL5mFqH$P&Q zWldUN$*%IoLV#LZc8}9m?0M9YC?Mj+eN%k?7tUqK={kBDAz+7ugFoXEB1 zG6*^TrMJL=wgc%gqJI ztwmhh`&+8An5kFhuihv+P^l`0P5WZdjTdI^5V3sf7#<2%Zd*E;jF7M_S}6*DzxWQu z9{7m^;~%`^oUu|_-j53q+B78Bp9O})vx`(L5>^B)sJtp7KT0Ug*OiQbU}1XtY75=0 z1lwb_tIusmJu&@e(zA5E0bD}=q9ev6u+K2-zeef9p%iIQD|`Ug$J1g0-A@5DO;+)v zf{l=!y65D(QzQc@Jw(!G>0tI5g%+SgNh>MHKVtR>_}kTSMJZTvN|s+^QkF;Tn356+ z+^xJJd%Kmm8CA5Kg+T=Y-R(IpG8llAW&C1aC~KA;j(2+lkEX>TLZ#}aTkrH@^lJ*% zWLFqv*~I<)S~E;Y5-gF@Vnvpir&xy*1=-i5jeUyIf9q?()Rg%MP`m78ase`|xo8X) z#?nh>8Xle%W!tYlAZ>Fbx7%9Y6ZlDm05!}WtkvRR&w{efHcubsm3Ok6-=9W8ZRHL!#9qJ6!=%= z1^PL>_MGcjk9L?dk$MYg%n2m2f#|OVH}urdfDU>;nnZbCq+IuN#xX)$(Hj_r=OiFL zas~z{=YSGDO#MgFgUETqP`-jOl+}YJ&5`ySQz?9ot^1BG&wm|R=do~WCxEIHdgVx@ zJ(M)ahv+ZJ)suUaEVfzD{WQAi=#Lh*I7wI$u@b(af)ep7rHEd;sBk;N!lspryjR#Lp=b6tOs*+DlqI?6^Boqk9Ey`^Y zE5!r?@)c7Xket$qceA2+V~Gw%6V`bMGTk9aOs-vA?ie4Zcxv}ooN>dv;jzpE!LaNj zcv&2f(sw7@`qft&&|OOizO|Swz~OmnN{|$6gYz%FP6GtcH|>Mq+=gWpBeY$cVd=!k zU&=iG6G7&Q<%}*MC{|JnTzWFL(pALyr_rd6xd1@hmLwk;CD|u{qCYSG!rjz5oY^x} zPEWV@8`5Gh- zr(L6~%V|OAC`pZCQTu80z-|P+{gJDEn?lZ_ycMV(g3C^Gh6gqP=Q<$4(9B;Q!keM@WgW>}o zik1IvJO=~t^FSkH6O4V`SlEl^W7p4W30%dqV28nJgaYJjTQ!~hqV+iUq_^wi%xS$L zBE20MALmr56>|ehj@Re|BnCWjV7i~{{vwSIZJe~#eaPfXG|=zhZ4a%?BrzKEz?O3r z0Mxa`4GF}r*an@!iZx4TFJ`v$aFP?L8PnfULr+1_x83>&{ZmtgevxW5KTl;Q2nIKY zup3NTQ_=+De0tRP;yoeBA0KKuPapMn`Y1*Z;*X*AsR=@`g&JQ{!qV)y=RBQNoHd?T zF!tJVHWUfL#gdumWsYwD_*WQ^2L%&52TaH2GzBB5L2PI`5|v!Eu96xVD(_)XgqK>< z6f25upumhQJB`8=qp+fo?HlqGPW4a})|^Cd*+U~;8_s3qIlBWMIv9)|5f>_2?Snh# z!hV;X?VO12g@3H4?p@c*@#I}n_begITjJlfij{78bO@#%<+)=P;Gr9Q@3YD_nuK** zESXLZ2WHe>f^%1%u&V#r?4&pGU)r%jN?iU353L^lAwqm6?8 zLuQ0UMnq;tWlT4Q%9HgVqktdiN9v8dASMC?Nz4;TvvUvrtM^*I*LE-TL^BB&7w1%E zM!4^B4OCaL2iRILkeR~9(R+_UPoZCLLG_F8e)?YZ@%JZ=S5J3L2|aLi9QVhCS?b;^ zBJ4Pkjxm}J!!gi20-UxSrD!i=2NziS+-i`g=x7EF{n5Xs&o^*ODGY(OnhQq;7&vWR z+V-wpvN&^p!Eaj{iq|`J%sf-ibKwkeYY7g4A3@%wh4!^1#GX%o?miM}n*<~;T?D05 z7WBNP&h12&J_5Gts`CmL($g1z`|$_W7yk=%y6g?sR*%z`Oef^yN}*s3E^GLv$7apG zisOwQ9kpN1xc~wFDRcC#Jy1WHC;U{h&yfTchL&I-S=D2rO*nOa)g{yvRh^5CE!Mgj zMV7f8Sp?xP-2E7Nz8bVmfSW*=qDp9L_CjIkkxew8A;#4LU^pd@B#6ms_a#8Wd5x65 zzWPtVF?#oIjA`aFKvdK!VEf=hL3~@gYh7@r`)QhAixwgWmM&5k&qDecpC8id#2|Pj|$F`t=BA3VqlzCHhV7$ugA16;eSrjB6z|Rh0Jg0Yq&VAJsOXyLE{m!kQ;nU_Q0&TKh=z3-dR&+Ex2%uTkc!F zkEwQ;$JJ|w#Y3bKqAu8lWrz3#TT$g*lk=9!9%}H))pxR*SyWL99S5iuvo{yV3Jg=j zADU|kj!-BOT2SCF34;ImAHN-37j+6?(Cw4_dInhV`uV1r%b?19%ScKL;qAv2w-owJ zMCZdb;SxaOx6=~>NpQmRGB$u~Tj|+(E_`FHT)kKFi_B!7rk{#5cv>OWO{q zNW@v32r#LR1XB}OfNV@bHGl3$ELYf(SN1vNwwF3P`Dq$|JBao_bMzZR<%t#z@8BVp zJm;iA+J*yR(IzyZD2nhf;oJ`an`tw0^hVv>{z@_OIB+O|qy%(S0-Ib1T!~A6JwoaN zSO{-$VM*p;Xd=1ssX&v_o0lYZZmnoYE&^}|A<9{%+m}9mIv3PaGJ&|7MaR@9 zBVYm9tg4V5rJ!H19$K<4=s8rWu;n3vN^&6GUa9=j0!T}bd;lOY5c8fTvVpJQK&pga zi+}%Q`|Y2H&SM@iV>5l}FWMKqhLPx&&3Is=Ralt3sFp$t>NnukufBcx-cM~dl(2uP zv$UqC?4w)MCKfFGiq|D)66^s-dvy=Ml{C|Y08F18IZk~W`_NXqxRGS%Id?U1`2vX| zUP?`sL7mU^Whvf?qBTU1rWmeH)wzkkn4UEPBkp?)4mBr~G8Wa|Q2-;8jte#pS_>z6 zrfh&tV@|Kr{OC#?)ILw=@_29I?w+!*L^~n|bdLL%+AFOj`T&a;rw>>0Hd}6dptn=A zFptH$Bgy;1(LBrw<`2Z3==X(Ol9jpvh`T5#y^}c`=Pi_n8tr{aa>5ky(?8RC<WmT-RAWBhjm)*OR%& zb+6E**h#Z%(-#m}U{#+uAZLV^&BesM@hMxkuD zrdt5SH1-3;hqCj9nCyQ!=pP`w<^}Cl??d2U85vlN=hlvdRzFqT?MyJmQ?z z&uo@u>T`lB2<3RfNpqhgfdH6Or(#=K?wGKV(<~c$ia`m%R$#vcuMA_jEEnCk8d5>4 zd_6UG>!CD|KKi4&$HL)R&+d8p@(6My)ZHjopKVb+`Y3_^I2P<) zy$!4v;Z9(UUI4 zqPg&uhZba;f0@sCbgjnfQj>r%^jX>v0EcrvzTyCN=TOd!dkLMtjnce(O;z!QBDeZ& zqp)Gxj$FV-ZLpyW$AQHIw(ElXFxNVTH-83P2V^5vj5Oo=AIV@8i1|I!jkaT@f_E%v zA=w8fopskh50nY1Pa~;bF?C{d7wVQTK!deOfgYt|W ziyE$_0JhV(z_N0%^q0fyb5PABdI&6eqx)h%{WVHPYYctb-K{LS6EZgj3WecTy@u-W z2;F$_fcvTNikb8iOE(pFZz8chSnFL3jRD8vem}iZjFi(VaMT?0kAG8Mrys_CWCZf( zrpK5*V$uWL_F|vYZ!26p{bN}1v+L`Uqa1Oujkfwbyx)1>VIrCy&s45ZCk(8@g}&IIhuwOU&Cf3q@t?C;@-EN8^BX zu=X$>?XveL4$Ck*F5(DXsy2RAPZPe=m$BMU2jonRy+AtrC|NB@z1d<3p5(Q#tHqyk zqW^9oB}7K)_ol8gYL6)fBQ>CgpB<{}qO7z_Glh8fsN3_`)n9vnN=G}rC5)m{VvGZb z2}}ApY<3|(RTnS$s+Botr3I9V$eGx@p8N+MPq9zxK8bMfd`iiZdaS3P9nyhi{(m)lcHKJNXf zZt_MMFw3b@=jQV#nPdeCtqsEx8XzVx;KMP6c7vSuX;EXpIt(M9Q$ zCa60#lPOm#76PUv3%#{5wGn|zwE8-p6e{=d_1Oz8tj6>7Lk9#Mc`wN>K)ao}%V}3v zNm(FyI0)Z3gwR^OHaV^&3r;J8u0zeG{!#3YCS}^MB9ad>mF8K zB!-=`V*8@H?j`RE@ESR!YgO5SP<#5)m#Qz>|0xPcv1CGUz7BH%DzKAypq6kk)E7XK zDbJWJ!!kVc4WpuPbO36y*b2io%+9jc8psDxT>_$YI5;3Z?$8wwu3(BZyjE5Nam$Kr zw(qNckWL1$4|SvofPkLvp!TZ*6JzHmaFF?SgT%TL>oO&#K8W&Y;PB8|2m&-w@zYS>lXGW!pg0#o zGe!TgC(T&&CueF+f+D)_C(?&gK&aQC!3|1Z0RvOC+ycRE^`_Ke8 z^FGZPfQL8yrYCZ2VT?xQT=j6x9O_M1T$G+8AecH!&YMZw!{->TRaO@}aCdnk}L>}D|l%Q96cax_mCVOZoL%zwMPeVZTrPmHRwzpU%9&2n{25h9m z_Nw83i}r zfPmRvwq54tD4Jc&$HkE=hQ``nQ|gnxj~h__A+O^Q5G3H!VAWT5mxh#pZMe-00y8-e%Gla!RK_N$+jye`)Lg; zsUr|d81L>;WmHpAWP4S)YGFG{D@>BT9fYh0RcTz-C-MP?YZ>*4aWTZS3a$>2q%r9S znXyxZWxDWeHDm?%%TdJw*-Ew7E+1I%s07qZaS(P&J`_sMRVorcb>9 zKpJ1JzUHT7P8dLb9~f+PYqs3IMP+MI7H4xtuEI#&qXg13asijtfMlmk`=2c&EBi4JfB02P`ojX+dfc)`$KGG1v=79Uy=lF?G4*?4RVPV9ztD7K`Z} znH-5S4rhB5Qn9v%o}u7iA5IDF#rj&~AOXx-+To=QbYt@Q55%Yy1YnzXmKN=bxpLT6 z5|#EE#ZZ)$fHyH##CN_<1?|ysSz4kxFk`an#z{-aUX!DALw$3AN|7K&%ls(SsSW^8 zW#ShQmch=@Z5GaOsu+KgA-9r{geXwmjr|yYx!g+HvPMv2=q{2452y*C#Dh~#J$b%hbc0x8M z-ek-!;qKEwcugY!41Ml8Q3y8rcxTPkS#)_v4Hb&GYu8&uIckR>P>06YH3LPUZro?d zyBcb*y}z@jo5!|socKe3{JuUUrxYwlSo})$^+s6LqNR0v}(0_(5NN})PK%; zx6RIH;(^}|Dl=o_d14EOpnl?tCPJJ&t@S+_jPmSPCCQcF?w9m+2A^6YVrNWy_RlZ^$(X zC6pCu(N4A*ngodhdmSf$L^_bid#0B$;pNuvw6z=dNwWxbXQ+DHwFb$(?~;OgfTR!u#_VmQ?26$U9nq&_2rf?yRp9&7U1=Bt}d7K-@ax4 zSK z&wiS9sRAAa+u}Odhvy+nzRvIDlbacdMNFa^F4>^~Tq~bhQ62mAAG&|LqT&_`8vQu- z*HYB2CM3?WBX;z9?mW{^yb3)vN~j3LPS2jvy17dmAE(N8tov1EU9BK&dd#itdV21y z{l~c4PWoFA>aZReZO-$dFkJ4_OXl4LzqMZVV>AMH5_Nh~+met7W#GGk{yUT;9B0^F3Vg#@LC`&_bSyn$GR+d@)^Qgn4%8N{ zviq=q{BEMe$rkkCsloEI`>P_trlb8O=SIyjnqBKKS6Sx5;0@KKx@W#!G}gz&I4vJw zR8erHv`RK$VGvzaw|Xx?{7WnCB43xa%jz3;SOdi4w#gX{P-+GfB71@^ z0$aJ5-)+@&#Ox*mI}0sC$`D84cdqh8E9MO=hNn(M@~Hb#h3b8g>j+v%FL*8{OO3?W z*DR}P@9sa&@YHOSE8~Jh(nD(n_4&o0{rP|qq=w5sW_-_ZgjOhi%N4#RK5xar8Dw`~XFwA$ zLA!beOuNw^p*^yzC)aeB6Oqd`9s`(5u{47ixgro}jVgGo^eq7?zf*;WQm8+lTaN{Zkz!#kwx6*C*X76g4{LYcYsOc%hIHhz@4m z1e!^x4zC~|n7?9k)}?uaGs?I#^5U~EAH-_Kug67L+iJv$rPZ5EYzwm$3CmO9>dUR<^OYb{B_0YsRjXhbrSiS|>WGRr>FaBQX z{yF>efr@zSfk2e$y>OMeHV|Or>0zf4#FZwdBbIJrqI08mLdHkmtE|Vo#&V^vUJixR z2Fs!Ml`AIrW`J8BRt!#uDO{HQJ5kXqHW*b@j*+849!JDm(H zngXcY!u|7B49A*V*xC{aA-uY3#d~O#q0aGaJgf3(-3gBp4!d>0aO{inFA>K!M_b zYOf`t(7i~vaZvV#K-xUG>uBybuh`fp!j=jK2#8`W3m~{AHlN31-JiIbLzkrk%h6|! z31=9WA0aJE+%|nZ{jN8J=4BmquK0=!`sp9^pVgoK3;Pl{m;we6beXbHt4jqGruI}A z26fd{7hRq$5!~7;Xe@!30ypla@BfSH)A!O*2eFbH3W4w{MaA7>jlf~R<$B)LF0BdG zu~jxn3tGZFv$P-khARzKzjNpnwBE-7fprMd1P&Av$bigT58U*x^D_qQKoZMEYQ|gV zDqst^D?&chxBbvPN7Mnm6OUT#6zuLRCDu(U8GV!j{LY^c98*Oz9C;{5tB^^!d3?mN z>xwUXEtHEf1PYY|gi`xy)BNf@WlcM?zB`3Qz@8b;2u9k=7R#42>m9#+e^-6{8BQ}S zbK2qw?RYz$>(UfR|06*?6luR>ty0_`)q$$ZtZeb*QC%ZD=M{H9@)vpJsF$EYUk~e< zUTO$?TbQ}k7?hl|?9HwMaG`&>ua1F-v-qaT_FhEZ@3Ly7$$^*&i z2zjJPd>v`dpB++u5O`0*d#_yr<0;y8p|IJ`$91TKcn~B9;P!t2;3Sa~LzE(8Ok_N4 z0X7~IIQQy7BHKC(NJaf(AC4lxYxH<=mWB++MejLx1yK~WV4$#}hN^)=kIt{NXjq`P zpv6~nh2+^~E_DRH7NS|<&4#C9xvtB!L^yJ6Cxpi5sn0HLZNz9C|APvr2NPBscr1Sa zXnMAwCB2gl1V1lnB%DYm3CQo*Wn-EoW|n?v>ka>s{hRuc{7*|(G4-9JlII0UUlPGR z{gZVCb=>;ckk~gyYKFeb%07**Q5X*PaCf;?D`6`TWptz8Lju>_D%2gq$KkP11&3>! z@_Y`u{8-pfLuSlHTJY%}L6~Uj?3X&g`$uDFJDlFPo-MvhFx6oa?xmMp%l)YXMAUOb zw{)7AE)Y11g)0K1B~u87WZc*iaI+~XuR-9u?UDrBc`g9UV0mRb09d+24e;C30@1G- zwmgAS`kGJL&~D!VsM-rw@J1dZ76RvV0dCK86vrVn}xWxw07+1Ji0EUn1IP! zlQdm1&Du3M|3>wdujous?UJWp)0KWua+z>sKS_le`+T=oahu1mK7cg1RfMk@V@#Z6 zXid_6u#fx$wJjJlSk&^F8PwdOO}Ld4pL5;Ey(gNu1~1~+}P)7tVUXCF%k25M_KP-u0Al*Ep0y;+o zpxcvXUZB>m9T63pIwR&O_+RI}cUAZeMfqGvjg4cUbW?@>#Be|M+;wPk z_gh%A4o9OS3B-olQ1UTAky*kPDO@HBH%m4dSNK1naDU)S%N-1~Sk;dixikS2F0y5SnUdbcy8lMysW+ZaBdL)F*XeJjxJu&9_dS(AF{b=~zg||1{Jea!EUcQ+iq?d6Do6+w?u_oc7Ze zv%*L9aP`2&OSwJhj9Bw|etSR_dI~oW{BtZh=)P8Xud!^O0yLfb0P0`(N3iE!YmMV+ zWUVpb(}<0qUk~pEK<%vSa>=M^3bsO6*k>ij=2atoG56&9wA5G2v6w%2chqrhTyJ^X zxXDkvLDopC2USmOfUWKDkPwpcm+I`}A3$IgmTq!u4u;-1L#Cy9N><#-9sp^QkME!0 zpjx|d*SpHu7X70OPTV3e)sK5tAOd*xlzP3=PvEV7Y77T_v_`Jb<{tBs&F3C5wLKpp zS;C#h0f2$K9iZ$O0(-}K&dF^`(`W5L)(FM%E}7@KNouAttiHDWhJ)2MK%5GFrnkXfOugr6vmX;Rwu&-cyho8SCq`cdR| zrPx%J|HYrlT?Njupq3ZgG4bZTRoaSf8j>1PzbBr!|-eOB;r4j+De)>U& zL78BO4rUkvUCEN*%kH+b?a#b`b|KK;_`(1o$S;$F$l`d>MziGr{CeAwfJzFn8#Dh& z_1tHP!6phiDK#rUzR^e;GsF;QBjJ-D?O&=-|B#N8z0o;9{!@&PZXs?+?O0vg3wq*^ z5NX>)8jCN3Rc1MWzGFiJ@amIb`oKMQf#89?BAC7uW2+!y1bDoYCd0OSQ^%Y@UeG;* zpINQk_!O-K=-TI8mSw=lxAls0qtvYT^H|;?u$tpgs_8o0is01N|@6J;Q0oh$ysV7 z_Kguye7ZP5{=$tXe9rypx4JfI3j>T41p!;n3=kv+ic6aiLR&CvPJDIX{XD1u74Gg# zl;%$V{{N`H_7%MzkP9RN=%}N#cO7X)yUyIsMFpz2Lzg$eXA>*Os!jO0tI!1c;jy1)WNL0OWT$RB4F9%@W9)Pi6TSC&phwH!7wEcJf9A9X66i{IR zG`kNChlfZvBlsob0p*ZUr9pLdIk?MEC>6bSQw&ZUObobvj;qRKbp^iJBFQ~3S);k z{9f7NWWSsg0Q~m8&V^gegVY~lO$Kew*J3b;n({SjXOMa|)U5+9>h4&APi5nWZl>Pd zNY@5{M&>md0jhX~jO;q)*+wec=~9sQV59Yw&GSrFU~|NAgQ6aTCx3Vqlda+ymB%xfIwd0y1^w)*A!H0Mam*BvB z)dhA3YMRC_^q4XZt-l0#Rp4c#A82v5~aW8VpZ4RHP`9cdz_nKdV0e%0Ga#HP$TYD@IcE=6f9L z*I=ThVcU@@mKN>qJ)p-I&gcBugUX)QOFLD;V_y>d+cdUP-hx7*`ZxLyLnA{?%nr?- zqgBec`0d>_)!MfDHLvTM>OsYNi=}|KM*+)$cdH<5&lREBXa4{t-83}_MYLND&2u`! z7RTeK)yGsdT+}TkcI{<`yeO3ri-LEE{C8y2XKoxdgPCJalT?q8U0gm1QON`NQU%rM z+^Y?S!yH9)5j__w(q;^7%xSQvT*ApI%Mky&)gP#W17D;uLAN$txTH^{IdHLscTi}a zqW9*_;j@;tCXB}9mgaQfgV_M?&7HPim=MD{q z@0mca)TJsljek*n`j!9eb0~ba5C&FF=tYe+5i9k7{KI7jmGc1pVW<39NgM5+aEDY0 z)rY(aTK)()8&u%7Btf43d+-2>0=Lp+z`SHgbWRN}`_eMlbr8c-50fPSEoWBE8(L>_ z9rcmBLbG$pdJhBX%gS`HvDdsDuf;Df7Euq)8A0aE)(6(TICtvE07@hQtWqt25Y?3a zWCOF4TC$=^f%i#EsuN9@$aor)+&##| zL&mQI%$WrHPtR~<;5;_8^U$5f{xQb4=dPjJSl|>e4L*e2XdvnG5O5FMKv%meEFL9s zl{63JV5cK>2TOkJrS`s;+ZZkaXJ;psulamonj6y}FZ!A_Nm_;GPR?tzhq$_{Ge zP_kR8SBXjzLlqbB9y1HiZuV6p6D(rFYW5u1!(;Uc?kE+^W5PIvR_iG)wo)bLP_`F< zaMMG-&fsTDw{yxgru~>Q4G#91ClHM?9Q#F4_4n1sA1jP%Q>jqodazvx3iLtcApfK3 zsRpPGZ_#Jho3wQ8)V+i@+NIx^r^SKLP{_}7s^s3H#3D-FC$qnvwHKavffB~x%nb+PNGm!sZ> z_ix1r6L1mxt9L(zJE%jO3KgI)o4MY?1O)5Xxk%r?sy_b0Ujy2@Gp0;f(1+sVC2s-= z+d+vjq`&){>f=B70bB#V2iqR(6cEl{{pQx;OZ0-pIqH&37@!Izb<2LA$RJ&zcQyv>ne}&e zOCIyCccxw3_5|u%tjTf9#(qgjxV)-55CG>BT+$C z>mgZ|MksFwZjtP2E}4^Ye(yPAJ;4vtb4oK$uOU$pYZE$MGSPdf9Wmf(^^3p7Z=|-T zUErr4NkiJJdSU8wD}>_^2HDSTUI(p&r(_ibbcX+^kfx!$rxXMenX-Cj6}(Y|NITd! zN<)blOy%|hoA}h=c1ZUNXF4EL1+u)ULwTA-r00CtzJ>2}m4%Nsd7ve?-lUUw^4dMD zbzh@RhO~&UC}={!MXJ5nqYyr|j^{TXm{au!v#mOLB}PiN!lkYA^lhYR(wyz?A^+DN zxdnsn&U|6XcRi5#CBKhX4)BVHm+VYwDH2CB2)$Y%qM-2kOi zPL|2rBUux8ojni17^R=Z7QO?a9NRuIy$41g zrL-R`kOc;RY_L}B6`SvN@B=c5?vq|i)EUdK!w$R?!mOlbH#k9gyyl=ab{3dkH`LZB zDw!H8lQ=I5H`@(7nkVryS(KT;iXG*G7%cq$5#mg%EM9{T+qkDXdP z$&l7x(E*8B={Z{wh@rBq3k#A&GsD&;{qxtp=IeCJTyTx!Ci67)TVT1e6E^?ri-h)) zq@F$O#OU$o;}wLqTeB)*VDoa99rUbJga199>D`Xw1@cT9Vcke7I9=amaSUkXnj_)5 z@{7_4FYdzrB{Zd85T+dOo0N&2x-qEWlF>SCdMuqF?E1 zpWNirXT(_0P8ecmz_ebiEa=kd{_pad1@ivga)<&mfrzr^-k!r)yM)pO1Sl6S-){4) zf(<-$|7RFp4-f_Sgm+vs6q<10z~}+FC6o~p2!Hy%1<0{+sCw)Wj2(Cz@VUkx;WPrA6lSK_&eZq4N zx>S!ipsx^ZB7+USRL8>V6QyW%vp(rSQmb_(k%!WuOg4Jy=e00jOZP7zXB46h1}$zS zoXtq$oZ#!cK@p%(IP^mKe70}Kv3Yn3_(fhQokr~}7(+K697vG_QaZ#TYU?^(m=P+w zx^xct1wd-Y&J2Z=uJ3bP04fr8XY~(r3rGu0;(K&P=|c|mYSw`Ec8uD9P!0l!nw}hl z5CDNd6_)TZz<>*rU1L(3q`8kNa5_KGrUVK8I>o_Uv@(E{6;-UU?Kt&X`g2;^%dXct z!1aW7JA_*hxjfE2x|S{L2~sk081Ue0zs3`IVB09*dvM_kDFOYwLRYt~r&zMTas{{o zxw82s`>NmGP08v5{e4K%0=SYs*>{A~Y`*HbB%zA%F9MU*_)O$3_y$4V7F?3Rr;glS z1q58#6oBKH0@{~4ACfa2qCpQo91wvQ07~4hW3FDkP(55b+CycOMx*pDT8tY)92GWu z86{NuA+FKEV)fyhEC8Yi50ucu95fst=lTv`!(uG8q%1Xj57Go|kHXOiXHx+eiobqr z<}iw=MOgl<%4_lLRy0II2L8@J+xc!|ak>>0(AZgGp2FBcF(Hyk)H%j+sxcrQfo-)V?z>LFCd_XSrW)CqPxe05uom*oo)NVAywy!;k0bN&&sNSvDd&{~%|paxS%6@eN4ot4 z_%JC7{TpYOmKiv5i}7K7SR|N2<`RCmk3Om~M+4=jc%6CY($D?;(;omf26eR0OsBO2 zY|6T)ca6!<>`(f6yH)f?PHzm;r@UcIfshj(A%;{bX>FJ6NE=v zKA(^OCN+^(_3Ex`#-d<{*pCN|5h@COE?9*#)fG&C9fLbS_bp_A9j%39mLAZWR81oG zoPHiZyeFku^l!3>x7Yw^$6YW(XIiu8KDo+JM%dF}i~+bBoRMNZ`S~fotkDrq9(y6G z81RIi6TuMI#)N9L-{>x|_M*|X3BkBnF+n$NLcKLNvX_%@*AAR_zQF0nj&!6WKjGXB zefNrYq>1c?@d*Z;r(m5fSqf=GdC75Q(wmb zfOrZYb-Eg1K&vEB>TCbjC9NxnG2<8KpUls}w zhw91|ydm1xIyw~9p9?x;9-01rK~tZIt+e_l@|j%&sd7-w76lQ6@>{pn=mD0+mpyl+ zLm_lnHDn1x?Elwa!4*}1}k2DAiw-Qq4`q6SL` z|D*aJyfS)oVc*cE4X>u@h(P;|vO$nY;(s8iW_0Kisw59;vx5ud%HLcv9jg>w%hKV~ zkJF6mUubd|ZoX)lozn#~tc+vYv+osjtTkx`bp%JL%T5Sl5UDJ?0h_YP4RfLt!4uOL z7y=7O#cJVnPeQFPlnBAnZw@}yB|<|j$-^BkCb26(#u5GAO^E~5cu5fAz$R=NAEuOq zQf#4t1u-6Xg;h~8GIFa6k6k;VL%-y~!tXx*P8kPg)Iur}0plN^&O}4kH(g=BkzJWhz41{nxAUq(wnS&x{(9ka2#>i;@370RqKRU`!V52jFe5mFRR)cPK)^NeCM7Usw?w76*WsN;Hiee&=F>F#*)rI4s zCLI(<+p&hEn_hY+_BTgBlEqU=4Ra}Ueo9`!0nxB>?gg276`*`MiqSBb>7gQlGv|TI zqW2K0dsN^XyloGag|)jJ;b{W1Z40;=LHy}QAAh2FnIKI!E+B!r>jDyKmFF2}RURn| z6Gy8o^xDtaqWl#@&SZw2MU9+5yS`H;*sIL7%*fv`Rm2b{DVu6YwMzH4RIiM%bap6# zjDcKooYl7{3IZ1d`USTX#RNp+Lkg5C=k&1@?P1Z3wGU-Gq$o~ABec1%_7=nLHCB}q zHYP#60oG3}c8kBy`NCzqwdRfda<5pyM1-z`W8VwcaU-THtiiSd7t)1RIZE%pFOj14 z)nn@=nLJ>y4=4?6bj4qM-1;qab*a@=%QblK0eWPHMRRKg~id zpCJf=``Uo-9JvLc3v0N@W%jT@k+hiKtyCGe#CW9b)Cpq{!7-*#5Nr#BZ1g2fJ5^Zy zD7y?#)H>A2OW%`Bt#w8R<})rdG=2}(j^<;zQSB+qcFpRKlsN21`^ z58XN28$bcSOZFFjByr}@(a^lOw=+W@18q}_Q=tp;3%`P(+Bh^CV5%iBd*j@4Rgbwb z0PO)ApQpU;v6r)#?7{3=n$6N#+3C{RBRi;%mIqyTSg3>!UP3}VpvjkqqPHxO3r)5+ zbgmTnU3(;QOW?m1!vqGcwTl6cS|GuRR9xoMm#c3oQn^_%GiVAiRMTdHj+I=(o-PRW z&Si*Y{06zun@`NgaN4t{(v+}73@}eICkWGZD~pJ)ihJ*l8hPnhcjvcBcyN*D~K(%2#6KCqBixl?% zypsOf*RY4P|4XyE6Q(eN3l7;zv(-W-Zy5ZN(Ugp|X2o+!Fxg?cTmtM~sS({0^~it6 zoNA1aH-L&h-Ml5-fA$||--s&YU2*(h{eMA*O$%Jt`K2fzOmNz4o#TV!w%2(%cl}ES z4kDrT;#9QSe%U{{sNYP*f~}zUJ*p?EeV0DwqJxssi+{F)A5=q>v$i|E%5@yCU0_k1 zaGx(?Hh7Jp*Dj&gkbV!o^VwkwZ`mQdTj~EE-BKTr9hBO_%e+Co!(+xPq*}Lyp3p(~ ztj7*RNNBCd@kax?Ms&M9S`@^5hoUFk3vel6LqnOCR?d5{Z}&CO6BG+kJXEe_jk^?V z|M;Cs|BwO&;(?_t7q34AHCg&?j*)5;3*SXTc_YQ$*G5G zVztttXs3K+}0{TLzpVpZp4(G+0{--`?fTrO58WYS%b)KKX##p9R*|pMtS_73^0Hz;V z(-F&YZzuE0fwe4AMlOX)p|R|*DqdEDoM?evW<4Ro4j&((iC$4R-6@Jz7xBN7l~&g5 zw;n5z?E{_!R0!tIZPKrUAl<9q@+VsNPDoto3jB#eJAVQOj^wAL@mCME9KbuD`K$oq z0jC@4wzO-}DLKAADx0yUaFBH_XZq1}A8-H#o49nVoub(JbYs!`qeOt?=Q3a}Qo|dM z#B6zTNxAGnACXaCPaU3^J=w0wkEoJfAfz? z`A%RtuLUtA?a@9jdS3cWsOHgKgh+TPn3+WVJQO^`Au51O>__M7QYgGOIpTxd5EJIH zg8=EOn|`g;TAwd@)(GwEQH>JC%Mu)<2}=m!uK>%25biMj6Z|Cu`}ems%m$igmb2 ztpy`%RM`kKz=We40)mJL1|Xw&&RIi)D38+H1eOx*k3=|F zfU;jx=rRisq+sd8b58Ww*6`S2+;7P=3Ln&W*YN<*49X(U9c5^ma0bwRZq{vKHs#f~$rP6cd$^gn8jXA)^l|CTkO zol!=F3n41r&Qp4rr#!iT`qsH_U*}wdreeN3eeu7lPd^Fh^3Q>6dnIB>Dwm+YL4wOV ztZ9zDK&?ChLF@MLKNFw*6I^XlQT@`*>NLIBzGT*gc2AaaFZ5D*INvT{WSNK z!l!4IT@%@gHdM|)+-=hN-LLfbfFs|ru2JM^TDAsemDUsDz9l^ppb@V4zA&FV&GkKR z0jaDAI8{Hv?4mT5t58p#>RYJnVJu|9<%7laXA=79ngsY&^Zhx96~EJ!n6nQ5jzZ=e z)tA$uo!)7FgEbEJZwRooSvi1`w0^0@Fd9VJ7koH>8Q6{g3%6w*MWI%f z_SfiaMxZk_*$<$km|YG{&Bj4{Qd+#O!7Z#a%lo|6Z$KG`()go8L<+C#^Eom%6!KGL zlYTR1b3IUi7O<5q-T_sqgx*89O=E{NP($S!Kju;gQ|61G;1+-sTflKN-u`7$NaS4j zqMW53NiXCDsV}J9A*O(4I=TY@j9L zli0uRusnM|RQg5r$>pel83EMJLW|IE$OP3*L8h;6@!fu!9vtX{C5QmH!zp$|U2FOK z$_cEZOHJYeLSI_Sz$$F@l%FV>&@e_-?yqK=1cc9aWNEUL2^5)0RY?0>`*Yl@^L!L$ z^Pt=nWOz!+F8vTXfM?<&BskC!1s_lYi0MTek1xARSpjemnEE&*z%$uELzf^@?Owb2 zJ+sDe5Aj8qAV~4RKGtVPPutI{T}~+XXuCO5aC%R#;tx27Ymi!#cAKXp%#nLSR;)7h zrP==irz#D zWZ=jeEBVGJ+NW#z<39b5mxAlUar{*<>7=EE2fFN-(-Gld4V+{Jh$3KDy$Q209XVwrn~0*YWB>xFj7zS>dk-qR~V%yp1k*1Ffc zoUDAib25|!ibkU~)ZbzCzoy@ZBV$1c)02pES4 zJVZT7*CxhL9SIZz`0|2nZ7lMlEKz@lW)&EPk3XyM9~rmgCs1;?L$Ui;XTRwINn`y? zXZ!fk=U&cJ(c*CHTd-D@1yJ&IXlA3|wcore8;gmHh!$zIE5HnEX{fl*A2iHP+VQ>( z>zjSfNuCmr_u26#+p{U{Ox9?|0Wp`e1F^wc4MLUBGM?oQW&YMm`&mA?mNL;ON^x|} ztOTM4^tLcQ@C;6qX@6luU+^^xWpPp=8~v#3l^%3B3x3!We7<*k@`$dO#a(v0TI$f( z{;CS|4Xc~_40*0h)7!Ju?o6K5LzV;&pQv5p{9$ghfvw?!!0FHnW|L?{Zhe}ydkSYC zKSi$^{oZ2%L3O$z;q?5Jpjmm*tWQV zxMTrghjrFX#6rRnsV?H#zKhSkq;X=lg;=p)fIS4pzE|v@MMaVcNt7TcZnMTxYW=jl z!}ftB`8`KMAQ~v!|6b@A;>Wm+BDO$yd}ogd7<#_bb}^sHKqVH}A1dfa==1EgW^WpT z9$BQv%N}UF`)mA0^_PE{K6#*#<{fw&T@N*n*4_Jkeu~dR^bm)Fn)rj_a*kq>!eN?u z^@a4Dy^2(5QlEeCh&kwY?qILVm8gDQl-Gi`_E`h$U2_vM6MW}aBTXB!1P*><5|-!S z#n&B&6q_jP+o^|58$MJ8$r@BS--G%0eaRi8MAu}292Sb@`DstsvCNjN?emA}MSWW50_F$Prw#C~HO_iy z&Q{miPkkN6w8=ApJ(4|BpAE+;( z`P_G^PhP4Qi!3)7a>r!Bp75yTWm(zk8k9BFee#xVLg93U{a>*FNOPNCQ3K)P(ZjCZ z<-N7YGb1?IKn7;!gI^Ggo9*a(0y!WEN=mPfa^^f=t*y$u#^8d>1@deyFM$Pnk=)UX zobleR?|&g-mQ5mQ#M(G^?O@O1lG$}tETn#GVa zC23bYBdnPdlu^5Pd&Fl=QDN9D|F=+s`0rLiA;S^)F%(`VvfaYfcW#XF8cN4HT03 zg+dVu234~|sO8jKIFw(tnQy|_WTcMN+NJh-lE>N_0>0{Jf9W}alSpsQ#PNNpyItSML{b|2^DMe@v0EP}4q>iw=a-yH(`5fRtaT(Mw>9 zo@fF_gPEe0%#tILeAr zcEXQMou_sDrT!l+QM_L1gl76i^}l(11H|XX&Zx62Rf1kCO|F zmNTJZrvy(*cZGXvODQys5I`)jp~;&g&n%>SJ75*rL22PO$GwX{vSC!-=*fn# zl6SJtEh+E$4TDmjGnkT}PwuhS=2!#Fnw0ljda9;k_osf5pc+R756jvuDKq-nDq`Nr zF8&^qwIRIb5*I`9WTWrHJzyJ|;+`QW5A?Jdsw zF7K*P!H0+m&z?sfv;-t#%cK5Kx$skO^yFcEQ&RM3zwbpc_pz~Lw-4_hcY`-SCD# zHJz6gQ!x9J`hb`O+-fJmCLdbJ(5Tej?y(NtZ=urO9E*j5%+O5KH}62Afm;zInryqe zZR#uUAS&5aYkC~Ilv=UIj3*!z83q>DQ-7~S->wQ`wWEU6E>_Da)5__JI8n**P1pXJ zzCsjn$+*1?+2=wACpc~(6%h;WUE}+oC&?VJ0FD^USfS5eh3R>9c zQ9H5mn~oNWBF|D70`sd7n_$JFW_cCkW0DSSwxrh?1~4kan|rl6G}iG75>RD8_}w3^ z4j_B!w4Oz{&I1wkfPDk1+zJX3e%4eT7Gdr+c(I_*-nF<9B6E65c7x2l&(-WxMkV8v zERnV?&1I(31H5i~+?OCYiu$eWkwe5N=e{NY9AlSpk@hjh8BFZFlR^3Qo;LY8wF>=o z{wMlJe*C_lTIqnt>~ZVl2?WWlfV4Qn>nEao{e+rpZ$}LN&iDj=swnj1$A0%)9-m5n zv5H;^KF6|YvMbC-l+KcsF0ZQOFpcYrX^$E`vjdZvocgar2+%AP={u(6qgMDA zmTT6w?nTGBW|v)*$iPEI_xm5P*~`Fb%xW3xeohvr=N_FJmk zOo%$bxQ_=%23h<{|0ocyc>nDGtBHWLT~*{$C(gSt=zc5hvwC>Vs%h zpXmcSzrY0hcA^QumhLdEw?Ye>u4g-(lbwvTgI16oT=Xq4;u_~O#|ZY>-dwxQ6&dKE zsUEq`bv~F_AaDZITTfZ81Te7?tu?*Z8h;;F+e21K*#XBpWEwzg)XnL6l0%;RB5=u4 z$V^#vF5#0tg)M(Fm}*m;$U?Z|_IF?&CsO;y?zHEJmG)+8wAO>AR+63GR2ztR(G1iv z1D3u{l$R6BO40(aMR|+B{vYXU-D)VL`~3jMC;g$n{rCf51inw4n3dczrQm;H3(&^w z^T!UqmY_f82nDd@r_~?-#^d1&DxU!plvD{}J2%X}A+kqw3sEfq5D8NF0rq)MNg<(` zi36GjT0i$Q)OVMRa(76Yn$pw^xsw_!IsmUX>eZ`_o0Dq^E<0 zBC3#naKPAS2M~P)EB2hJHX!T0&hc;a9$+nx>y*ZY5_7QAK9886TGcksN=+*;EE>zP zU7nB3c>elE^))ga?Q$iDI1fC19@ehKis@0% z{==IKB85-PVRuXfA9X=*2E@tQfb@H*H|$sLnKh7m3FK}Vp+44LFY$ULnyj7+iZhK? zEa9C4AZY#V81psU5>HFJlz=G*NJrW~u}g3cT8}Tk0?VHaGx4rYUvY3B*5d=Q0`C8&-b^f^MDgfTBWBajDd%I z$RH@qzWjrJh5h^w{`+o!I+YT@yU-I>N<$+WM&Y|Xq%m}S4$X!(%=g-Owm3g2dVb3-`sy% ziPx~2e}TkY);JQa>Hmf2RJ~vjOcG%d0kLEM8m!dYt-5JAbXE*cRL&nql!Bn&nadvS z1fUxgdXx;5oN$F^=UYRPX7mG-CF2f&=TIM?wqaez}7kL>4e{w zo&(L7>~Dme($HbnDt#ntr;=u`=G-EA(J*aF{h6=yfYO#ZQR<{T4AH3%a`MlaYA;FW zNzLoc=LjzCBY@Gs?E}hyy(XJ?K?pl60T8GpSVES3f$c@06=+%NRmNC<$Plr*%>^3X zs8wa534h@4fXfNj+5Xnv|MMDw8&fNI%K4M<)M@;?3=8}U1v z!v^y5iWDevsxSVQ!1WOErN$}3mKY!K^Nz1NAp%$`3hRIV2+IH3Vab zU7m=d3Me!dJ%8y!j`!##0JsxZ^A!*^ITvn8ft>&RCHKApd|TRLD0E%3i=u5^MAd&M z=TKO8V6S$jVE=2E;lM89)l1o@gzfASG(l_ro<~?O!Zt!X@mcRkKil)PpCv1vIzkWf z-7DFH0=<0DoXT!ERIVlF&%*jzga0a#-A63%x3+5BdX>5f!nH`FepBSmbh}p{UqDL_ zVHCji-y*)vKF)?o3=Fm;G&MyjMw45Md-x(Gj_T<)9jdCUc9dAl)P4N4zT7y<~J^oNVNNP{(cj(1b`S+;~~2mDpx?@TSeYznvgd$bEPq8h}v z^lb;iuHeAbdPL)l-_h88I^w!L0!55z;)JIK@Bx7LQ7za-9GPk)%va5WS~r2J^JKLF zE)-WoLuE-fC^mioqiq8JKxWcXxS&f<%-9B4LtHxhh)~2xSda7+FbQ#G8qH{DicWd4 z`n2P`%-Of-n4IzyQFGyZ?@DWX!7~rSYK0rnCI!jfXR{k*H~j2;m_LN9HmWvfPEw~p zEcaQ445G`jw05jr7D= zqv3d{y2aG6i-aUzd44D}ThEf#)QK^FuNGxXUTV#|fuPvqd zf?9`=i?!u@KWrt7Gjtp#yQfi{~jiZ^{RyKO(^b z$k%k4CiRXrFj}!l9g4i_7pF$BVG&rWj9Ij5_S$jGu78syS^Cp91YYNr7KPCE8IrSR zp6q~5pmVmX*xsaLzEy9&OGz#*{i0TZ?tN;Z!)n>8tyemRw?0f*`W>7fA8=r|8ykB2 zP$!E1qoCX8_sY73jG^~G_TKIAY7sNocR5@@|E+{?QLiF4M_9*gNR~;5{SRn0{AuAg z*2-iPLG#6LuRi{;{BNJWn|>lOu&n@n7!hm-VOt)ypjo0qhMVSS|LG=sMp3r2OiYEt zbbwzA;+Fq>0Iw9@ED{MnHy0+vm>A6#>dFC z3!e~>@RH*IPt_j{Y~nEdu7m0G-Rh6uPQjABp$COkcm9k26EeyGch&>Td=Pt8S8kND zK@~#z>NL9mQ$Vc0vFDjoVKmUmV_A z=N9t>ExOf=pr=CDrD|kCc6Iuocu?2_2g$@-}+Ydt@K;$LIML~ZG<tB| z@TREq4hU<-uwfw;StIa0@QW}hB6-K(B839N-v_%()f_h<)Ag>KCYJcyk%j7z7@It> z26O?ot#X0Q&a12e!k)u=t!aJ$YY}s`4@!bzkrMfiq8|gWwZL&`Ymi(!h2zHjM5;an zY`HcT+^+*M2L`Ss?GxGY{@(XKLqfoJ_4|u~z!3l3g(tZ$^n=O?o;#5up`~`F?}O^( zw|)ee?cu*JCULDyO9frvl3CNrv~F+c#T}?s{}NdBKT#urOxCZ_7p#Rf!c7B=iHL^2 ziyu2{2Q}E$TYAD->ur-dEnwPR;Nj}vwI?;jlX}^2RF1*#1u8+e)`y6;rBxUC{AEf( zKe9tUg=Nw=y-j~~!EXZ=d441<4mKW|$4yn-?rj0aQO^ka2lXRxSgdhJG1tMOtFaEA zq6dL)D(2Zk2TO33S2qs;SkOw9Z|}2tChqAvcaiTj0$LOPemRz?^VTeDHP(=gvCsJC zH)##D#sX!K?70hdEU!JXKq;tf&FRJOVV=fa1q6BAi2Y>oeHw_cw$9Z5biK<~FTt<1 ziJ9B#o?ez6U}r;0QUu9s=uaMN&Q z8D{%(f1`biLxppP@c!h3iotpC z4;GImENo_QbPJs-FO2}TyoK}!K_u2Roc_G}Qu-0MC&bHpZa)iK68m7F5pfqZ=U#gm zxDnn3>ZGa)B(?33iNTmS16UZP#`>ILl%yKXmXyyKqHE9XPwd{@jQXKq^%vD&_^sk3 zT4S3!?SUW{jLD6fiDH$x7NTq1cU`N|HMf)Dmu7!p+J(v~{b8c1YzcJ%ynPSvi9Sy8 z#$jtMaj(Uas>V{c>XEGnwyxG<2jivsmH9c^jnEp_K#KCE68K4=Lf<=v2`|fEDTdjZ zs5$2pGKmJSzL1-Dk@{|d6u3f}PmukDg7uD`l;7;tH9HX#z*IDu$-4}Igw<3OfeonHykRkO7XDz9GJr%GY+W&RUkU2o0 zbRMo=4X!Az6qRd*?7K(Zdr11C-g1*;1qQOqxv$;=(Y3($pzy4;Ts+zQ9hy@)Lwqg9Xl)zoxPI6(J5!yr5?R&hHbtY&o&;DzVRD}E$i^xZz z5NV~5uohQlC&s<@l#(v&yhiQ!#dkjb+>^>`u|oPd;Mu)u#{e%Es3)O4_2)|w(Hi^3 zdVnp;<0Lu_$j)K%&U=q@xSgsdFWr(|4xlNb* zIK-jk+4#t*uDR^O81UootMnhIz1j{HzDs01GEg*=x3+%QvT&p+O*bXAM#9FWSuoRx zlJlf1R`ajHm$KB_x@|zFbWm#5(dJz+=+_c&0my=Okai!gV1CVWf`MyfWbNA^-?|B@ zlaoQv+>Y4oV4UHnjKCJ@o4-`+=}qv&uU7xfpD2eTp_$z@9ZE-)k=K1t=T^M4DNp+Q*-K`cXMpN5zRDBUEvF8mCWFZeQ0uTfM?_*GUu_dLa{|Oed6nmmBxm!KGZuD1He>4}jWkPj-M?g9D-}fc_qd!Bvws1YVVTU`k}vHe#VF_Y52ZB~Sx7 zl(y;fe_h38I~apjJQ#81BwAWQhG;U|N7S*jCBXX z3EQszn%-{7!3Bqm0{EVI05q$yId}qcM@^VonK9>sgb}eO)jOE_-jmWDZ0bNMpNFHf zNH%+tBpoum87&u`m`iuW80a91It1|0_3%rX#Ee|iNTKz74(j^%{k7Sh{^0R*iiOgS zqWc=1?3QT8d1?Yh>oxMqJ^wYqp?8o%Lt49=u+NYx6}JglU?tgGVagesme}Tua>_wR z2_Gv6^B`0e+?QX7yC(q4_fBv6Ya7V_j#Xc4+-i-({I0ZYu?AAok-DQZjOSzT5I}xcdXAeSNxmllhPW7n57*D=R;xr ztnzf#)5Jv96%o&v#CSJY7|FM_7=yDKp4Ud$ZT0u%O&s2s=$?MlmKiWc*64X|`ge~(~6-^Qh1pYOh99ZQ6& z9B)kj{yz*&a3<57tPmu=WplCawT}td&|Uj?!jNe=>5H0den1bMT>*#u+w?pRr2MrH zX-v1SR*xuSy81+mPs;9W`eKq0^n!-)U5YXY3Rc@;Okca-vmFybFU2~s zlgX){skxwlbTpzUbr&cHpgD7$R(4SU;4A2tR0FNVV{-J=Tirv`(RFq8qu>15LVIZl zlKhxz7)62tdW%w2Pzshs;U*4@e1CJS+( zW|YT70PO?zhZTJo(-#Up{uv5i3JFM@7a)#FOw;qh4o3rxRITU<&Y%tf{oIoYa4>_o-6xFEZc1;pi)|4Nty3)+)kd_<4K{SZ*RE!`G z0%lAx&{a!}NUcU9AQj8v@IO9>m`M%zkQg2rQijpG#p2Ct(1F&ZV?R70`l)-AeD8m0mHhRgGBZpZ?l0JZEnXaeT{=CNhSc9^?X2oT?#Gk`z zW1|naW_?{&FrdzgC1d=&7|Nb4Hz!zyQ=13_#^ePdD=+Y8UyC&{*#wx|Gg81mQo|iO zWcI8pSNk16DZ{P;vVvD7lZSQ9n*)<9RP=Tgi7$+80*%M?1s=e>I6`>4_u<_&1bgHh z`f$)#e`v-yqD;cmUkek-pH`oKqWiL;2SKo(^KT8n!IoLsG;WK`Bnsu{_M@NNl&Y}+ z$3$kYd)H0j%u|z42U5@~MmRZ+K2^|IApI{f4L^5<&>aODNY3tLWphOOL#-y& z>SqNg>L3xhG%>x!4boz>eF6@oTsIt8-o>EA3VXD>{@-4Yf!eTv(D#-y0jnQ`EI|du z`iVL!j>IX^M2HT_Y_d<^@HX6~((w4~ZJ|sU^28~5KE#x%?Y6r`8Yxjcs_p1y5atOrmnRbG70jxThm84f7=%|dH_#xUdxADsw{hzFj9I_V zS*9`zs_hB@miKNR#^$<35X#CV!5?0p1PHe!p73rT*sA^qMYE$Sd@T??c%lCIdN`*K zV!|t4wVeEcplM&7QY_9NE8KV9Q|GN4X z^$j*Ze8UlvS_CF5&AAE*RptP0AElvhhqoId0xuRymJmbCNH0%n-2W40 z(*&R~aWk#{4~wl^c4mT;eENqvRQ`cfl>4qPZ$x>rq(V*mXT^bpCI)pCOk{`&jmfM@ z&*-eOW-Vpm=|ov?v1@#pZvfsFZEf@l{3btx5aHNSu`zGnY1)y0$eIhrdjzF4|ItYq zW;>!`YkZa=@cHP6_b0lXp~LGPss_?+f%q9t1^FLVFs0vUkJx7qkLv7Byw$aLbvhDO zEy@S$)?Y?fxzT&FBUFKXNY3qA6tWAx{qCQB%Nxb+!ycIbYw%AS3JJw35rX*1J85eG z8qUs1cHE_!ZM_2FPF`{p9g+n2F9Xyilm|Hw9)e{UjpKi=zN%cZM*C7sn5jbQ9-c8i zYUNsDnX7l4s0KSoh*9*sM9bGx!u6>mq>0gm=H!+=V3GJ=Jw|j^O}7$|KZ`O5SU_}m z+|zRQ<1g)MFEBdv5I6*{(cX8c0mCT`^>%sE*7WC9b7I-xgT*d;LcN1)P#IIJi8jj? zfkqafg(coD>A0gs0{gsugZ1QDjUeQnJyLf_XXm5MoEVUq$o3eXTG>q=u110DS(9$> z?EI>asdhvY6oD6QwW}1?S{EL2kp%jmy7r-0afCes)UO#cecd`_^PohLc0uA#zx7N9 zmo%%WX<*)U*l2qFGIccby=Mcp*clFa(|^W-OV1>$J%a9XO5~OccagKS)rNY^qZf56 zcCjzK$p~b})?w*D0H0pUnN$le*e~p4Yb8=}Bt-B-iM4oQp46PSVTDC4MEIo9LmSOw z9qO$Oz{7;kctv)bY2=Al8RxCF3fMBduJ@pDGYU{(-)`!XRCpY-?7bz8ai0XnQ0J?q zjFB5IXqdhSUgs*6YWgty|9t0f@}RNS84jJaYBaQ|{Y&8jNQ#n#=)qlquXHxlPC0qK z`rpM{xC(L=?qNRqBSKCChj7bVA|dyx9Wk%-GMO`~Sn+q9(e1@W^!2Z5Wav@IoxPj$ zorQXPl79=$&?CT91;ARrL4OLm8FpCNCjw8V7^U;_NNQRkv2LjiEyO$jxO;b^i=P9Q z7OY*QSS(Sm7c9wPJw2~2zV8}|htpeM{xy>Nr%j-}crDld?&EJjfyX8Q3U150g;fFw zokI&J%LYM;Fe8a?IDPWTdBMzhe5T!YmPnq*g~FGOP7%c)v!2QiP`lhT^M@eWd8U`S zKG6-j-vI-W0vUqDD&>bMWNXIU1Ea{&1p8($Q*ty8q;%9a{5b3p`lq|X(GrA=#cy0f z2^Ir?z|gI*K&?4x`3Qk%H2|0bkICaJ@bL!)_x(#LX9m-YNb^ZA3E1o;5#moAqoUbT z{|)xQO?_ZEUu^d*I!7#kz+#XL_pCB;gZ?wv172%BLgUKo8@26pejn73r`(Iju~~H( zQR;;t;{S9bs zqmCZ4`|mB&Aa^Z*IjXQ)XO4;jDF^ zeu_uJZ5)Ow=e!PnKyB8)pcMM7*AGbNvf4`(P{+cjM^A{bdW5`|+YQg;2Q!L38Py3r zZWqo@!zr0NazO6E!s@7}-)=Is{injgcJ!aI=XU<$4@mB?$o{h&Sfqlu8O`3x3(I5*dGe zwBUTnquUiftQAK zkSiU1{^<`N|GgZ|JV+iZCE|WxeMwm+P4TdI@_~ zUurhb=0;_sM$PYLu>eC@MjlcBdXOV1Zk!A6DtbndX0Ye(ffxX3+=t(sA z6~k`L#5-y-dnT2$1)dyck!_!T-OsB}KZk}8{pfGGO++QcfeOOC9ih0J+Dlv*$2?`p z4K&Y438_&UKUQqVgCBmcLzTde{4<#pJ71t)(YXgMY0Yf)>Y%ILHKg#aWNA&baz4>y zhmQ{G3>#Ma+kWX}<}DS;zw{RQ1of*hUMIK0=!>Oh-i@ z@6V&JplSEawfzNfySUqP+`Z}L7Ul9~NJ;{vb{fp|qcp$;!on~UmE`8SD+yM6R_e&h>L~_JIcSisB zpdeVMZBInsyA$0ib&B@jC}t3-VEC@c&jc0sAX>LqI4!3O{xIHfuYIO~W0hV|!Y(LI zwpkslKHN&ttKS6V!Oq;leK$*Sz8Yr5V5M6p+4SC`NN~+|D!R4OeSn))=4k91eL-;R z`_|0uK`%!+ca`jl-ONLqadrircd(u;BXVp}G7=|y%OhZqwnShOsE^dz1jQPF+>Mj`a{>WIel@JzUO zw&^VFdJZ%O3*Ptvd!)Pd7S=B=U1@ksK|{-n0TQ9%$)a2&A4$3R{C9tZ5cs!h7Ru&O z)DEUJdjwBOAgEBe!<*c*T`X3##$kdu5liCNS}le zTe14+0X(t;5xyM2dfsng7?2@K$x&JHo(ermgG=k}4iQlsH7keN<}604idG51F@-f*_j>l0oWbA zRkyAmF?<@&=(w&`t-2W|KS@c)4+6<&7vUFM%!e{!!Fw&(e&|4uTky`t{zuivA6Z!e zt*I!8u_5*!1I1W;KI{>_UswaKI71rH3;C~q-36bqT7Tfl<{v1x%Gg9=NbF}tJg&7X zut{i)C?4X-hXxZ}t~2cMX1mM0CVtH{ON8tXel`~P!D$!`<@U-x8v3+t3mni768FdJ zs3A+&PL?~K`76xoj6#E=9uCag4y}V++X2=GlPJ0`n6adPJ>|8q6(8vQt;TRiZKX|W zm0nAl*a)cYv3AVeo#uhhB@n-Rc>5)1U>b-C-f#_~PaUduJ#^r#nYR*n;n~|T_{bbz zt|u@nK#7x%%~vYTuwUkROO%b{+oHu+72zIhMm~Bi#_Pkev^>)bD}KbVq1xE$Ewak5-Y3p zjNf9w(e~GVraA!Y!3EVCv4^&o{F3JK23t}J93A_N>_`$Wm*NoJim_@Bm3`}6N zuAz2(&^YCs2nb{S(q((#uFzeiiASwXw6EXq%w)(d{^YFCt8VopM3aX*P;L?z!@QIc zI{Bunm4~2o+TCBWW$lod@%~-^vii%UN?{`Gc3asj2EU8wnmN^E>Z&@s-sze>~-(QHH>1?e1bGu)5~Dld$Z zF`mALY!x-yhE8dUqoiEwG+i*CuCtn9cq;Xzhhk3RZ~ZOy_;wnzw>Re30g%N+D+%bY ziJGd@Tt9FsL$n?UrmdI`vD+Myi6}}4;&b72P2G$(sMdtnH?bt0LVs|k7fgEbP&*G} zAn4D(D;fR{R^fvfgw%VeV@N|S>mFMJ{~f){*l7GP(EJ+`2^lnk=%l^GBkxhg^%(0X z=A0wEWDX%R#<_{T?3nbrDW@pG4wy`OU6O{$5-l0ShIDMOn8tCPI%>IU=2Bm+CbdtR z@6W9ceIx?;OHO@GQ%zWMZQ#)^y&ZZGHPdv|&o@Po6~f;zy-L}_dKw`SWN;?aqSF=? z4CO2zvHKQj>%VReC^> zWd@m5!>eAQ4DO4d(sKwI*2ilUH{_9=f;D)ldwY)(qU*8%&t1e?Wlv_z( zaEL}=1`cuIC%k^)q+sp{NsI?3Y|3T z8!Q6Cyhl@N(S?dfLw{y`=!UZSe%pp>*6{qcK6Shz||{_3OoO`b0Apnu2RK+utBz=j(xh)X0DY7=C%xeSK^8oK)pFJ7E>4Nhk$0E3 zO8Xnrb`5I)aSbZrwiROj5brAz4$M{lRV`339OlR~9kVfo!#uFv)ss`Mo)!STHx<+6 zgP##F9D4OhHi8TU!Y0v6d$?-bBQtDwkS#;ekZY)3pVi>y5+09gk|S+7Xm?919$gp` zb3y4t4L@mhQ*dCMCy@zxrnEhJk=m#A5%Pe3G^+*w&Lgeh2Z@xH#%Vq9%^{~9J%Xm* zbDtDx?NvAoWJc+E?vfqeF&ELirYS^ZF|Z`&y^&Sv*Rm@8`f#j{s(Jw4A7;OuTeNU9 z#>H}sod{W5GG4yg<<}?;JfdKr7}B|rBb6krEn@A-8|H489-MdpQqr5C=^EgiQF>dPUU{%Sn^ z1HbnbtO>Wwkxoo{Ci+cn*8(B4uJLT7HX_T%7q#Ee26}oa~ z*q)y;mhm?R!OY(H?D;-K1G~)Z84BMcOEFzJ0)+ob2{W0hA$_d^`VnB4ENhWI%AQ4B z;CmSPRR@C!Rtx<4OfYN#s)5%kqOt`)Py$jbV?g8cKlo3K)yZ>n$kSeQ7wAfb_Weop zkETW|9L3a6=x*(i&oY&KR#S?vi8kf`^!G?spuTBGiW2AG3m`ENCDF`77JN%kIJHb7 zK?20C?Yt1BlCi7%ETWs;$OsMRybAJl=niHUNbR!tx68cEDe!3E60^W16wK^A&z_SP z;Wgbm%az4nbfll?UMmJs8W)VWsJv|p&ZAu555P}nz(}$NT>+0w-)r<{c~17OfznmT z946VQ49h_QSaMgvkPI49awW(fk5rddsGTvhe5%1`S#H4cW3QbCil@(LPS_p{%vJK% zkmNX~QBS+NfFU5!)?xpU0s`3n1yT|cmZ5)&hF>QcvK@1SB_J?hbz;)k;ozb47J(p_ zf7cIkOEaE^B;`s;aAQcSsjv|e9Y`68*7Y?UK+bgI>>WHrJppU;(8~#|T2mi>UcLX- z;k)kDNAjrT@#{gpPj8uqzz6E7rXJ-Az~^)o9wu|4hlCA5Tt9~L2*Tb1y51dUV%eX=f7Z^1m6sKg-D}^KA`E0|y zAZ_(xt=arD#d9e9^ikVPwquJh z079Ij1EJr0_~eu7lTWA@x9S}9P`D0RD`DvJPz3Qc@+b7`?89A%MFK$pUgR1&dtQBl zyt$$a{DHJ2#^^E8ga9DdS3o_kMUX}pL@gB9zNKf=)BbC#lVa>hZ)C5H>71M%GOBR3 z`NPybUE8SZ!L6peq2jm&2L{D_&fBoC)zxwl@ zGEKM@PW55GyIqzrFnI~{Gz{0Yf$wx2^fLQe$t3r&_yF=ms)EC=FQ}6d!*cq5p0rK?6z7!Hbrr%iUmsGX4j&TC0<$-8W1@BSK}SC|K(Vy-+DS2RnYK?}hDn z0%V3B8_8Rme~Mw%<{|oqOkuqZ$;eyiwgjj2+pq@;4=nO$(%KNycnW61UF{AOiwm3K zLY=S3CUL`6jU)#HO%1Uo8s? zK?ewgp*3vg(CMlrY^Lhrod_>!szGa!P~KhUcgg-z1!A4F;CD|Wt_jqoVF{(6yl8`N zK3_8G)Tf)b1$KDIgsLaNi3>(K0F$A=t(~cY{|r~K=CxB=5sEaB%ME#a8Ls>c$NW2A zDMaiD|4_4RJ^`lgG-=o!eQB^DQYx_wU4(TqDw zvU4I^7uBm;juSkcAcA+*(%!h_SWWL!fB5j3bSuvM^nM{r9Q@;)ATC-d^iXgUovOa1 zkzV#ZT`pjSxpeA8RE(7ek%?KE1drh3(T)IgnOp_-ymaS)Lq|)n?qHMH=~DZll-%7GCLDLcssT;WmxS{4xokLkK=c4yw8(@eC1c&ONLt6$*r{lC$ zdMQj=vCy8oQNBp@Q9%VI;b6T3Q{`yYd$hUy4owQdo_t!)1|g(1ci66i!fti z7iukSKia@)+dgTLFz2xGmFnW5Tf?AXMmB`XD{hkuYo!H0YOS22zUU%FYD40O#b+?l z;jln&g7aKR{tD}72i1}(VzY!Lf%~#(J^{7^`h>#^)>e0xR$nt35d~tXrS(7?n8GvB zz!qQumTsyBB680>K}Nh8!sx}$*ynsoVxkf~L!x+*REj2(=YU>VY&rKdrrNB^ir%K> zE$@GfEY3d3Af1q^dlFIdB!OH(*<)G<1de6xhnGG~$j}&}Odla_>|l)>H9zzJUO5kH>{qaUvo0OHRp3%!hCAaMMga z+MKSTp2`uV9o@T9P10`#kUueNw2uqLXupb{Ee%P%CJtjqxKE}^f~!T|+vM!EJc-E7 z@w@VH>SLbu>5^hrlstAkHKB7RkDC-x-Jsf%WI&O#kqScoaefSOqRBCO$@;_THWi+? z$waSC9!82VbD&VNK&YNupKtxvg(2YYKw` z@7o-#nDG$ShTqN+AahTT;ipL5@?#Z##rSAjT+g0M&F-D;mwqe^X{OXSEMoZApzOLh zXd2is{)gZe7OgJ%)4g7B$Q-4zV6SEiCjK3io|u115jDAVWk@2R%$|l`pyc4c>NZm8 zWd}5H9JpYcam`l)BTV(I8;boXeM_0n6ls@%?RkpO0)5r~r2U^sfh^g!(oTbM!;&igmJh-qZ)_aU(6MZyyhSnzI zK7bpA)BR`f|4G&Ay9&=KX^{(+{EpK9#n_m14FBc<$8zmfU=iSNb?NT%;G~YP@A3z_ z{sGNz=JQk-@Sf^B#xK#g$F#k*C`0Q+qnh|;| z-F4#7GalfzdjT7`B17;H^ zg)OovpI!YhB%@!Y{0>9UQgm6j(&BrAc4=(VSFy;NOSFM<{e@B0SlX_zOBRr!Zn<_58`crFa+>+Bms+Or z_>k-2zg7RupC<_VY`Z}7;EaR{ZVS&_pa#I>Um=xAHWP}Oc&cFf9&Q!7*0OeBk*8kn zvp^jq)xmtyp;mS(%fGLg10i7R>g1;cymhaRD*&YqZm7cga&YKT?&N8Vox4MS)qE0D zyaQHfTCiC+!>%VIBYL0?K^6G$cj%DR=PIMbEm&zS5Ay%9r`qh2qwuBkm1FDaX69~u z|K5|&mikc;$)&b9(FUKAWIFQkEbFiX3r-l@-UX=uLt#}vAp?Mvvw(%1>?8Kl(EuD2 zI-r8~Zq_q}1a;zRi_Ry+Y6!wQDo%3kR?VibEb=C-* z3J>SE> zJjWG;j9K<%Ek@c&EYLp$tX{%r2gAB(aY5)eh0|-#Y1bKPlUFvZ;?rH)?sg$tNKzyP zM&(AJ2{^m}AyG>z{NU7eq(zZcqr2ANnkl5`EXLbq`$9xX$R_A10^g<6V`2pv$v-3w zJ8BXsduq_GpTn3Ba`d{j8!?KLIDz=p*kNRvH4fx8;~s7-|cYC_RK>eZ$mAWVTXa7;Ge>aE|PweClNUC@4ju!&Ahx5-s1RF55sZHb73-l&}T+Oy1pv!KNpB%eV$ zh@oI(v2TrYxz_dgWR{tTrswP)VUQK`bX%=^iO{ga!2{|BudF~xa0GCk_nW}UtZIxANCleK|@ z-LXLSmS0rwlTFVPanSWN(r#M2CR2$XfWaL*QfDuI=E*f_f|f?Drl}>wXBJ=r1rWId zml>DvqIq7eZVIgL{1VV0P2Fo?*+uN=ahCfv%7_vaHD6G);5;dU18igY^Gqj5=y8)} z1h_r5-3r5dt1IZ@r1hdOG%vlg-n872o7Bl~EH)g0A_SGFyTX8_{Q5@KYUJ=Sr`;)u z9mP7@188vW8KUDK1dr4qQ;3c(Mh?j(kKk+tN~;Wk%_DU;6)bhoHdXL*dcN+j)qYia zR0jm|DrGea`JX9~s62a3<`E-qUszt|u6mYuN)@tij!QMx87F?We`K=RamqCMgB+^6 zcU|`*EYG4DyA_cJo_hjt-{_-?#p$-rj;6}DbQx16fmE|N%pW*^k|CDx#)ZO>=*^bQ=fAjD|v zCtz;S{|Ga0W)nag&T#Gwfbbk=XPebzFA$$T_OC>drw7th%4W7|JVi*K8z=Rcv%L z9m~pX%|dMe6eU)DKymKA{Nyq&ksiTAClYx%eM+wed4?s;n0-YLVY{)$ucV|Ck_}xl ze#CAb`!vsgr|rE)9Q4Z+>+Bc>0OWx@p3XNvfB7AlH9rS#9ENUejtx<|6K8Pf!j=eK z*)@qT_<^62i1cwg+Hd+{aKUs;Reek#X#bP;AQg2Fjmm)f0TuFW=rhn92c1XcZW-$^ zpORGV6K(Z7O?v|?&`mG12&#=GVU3UIa6KD>Q>hO_X7U))i>*V&`S{eM>|q2 zSdQ7mxu5oc)z+Eizo0P~k=+uOrp}hXPYdffa8?i6Cp%n-WAVM}=Tu&CP}Ms8P4%~b zs{^#cp23vNA8NLs0pQAo?N{6+%J(X^W7T=)3LpgtOi*3MjLY$TOBU#8IQnCWc=8)> zYv-nP^rc>NwhsWm&dqyIv8(i?sA=XK;XX$mPQTuHk?|$6XdN0L)~+MP&%9*?U8m5e z6^67A-7q@lUFhg@E1uy`-+!roPr|-gg5H7a7PHL`%E0$7>+eLAff&WW?w${1(e+Ko zOxQM|ONMR$1eKt_QAa6=J-H)>giWB3^D{hUqvB1bz5f{aFrStnl4&Z?MJdPG-d6A| zYWjX8@neVnFV%jta7(IJx;ISfh~Y^e-j)fpdRLmUbXZrN6fdp3FKLzMs!g~0+`SRY zffwer2IMvKM`l<)0F+{}Lv7(sc$_ZKnt?mmLXv2EYrzLN{1WBT5G8yTNP}$<8!VnE z9V-hhG@u#}rvh6(P}yGYQR^jpM5-S*&G!kR*pbiEyT%zJ}PqsUJq-F9QZcnP+t& zk^Xy3O-_2re%T{m$|ulF4UM~tZI6jz-);;XF=zX$MM@G|=9AX9TURdy_cOT?`nj@g zp`~bv+kx_54nu}S9{SekjkUpj9}4^HHmE`X@`fHP+;>MJkYJ?k0Pv2?PEaG%TEk4T z4V`l|#AhjO6S`Rp=g)N(=xd>r_0HnB6h7)BtB%kvS~ccj4M<&x4vksy^rS@gZO1jy zS&9Y{zT|CXQ@0o{v3?+RS`Sc&4osGP$QBKir>);6%ILaH@MNfYp<_Bf3tbsct^U9J z;5e#OqinH7yNv)mFY(4?Jkb=2W(7f-zC&0i%`?qg>g<|)OEw{;Yy)783Xb6aX{rqT zL}t)8nR+;eJ>=Xp5GfTz31N4PX`KDkI-j;jp8Hd8Kk0Nm2|Nq895zY4g$4lr#Mz*b z)4%!nhe}^i+-?GCWbr4wZ#0E;^g(^%twAG7VPRI4l)S7g zcXbsVbPT;OXGd%k1hN+)s;FSY3jj+dHGV(-+{yVZMQ&Dr1tni+J7ze5qpkM(f-bHw zKG4hR2i4OBlm&u$mn6k*VXkm{Tf$-*_zr3{4gMsiITR9sDV3PdhFZn8Ha^SsFh`|O zKp;PS--0q5($p1>(S5ZRlY7x-UhL~f81dnXbLy7T&2>MzBcN9KJgciah87~NS5OMt zmWBVZo#-*gfyIk=yHH$3ceWk^hx%aM<$pa|ohe;zR9YHci^vT1PEMb+=m8!8VnCh0 z0g48x)Hz#R*CKDdMm|b=+>lBm#)S!^)ympTmdQ4KZlTu0v7W*MJHrKH>r|dyi9@T6=dMMiuJ4fPd{aeZMhz-+%5lw z^;w2%M}lh41#&sdp|O-jvdVU=h5=C7tai~Hx*OFcPRIi+N?&HFc-o`HZ}+cVF)U83 zdiw;HIFZ^Ky114I*h`1p$c@fOB8srv{rfj+foTW{wn{hHwPu)i`XLN5fB623WSy7t z8n^`7eMC3rs^|SKDEIeXgC-~RDkK+zZ7WJMj`Rc>W^2qc&3<(=3O~e&iwGex1#3}% zIj?%_OUJAu=lU(NO%^qPhg5Y6a4dqril$2W?dug=G1}s+Fy?HashsY;{F^- z?TLyhdZ@G@z{;u<`A$;A8iS<~5X=9M|H78gV_qc3fC+0Bc!ON(JUvk_S<1HM`Llf* z0;hJhDlz<=;f5JZiVvRw;D9-Z6=NlPkr%7L)TChRFWq^vZfP>`snRDr~ZV>MTi1y+*NNJ1)^d zTLY)H7JCLyZI>R&k>8~| zA-YLWqj_leZGbJi0%SgWM%{-K0*YWaG7Lf6_th%IsN#tU!8-BDKF;gmkk`n!JcBzY zg=pM^@TNz}v88(IIwK=VSG%h@Uff|A)p;MXp*$`d}ht~5?RXBuJYTu%xx*CEFGRKZijs>_O! zNn!%XoFxY>`IBK-W=B>PjU?K=?SZXf_TijASY}8-`=txk^4L;l&0rCA0!?_K@Iu2yrfKEH+bgJHm$o;(rykvD6KEvNt?|*Qh0h#)F z^P&ll?;YHcuJ#7k)lQe^p`~N6W^*?gbJ-=ek<_N+;RbqK`pv#T94B5w54~Ymszn@? z717c?3MJdw4;?+D1_06|U9L=cy>>+p%z+})yfBm?0%%BWpS^Ey@WJR z*%O<9+QbpwqW!S2!aIoVAJ&h$ifr%pRv74387wmw25{PNi|K- zr-U{3c~9XkH%pnl$z|(yfxfJN-D{xp;fs$y1o-u6o3pVzbq;ju*tAJd)K3)7l4*bAFhq)dGfTSnKMzpj7cDqxQL>h|%${_-mAK+i<0AC5ir{g2W z8SiXu%8r247jNg1iBbE~?+S^q(}2$@oeq?`F1}m0+_KS{kadj}yuBZQxHoYp0p+N| zU4ddvrQL=7#Lxv5lM@RXYn`RYq7Kl8N(^-}ZD3hnNK>lK=P6+WU6u&?{NumaiR!Sn z^8i1(Y?cQcMT`?^Rp!Ek5oh|VN6XFfsNMfjg>Oq=M2Mz(+OXIU=`1Ib%C_Z0GESJ$ z|I8l6YOCQ~>xZVd+{v-hd~Hm<|FHeZ;<^bQt$`q>S%PqGkkbR?IYQ9qA3y*2&*}fG z@*5&XAu47CQtk}(f{5{~iblI^*ln{y@Kp$esd^`khgrj6*QT@O#wVFp#wE+&5$Wnj z1x|rhqYR8|o5K>@+-}niB}53mCm%KetPv)L&?>fT`hy42u5zq?$T$H{b~Q{uHAXu(VF(FW`{{2fJ;6b*PUF)yeoh6@n6*Y{ue8a zLAwnX##c20fg^;{6j_&G@d1;H9)O>{9biA6R{xbNY1N^6iqv@Rrxcp#nGy=4EI(|S zuRIn*MHS6AKkNz7dvbw($?{u1AZ>^4OB}`ECRqWIc_guLA&uB=@OA0J4jUM^6S?hQ zv9k;=&Sozqr!AoH6XUONM@|wK_28KEe6z&iyA?6sn4cD&1D|yvR~CP zTUVrlFvY5H8>v2;m1s#EC_yK(CgWBE^3bRNnG5kn- zy|rbz+zcUPmL?sGa1pxL7vY$QJ9d zdjAuToQuCN0L0fkE6T3v!5+HB@MD5q#W)w{;3ohQRj5$?*T3l~vL++~FerzNKHTcS ziJ{kSInD;x1gOLKU<3jGrY?d7thJ#SS$a9!M$L>Hw5|yN`siZMm$PX7oyP}^Pfc?&<$5jHZ5>-lu=oWm<7mL}l*_Nne4q7=}%@B#X)djB;zB!y=u9fZPW zGCL0YEm@N=K_TvYi_-$I0Vu|e%GK9!M4TTG?w{J+<%XLJVD9WJQ(!h{A^&Ev)qjk< z1FQgC%&LQ6+BX8aV$^jjO2t;3#?;oIL))b&Hy|b5E88DqI-Y$bv_{@lw;F1i?h5NX zD_g}dYt#su;?-UsE#J_{qV7VAmo*ojW>8NfJSxFKk^LEa&N z#vM`Nh|1~^4>=~&PjeCx-J%!x5XhRcat=gNYgT|Nu7Rk~PptJli5?MdBJvEF%T1TO zY6^0OudF#cVX`UXtk6s;4$|uWtL-YMIW!HWdGao6{K5q zn6}&)s3k6mH?oyL35LwJ>_b5p9OKNMcxUDBJK4#Gkbt-b6b-S+R2qve8InOAt8*Vs zSM(eP@_F}Q%G#bq0ST^{TRON7-FganmSzsNPR8iiq8(71s`KW2#zvUL&Em+rco-D1 zc91vd(v}wuF&eT$R1Xmwpdp1Q5b^}{dh7;+Coc60GbMIY>j43w?noy~Duu?@fm+sq^Dk*KEh|<)X4$dJ$OtV1KEaN z*XKGt+86b#tzy}{bMfM-2}0c-={Kp9{`f%$n*rp@!fl)rKI$zj8g57*U6*XobZJ^V zVMx5GPC_yxwSk8r_uZ~gW@3BZ9s*}$nlivw=N_47>V~3s)3B@UC=?i-3MeKmY5?z^ z^KzxREN-kFH@J~XNI8e_%wj)#d0qp}a|uMly4k7p#KedVr7NgXfcvI%+3kW-t zij}6?>k|XuVLnHHu-+}t35TEtLl(>I8yymWa@0GS0vmU?oo_=@DxpDZ$Y78n`H~EG zn>j@N8j`}&DMNqSbW5m1PePJj03tQ)V};KSHmVRFL}iGVe64!v;*a$Tifj~;$02fU zBw3WGsy5bA^PH5x#=`qk& zS^8Vj*GPA8p`&IPU5&Zx0&u)oAYDt^t2)DHMIX49pE?3rK=4Zg8MTz5{u^ExglXH= zhD*RIB$pSiduarn&YyIeFT&j~HHjtkYr}{&bV(pVi-tLDT5Y?Ye6hx2Ox8i(AqE3C zdbMkCxl-VBk(Ot56Hj?91#R1ys{n&E)Q3#1X(h~Y>O&86A>NRUiRwD2;Pdgri+yI! zS#QA|Kk4cd`+O884$zJCuJh9eJ`12cx|@CY1p%<2C$!0(eW&_PYSBF|@WWc`ki2^4(i3^&lL(jaVWHqzxS`w>fGE&`3>ulcK)<=y zR`8UaXjQ!k>^JPV@BnZ<)|CU8JtsO6|!r5oM2s(u{!ZdZ-Q)_o(h#n+qNhM!BwYs5R zgd?`gK9}cQ9N7|IdXF!#GcFjncIbp^bx)pI;GvVum`C{3Tvsf+c8O#?5Qk0_mdX## zI|W`L?j^)A;OXf{Gf;hjT%l~??!KXkHV~>G$Q;mv^HZ;<{!Zw;*wbwxVn$#RhuoG* zV{yAE`X19BJOB3Jfud}NHx>S9KnSUu;O(*N%UK0D?tS%&%%UwiM8~jFTcT|T%6Lt| z5t8=p!$1i0%Gfd<{<`|>zy51|UMp$n#_`Dg1qUzqVM;ei?`Pc5zwLzH!_ADt7w?3I z^AjT!27JkRadHTyngawXaI*H8YUCo`lwP@+Q@ZK>kNwxIt%AnS4oz|k*$h;5OXcG z1lUytiyQ4wkFz^SXFw&fCfy#hPc{od_;C5u5eE9}iOt z&n(!Z5QSvY?AA?-YSfC3j>M?BI_1{-1T9C~z(-9&fu!xXJvFy*FeiDf=(QvJa)}WO zWmBqO5VC3rbhQ{W0R`gFI_TMH#U+JC+iVp6RdleW@00IeR3HBp61QT2?@O+d=mM;| z^-U*i!gvwop{lk%mCGKYhULtLA{Q7dW=V|d=qFzotMyxg6{J$yN1sC z>0Y-1mr7o8I*_4^P9=fTrt9}o2m;~@F6H(IDKNF85@4F@)-)82sNPWYz7!!D5P2Fzuz=xLKu#2@Q#<~L(8P#|q^jpvb|0GVFe%wpsd0RiKLWM+h&rcPX z!%Qc&;H&S(W_47IJ-1K8x~+aCfwYxNk>!ho-R;O3CwlH!WOL#$=!UnFce{lN;yc+E zPMJ`_iYU_YGfft*lU28%vo%3p&xh-S0k>Vymeb!MhWd8XF>}RyE1*t2Lk)qQcFBMI z?BlP4^pn2{C6{0N!N%U*v5+X;m#h>^uV5ZtyQxj&Azmzds`Ctlx0%_ z_GYZuE}gIO873G<(1;P4Qy7>R%wYpGQ0j*J67yAfP}pG=BnB!oNZ6P16)}z5k}9@Rn8&k%B!g^|@k~?zSb4LKblZ98 zgl^mFq8#xVZC606eU1X#&o%CAnA}P!D+E)UD_Bt5Lxax*$>|d3uU4(zt@vK8h^If| zfqIb3`GN|rjX}^5O}(o!T9Bs~%mTBLroD7-QcX3FZdznR5#x}7S7&JnbkzkfxLqu@ z+mg_8x+Gc!gaZ;-c(*6!9sbmhA*{&eZuK1Pt{H1li!^LN!Z|AMMG-QiC?O3;DPei?IyDBFMQ_jj@) z;{rv6!?^ig0HS@!77zhX1j5P#XXJ#Il%P`|9opox(L_$u$P5@Km8m@62d zYW8Uf=&P4rNtz(y>CEvi`q;iqakGe$nRbdgSmGL1DX{7El=eH$bxXyms1Rtf_#zIAZ77c6(Y0#OE~$MQ$p2eN%i;Xq2_VUQjg35W z*wr>mKNk99Cx;qKKd%z*r|Fl*A{WR)5^p%M2O1NGt(=EH0N152XK}V<+2$5*-oQ~! z{ozEj-YfY3YM1$p$`2eNPc!?^a5(bCZv%uAGMmjs)pdwK+_e0;rB3lS(EfZ z@Lw|t=Kxl3I=brYl5r1ilEop^#XAniwPRbd3GfqidDC05WNr2JgfsG-Eg-H~FtLfC zxkT{^K(Brexv1IhS)JX-Af~NlDt*8cxcjr-pwH}C%0){^f)I37p7Rd8pwJnm z#PJ4E?dm1*u%y2oUfpO_23`oe!nb-BI~m~9mayc%1fah9__?RVVDYVb`qvH!fH2j9 zt>-c8?x8e#<4uF6MSsgd%EikAoNq;9p9PFjph*Y>J_9lA>0!xWIf<+(B&+t4djVW4 zpYHTC@7E;W;XsE8vYzMq=y3&#B|YH((7lKd=QYElx?^QFR}s)=jZ3yV>aN=@bSeIS z9T`$k$`*y}l*g|^;zWnOjGib167Wf|$77%jT;2e8zzxwPOGG>}EpV%!c$Dj-KfSTE z`uJ6P^-n8xoT2;=j#q!{^Zq(($^fb`T8WV4Lu*TfN~#B>8ujB(Dt(cJX#Md}aGD&R zAkIz_Dw;2_zWb&rK{Cy=TqkIAXlbEEG||Kksd6NHS#9oU4=b@xJ@4Tmkv;|Z4#^$L zn>f0xHSCkEI6oe3A&|~nK>5FdQ=6?^SmQm_h9{v(C=A*Iqzl{6Fq8EFqH+yI(MVt6 zqrJ<$11tC`EEAM?k{*#K4oOfl(DKoIrsUSWJDg~1N>isU9F8E;IuxV9B_^1w)+Xy2 zyN^yh^TB>&`5OJySN+fu=M9wLR+qS;;0#MHHO3xr9BmS@l`;im2|@Tui~lQM^bcAm zqBGwO^}8z1Gr}}w;%M$FaL6L6#|yJpVG-XVcO9Fk#{8NLN$h9@EypW7ugedmNzSCa zALV}a@z?1l6DI_@FzHNUuk9Iciz8LUsX7B9U5-0j8m^FVw!6}Xz7r|3k{Qn(pnFd7 zKuRcFE+8_x2A!o3o)RDjv=Q?D+si!FDLp;E8SM2u2Ive02?AR&kl%=Yg7EohStNP5Zzzf4_SFo20k3LRuw#7$DulnaH!y+H$U4b|0D*6ON3tP6-!zOq|5sa#Sr#MXYKH9>#ClxfA-A&~fx!LnofHE5+))lBFN-+&PEz9q_XF&XG8 z7_1a556Z@4=_3yzaJLhY76X@@4=rlCa%SYo4U$dfYqM4(I^(nUFcYLMq9CThj%jKs zO&PKk+g|4LR3A+n5O_Z;cW&&XUNVH=LMix`Z=pdZZs-z!G}exv(QUFUKZJywY8{K`qAmbqZVtz)&4!U~I_?j&~RD*wT?uYJ81a zkptAJ&QKn7uB`k?uE@N}(-Zl0$9Pw2f1ndjl#}H5(1ktae}=Q8_uLQrY5`&tj$lU@ z2?F;G$zlt6UvFfa8FhW-@7XpG{LZu>W?>2+NmwvyDFQ7r*FWde8o*d=^WY4{5=Fyv z?g=(QR!OFwfYUfC_Lo|XFZuzQNH}&L;DH|PVee61tNH+%(FAIz^7+CFBILWnZLe~4 zX9I|veAMM%e@)8T;J%-+2lA_DBb-rBwftitM(f%9vBl~)WDT4BR%ab7Y+ zc&0kBuXdn$)(aW}({%W@GXamt)6h7GL7db*oJ1;aGJ}z=S#kgl5yi2wg zuIFRQU%bU?(Lcs=DYgGXx&9Zi!Fr`7D7`|Usy@|HJE1xZ-MVb`taxKJE4n94paage z2sEuQ_oYas80Od^uofL1NOi-XN`x5rCuN0z9S%vg{Cs-(t;OQsT{7sw$C_RsBS>#E z-HF2wGxRIx8wD%-H_fI(ha8PS%k<#=1}ui2(Xn8a9}99Vw*08M^+eAl`xIwi%#UOv zdM58BgOM9LdaYMheQ(wHe^veQf5#}ORx=J02Nj-~K^xQJQAsJdTe?>7K3`PYyLW|> z99zh8#Y0$_6}}qH`)Qb<=(?o&(+VT^=89P`R8aitL7utbDDwW+FwgnJIf{19;xYhV z$=EG19!YR`_hYdz~D& z!`)3zDdz#MxS1{jSFTGBh*iWk$&hA=akQI*>jR7=O_V$=tFKXkw&(FQv&-Qa7}Cvw z2CLUKM5KIQ{y=|L-~0>xnQoxncE7RE69C{&ZRB>gUrIj07cp1P z$W7}|=5bx~m<1yDwhCfDyzDH1wUPm`(K%=9!cyTwZ5JZfU{d~+O5+QpD?ZTvta|?i z7TXjVnq?>8DRseg-X4``%~3`lL&CqIH_|ilXQJ|YVhT>>!^w9#2noadj3hNQU}yzo zH&5Ms7PQ40%L=`g_qf^{CV%rnD?!h)>SgPlh(^xbhM~zd@tge2!7$c~W{MHv1N^pn z|7#t{h;eyGo%UGk@{dYSw9dgM73gYxra}_%=6qJQU56G1QH0(S3kUkC4!O#L31R2f ztHKUPYo+ShmIyMq6o0ir;8);2wU?j=xxO7zqccDzr4D^Nzp5-G_{6Vt?_g0Wsf>Sk z|3l@I=+1n)&xgyUx?C*Sw{#{~AQ&`aLF;Mlv9H~5)K;?N!%Exty?PC2BdIM8UG}>; zM}r&aE|z*hEHntK!u%NBiUuaWLy3pbTH)NSmMy3gtmY=ng@ITS$8 z!gkhIO*@~zsrcLn&m2=qUmzRw>9WY}L+aiKO0;>sha8_uq5%AD-3Yz3RfbXa;a+tt zSfNS9#Et zHQ)zoo4vjuRKA1*kK8M_sH~VBXw57$OfxjySf8tKkSB!4-zdTW+H6arAkbV}s>fCw zW9YckP^@c7sdq{cW}Z{;I^Rp~TNR=LV-HovV-Hf-426lgn6l2){8MiH`Nw~)-hbcc zDMLRN_&Zx|a4O3P3S?QKIDVXW^dCeOBS$h7KJ>%6Xm~pG$F9Tzp4k6PHBIk*#8D+| z4|2jH_4nI1B(B$u?j*6SlPV#+0b06TPx-J!IR{2qhrVE^VjnFnmskPs10Q;7U|93b|`IA zNHAli02v}tRxs-fMZQ|?9@ISopdb-zT_C=X3$=Z7OYN%!Uyv(3F*f%B z0Dl0_KY?-b8L;Ehzz2~@#?W{7jG~1Z(yqjzTVrR~0;&to!5~CfVG9v(aC6aRzxUl% z**Q;&RHLdE#;Q86scErD&FFVhGx{Bya{vLv`s{O76NCSmod=;)tZKJox7EW3;9CRZ zF|g?PeZkT>#nD-Fi2>h=wp_4K2)58K_fUVC?mc)Zd9^}1Opo$qZwnUxgD2*iQSRUf z2$o99Pkt3v{vqGwsBPa45IYR*z3K@2&Zn<;lz4ek0orYU=qs16ygbk7-ze)%LuE-B zTT%cZ*ed>@MWS0xIk?bH=_gpkOCxz*;Vg^l?Rdm2Cc#-BV4DgHS8X1C`ysM;mRWl? zbdYo!|44Uyg3`GcBQxCBvu6WmR7i-U^H8cpjOo#B0T#H4*F zptj)zs7A9Npi$b)XM5q-cyJbb0FI4O&Lyw$?ww^Mxp4}gA12V$ct4G~S*Y^?#Bd4RMi-w%i5Gh9p$(%@V z8)jQBQIvkC`rowFs@u-V%wC{RVk)a4`=@poh~0*4JcMxJT5|wy@#j_?cVW8tLZ6<`)V^|8Jveo?iOyU1sct5Z) zG&k3Ib*wJ4t0`o*ezM;}Z*D1jk&3B8C~fSH0j>&x+4;d7RAcZ)sJI9H(~m*aB+lb?R3E)yDAWcF`=@A5|9LI6<~1>*|!kdtXZa zg!o=RMlhJy*y0(~7$$)Ioh382?z0M*$Z3GcEw*;rfpQ20`!!!iKQQ3MLNxGlLfBZ~;R^R*!yFlQ8 zJG@CF5!KgyJJ1v{n|c`?n@SQ+=G`3Pv5~CHcCY+C8C&vUM%Om)Xylu zo0*wZ?m!-`77=qNg|wiSYBKtG z%%Lwk!d7%cTU1uyU7_#5nY>p+5ifSt|ut9kb*1Fxicf zdaS+Qj3!ZQ*>#AdD2cGUe0pdZ_DcZp)Z{|WRwml z2psfEE>yxBA;zA-wl5w5u)V(oBfBa*i%0D-8anuzg!0hrX~L}M*$l~SOpZ7=I+%ag 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

Rp5@H-63rPkl?5M!`%e$q5M^(XmdeOwiW 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%0D+{l&4Elf+RUJ>DPlT<0vI!uxp-LitG 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

nj;X-C@gua07)qPBmS6kxKbsrp_1VbHQ%kr&^ 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�J#6HekB- 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%0DQu49$GwTP{0?>4l_B{}_v8!N|W%zC&9> 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

brXZX@VYhc!=SFxwhp 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 z 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

B0o?DtF!VT!YjrDC^S2?RV?P>**rUM?tSk)et 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 literal 0 HcmV?d00001 diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/class_names-ori.py b/Seg_All_In_One_MMSeg/mmseg/utils/class_names-ori.py new file mode 100644 index 0000000..644e955 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/class_names-ori.py @@ -0,0 +1,548 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.utils import is_str + + +def cityscapes_classes(): + """Cityscapes class names for external use.""" + return [ + 'road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', 'sky', + 'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle' + ] + + +def ade_classes(): + """ADE20K class names for external use.""" + return [ + 'wall', 'building', 'sky', 'floor', 'tree', 'ceiling', 'road', 'bed ', + 'windowpane', 'grass', 'cabinet', 'sidewalk', 'person', 'earth', + 'door', 'table', 'mountain', 'plant', 'curtain', 'chair', 'car', + 'water', 'painting', 'sofa', 'shelf', 'house', 'sea', 'mirror', 'rug', + 'field', 'armchair', 'seat', 'fence', 'desk', 'rock', 'wardrobe', + 'lamp', 'bathtub', 'railing', 'cushion', 'base', 'box', 'column', + 'signboard', 'chest of drawers', 'counter', 'sand', 'sink', + 'skyscraper', 'fireplace', 'refrigerator', 'grandstand', 'path', + 'stairs', 'runway', 'case', 'pool table', 'pillow', 'screen door', + 'stairway', 'river', 'bridge', 'bookcase', 'blind', 'coffee table', + 'toilet', 'flower', 'book', 'hill', 'bench', 'countertop', 'stove', + 'palm', 'kitchen island', 'computer', 'swivel chair', 'boat', 'bar', + 'arcade machine', 'hovel', 'bus', 'towel', 'light', 'truck', 'tower', + 'chandelier', 'awning', 'streetlight', 'booth', 'television receiver', + 'airplane', 'dirt track', 'apparel', 'pole', 'land', 'bannister', + 'escalator', 'ottoman', 'bottle', 'buffet', 'poster', 'stage', 'van', + 'ship', 'fountain', 'conveyer belt', 'canopy', 'washer', 'plaything', + 'swimming pool', 'stool', 'barrel', 'basket', 'waterfall', 'tent', + 'bag', 'minibike', 'cradle', 'oven', 'ball', 'food', 'step', 'tank', + 'trade name', 'microwave', 'pot', 'animal', 'bicycle', 'lake', + 'dishwasher', 'screen', 'blanket', 'sculpture', 'hood', 'sconce', + 'vase', 'traffic light', 'tray', 'ashcan', 'fan', 'pier', 'crt screen', + 'plate', 'monitor', 'bulletin board', 'shower', 'radiator', 'glass', + 'clock', 'flag' + ] + + +def voc_classes(): + """Pascal VOC class names for external use.""" + return [ + 'background', 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', + 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor' + ] + + +def pcontext_classes(): + """Pascal Context class names for external use.""" + return [ + 'aeroplane', 'bag', 'bed', 'bedclothes', 'bench', 'bicycle', 'bird', + 'boat', 'book', 'bottle', 'building', 'bus', 'cabinet', 'car', 'cat', + 'ceiling', 'chair', 'cloth', 'computer', 'cow', 'cup', 'curtain', + 'dog', 'door', 'fence', 'floor', 'flower', 'food', 'grass', 'ground', + 'horse', 'keyboard', 'light', 'motorbike', 'mountain', 'mouse', + 'person', 'plate', 'platform', 'pottedplant', 'road', 'rock', 'sheep', + 'shelves', 'sidewalk', 'sign', 'sky', 'snow', 'sofa', 'table', 'track', + 'train', 'tree', 'truck', 'tvmonitor', 'wall', 'water', 'window', + 'wood' + ] + + +def cocostuff_classes(): + """CocoStuff class names for external use.""" + return [ + 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', + 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', + 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', + 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', + 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', + 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush', 'banner', + 'blanket', 'branch', 'bridge', 'building-other', 'bush', 'cabinet', + 'cage', 'cardboard', 'carpet', 'ceiling-other', 'ceiling-tile', + 'cloth', 'clothes', 'clouds', 'counter', 'cupboard', 'curtain', + 'desk-stuff', 'dirt', 'door-stuff', 'fence', 'floor-marble', + 'floor-other', 'floor-stone', 'floor-tile', 'floor-wood', 'flower', + 'fog', 'food-other', 'fruit', 'furniture-other', 'grass', 'gravel', + 'ground-other', 'hill', 'house', 'leaves', 'light', 'mat', 'metal', + 'mirror-stuff', 'moss', 'mountain', 'mud', 'napkin', 'net', 'paper', + 'pavement', 'pillow', 'plant-other', 'plastic', 'platform', + 'playingfield', 'railing', 'railroad', 'river', 'road', 'rock', 'roof', + 'rug', 'salad', 'sand', 'sea', 'shelf', 'sky-other', 'skyscraper', + 'snow', 'solid-other', 'stairs', 'stone', 'straw', 'structural-other', + 'table', 'tent', 'textile-other', 'towel', 'tree', 'vegetable', + 'wall-brick', 'wall-concrete', 'wall-other', 'wall-panel', + 'wall-stone', 'wall-tile', 'wall-wood', 'water-other', 'waterdrops', + 'window-blind', 'window-other', 'wood' + ] + + +def loveda_classes(): + """LoveDA class names for external use.""" + return [ + 'background', 'building', 'road', 'water', 'barren', 'forest', + 'agricultural' + ] + + +def potsdam_classes(): + """Potsdam class names for external use.""" + return [ + 'impervious_surface', 'building', 'low_vegetation', 'tree', 'car', + 'clutter' + ] + + +def vaihingen_classes(): + """Vaihingen class names for external use.""" + return [ + 'impervious_surface', 'building', 'low_vegetation', 'tree', 'car', + 'clutter' + ] + + +def isaid_classes(): + """iSAID class names for external use.""" + return [ + 'background', 'ship', 'store_tank', 'baseball_diamond', 'tennis_court', + 'basketball_court', 'Ground_Track_Field', 'Bridge', 'Large_Vehicle', + 'Small_Vehicle', 'Helicopter', 'Swimming_pool', 'Roundabout', + 'Soccer_ball_field', 'plane', 'Harbor' + ] + + +def stare_classes(): + """stare class names for external use.""" + return ['background', 'vessel'] + + +def mapillary_v1_classes(): + """mapillary_v1 class names for external use.""" + return [ + 'Bird', 'Ground Animal', 'Curb', 'Fence', 'Guard Rail', 'Barrier', + 'Wall', 'Bike Lane', 'Crosswalk - Plain', 'Curb Cut', 'Parking', + 'Pedestrian Area', 'Rail Track', 'Road', 'Service Lane', 'Sidewalk', + 'Bridge', 'Building', 'Tunnel', 'Person', 'Bicyclist', 'Motorcyclist', + 'Other Rider', 'Lane Marking - Crosswalk', 'Lane Marking - General', + 'Mountain', 'Sand', 'Sky', 'Snow', 'Terrain', 'Vegetation', 'Water', + 'Banner', 'Bench', 'Bike Rack', 'Billboard', 'Catch Basin', + 'CCTV Camera', 'Fire Hydrant', 'Junction Box', 'Mailbox', 'Manhole', + 'Phone Booth', 'Pothole', 'Street Light', 'Pole', 'Traffic Sign Frame', + 'Utility Pole', 'Traffic Light', 'Traffic Sign (Back)', + 'Traffic Sign (Front)', 'Trash Can', 'Bicycle', 'Boat', 'Bus', 'Car', + 'Caravan', 'Motorcycle', 'On Rails', 'Other Vehicle', 'Trailer', + 'Truck', 'Wheeled Slow', 'Car Mount', 'Ego Vehicle', 'Unlabeled' + ] + + +def mapillary_v1_palette(): + """mapillary_v1_ palette for external use.""" + return [[165, 42, 42], [0, 192, 0], [196, 196, 196], [190, 153, 153], + [180, 165, 180], [90, 120, 150], [102, 102, 156], [128, 64, 255], + [140, 140, 200], [170, 170, 170], [250, 170, 160], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], [244, 35, 232], + [150, 100, 100], [70, 70, 70], [150, 120, 90], [220, 20, 60], + [255, 0, 0], [255, 0, 100], [255, 0, 200], [200, 128, 128], + [255, 255, 255], [64, 170, 64], [230, 160, 50], [70, 130, 180], + [190, 255, 255], [152, 251, 152], [107, 142, 35], [0, 170, 30], + [255, 255, 128], [250, 0, 30], [100, 140, 180], [220, 220, 220], + [220, 128, 128], [222, 40, 40], [100, 170, 30], [40, 40, 40], + [33, 33, 33], [100, 128, 160], [142, 0, 0], [70, 100, 150], + [210, 170, 100], [153, 153, 153], [128, 128, 128], [0, 0, 80], + [250, 170, 30], [192, 192, 192], [220, 220, 0], [140, 140, 20], + [119, 11, 32], [150, 0, 255], [0, 60, 100], [0, 0, 142], + [0, 0, 90], [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 192], [32, 32, 32], [120, 10, 10], [0, 0, 0]] + + +def mapillary_v2_classes(): + """mapillary_v2 class names for external use.""" + return [ + 'Bird', 'Ground Animal', 'Ambiguous Barrier', 'Concrete Block', 'Curb', + 'Fence', 'Guard Rail', 'Barrier', 'Road Median', 'Road Side', + 'Lane Separator', 'Temporary Barrier', 'Wall', 'Bike Lane', + 'Crosswalk - Plain', 'Curb Cut', 'Driveway', 'Parking', + 'Parking Aisle', 'Pedestrian Area', 'Rail Track', 'Road', + 'Road Shoulder', 'Service Lane', 'Sidewalk', 'Traffic Island', + 'Bridge', 'Building', 'Garage', 'Tunnel', 'Person', 'Person Group', + 'Bicyclist', 'Motorcyclist', 'Other Rider', + 'Lane Marking - Dashed Line', 'Lane Marking - Straight Line', + 'Lane Marking - Zigzag Line', 'Lane Marking - Ambiguous', + 'Lane Marking - Arrow (Left)', 'Lane Marking - Arrow (Other)', + 'Lane Marking - Arrow (Right)', + 'Lane Marking - Arrow (Split Left or Straight)', + 'Lane Marking - Arrow (Split Right or Straight)', + 'Lane Marking - Arrow (Straight)', 'Lane Marking - Crosswalk', + 'Lane Marking - Give Way (Row)', 'Lane Marking - Give Way (Single)', + 'Lane Marking - Hatched (Chevron)', + 'Lane Marking - Hatched (Diagonal)', 'Lane Marking - Other', + 'Lane Marking - Stop Line', 'Lane Marking - Symbol (Bicycle)', + 'Lane Marking - Symbol (Other)', 'Lane Marking - Text', + 'Lane Marking (only) - Dashed Line', 'Lane Marking (only) - Crosswalk', + 'Lane Marking (only) - Other', 'Lane Marking (only) - Test', + 'Mountain', 'Sand', 'Sky', 'Snow', 'Terrain', 'Vegetation', 'Water', + 'Banner', 'Bench', 'Bike Rack', 'Catch Basin', 'CCTV Camera', + 'Fire Hydrant', 'Junction Box', 'Mailbox', 'Manhole', 'Parking Meter', + 'Phone Booth', 'Pothole', 'Signage - Advertisement', + 'Signage - Ambiguous', 'Signage - Back', 'Signage - Information', + 'Signage - Other', 'Signage - Store', 'Street Light', 'Pole', + 'Pole Group', 'Traffic Sign Frame', 'Utility Pole', 'Traffic Cone', + 'Traffic Light - General (Single)', 'Traffic Light - Pedestrians', + 'Traffic Light - General (Upright)', + 'Traffic Light - General (Horizontal)', 'Traffic Light - Cyclists', + 'Traffic Light - Other', 'Traffic Sign - Ambiguous', + 'Traffic Sign (Back)', 'Traffic Sign - Direction (Back)', + 'Traffic Sign - Direction (Front)', 'Traffic Sign (Front)', + 'Traffic Sign - Parking', 'Traffic Sign - Temporary (Back)', + 'Traffic Sign - Temporary (Front)', 'Trash Can', 'Bicycle', 'Boat', + 'Bus', 'Car', 'Caravan', 'Motorcycle', 'On Rails', 'Other Vehicle', + 'Trailer', 'Truck', 'Vehicle Group', 'Wheeled Slow', 'Water Valve', + 'Car Mount', 'Dynamic', 'Ego Vehicle', 'Ground', 'Static', 'Unlabeled' + ] + + +def mapillary_v2_palette(): + """mapillary_v2_ palette for external use.""" + return [[165, 42, 42], [0, 192, 0], [250, 170, 31], [250, 170, 32], + [196, 196, 196], [190, 153, 153], [180, 165, 180], [90, 120, 150], + [250, 170, 33], [250, 170, 34], [128, 128, 128], [250, 170, 35], + [102, 102, 156], [128, 64, 255], [140, 140, 200], [170, 170, 170], + [250, 170, 36], [250, 170, 160], [250, 170, 37], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], [110, 110, 110], + [244, 35, 232], [128, 196, 128], [150, 100, 100], [70, 70, 70], + [150, 150, 150], [150, 120, 90], [220, 20, 60], [220, 20, 60], + [255, 0, 0], [255, 0, 100], [255, 0, 200], [255, 255, 255], + [255, 255, 255], [250, 170, 29], [250, 170, 28], [250, 170, 26], + [250, 170, 25], [250, 170, 24], [250, 170, 22], [250, 170, 21], + [250, 170, 20], [255, 255, 255], [250, 170, 19], [250, 170, 18], + [250, 170, 12], [250, 170, 11], [255, 255, 255], [255, 255, 255], + [250, 170, 16], [250, 170, 15], [250, 170, 15], [255, 255, 255], + [255, 255, 255], [255, 255, 255], [255, 255, 255], [64, 170, 64], + [230, 160, 50], [70, 130, 180], [190, 255, 255], [152, 251, 152], + [107, 142, 35], [0, 170, 30], [255, 255, 128], [250, 0, 30], + [100, 140, 180], [220, 128, 128], [222, 40, 40], [100, 170, 30], + [40, 40, 40], [33, 33, 33], [100, 128, 160], [20, 20, 255], + [142, 0, 0], [70, 100, 150], [250, 171, 30], [250, 172, 30], + [250, 173, 30], [250, 174, 30], [250, 175, 30], [250, 176, 30], + [210, 170, 100], [153, 153, 153], [153, 153, 153], [128, 128, 128], + [0, 0, 80], [210, 60, 60], [250, 170, 30], [250, 170, 30], + [250, 170, 30], [250, 170, 30], [250, 170, 30], [250, 170, 30], + [192, 192, 192], [192, 192, 192], [192, 192, 192], [220, 220, 0], + [220, 220, 0], [0, 0, 196], [192, 192, 192], [220, 220, 0], + [140, 140, 20], [119, 11, 32], [150, 0, 255], [0, 60, 100], + [0, 0, 142], [0, 0, 90], [0, 0, 230], [0, 80, 100], [128, 64, 64], + [0, 0, 110], [0, 0, 70], [0, 0, 142], [0, 0, 192], [170, 170, 170], + [32, 32, 32], [111, 74, 0], [120, 10, 10], [81, 0, 81], + [111, 111, 0], [0, 0, 0]] + + +def cityscapes_palette(): + """Cityscapes palette for external use.""" + return [[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], [0, 60, 100], [0, 80, 100], + [0, 0, 230], [119, 11, 32]] + + +def ade_palette(): + """ADE20K palette for external use.""" + return [[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50], + [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255], + [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7], + [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82], + [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3], + [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255], + [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220], + [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224], + [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255], + [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7], + [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153], + [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255], + [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0], + [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255], + [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255], + [11, 200, 200], [255, 82, 0], [0, 255, 245], [0, 61, 255], + [0, 255, 112], [0, 255, 133], [255, 0, 0], [255, 163, 0], + [255, 102, 0], [194, 255, 0], [0, 143, 255], [51, 255, 0], + [0, 82, 255], [0, 255, 41], [0, 255, 173], [10, 0, 255], + [173, 255, 0], [0, 255, 153], [255, 92, 0], [255, 0, 255], + [255, 0, 245], [255, 0, 102], [255, 173, 0], [255, 0, 20], + [255, 184, 184], [0, 31, 255], [0, 255, 61], [0, 71, 255], + [255, 0, 204], [0, 255, 194], [0, 255, 82], [0, 10, 255], + [0, 112, 255], [51, 0, 255], [0, 194, 255], [0, 122, 255], + [0, 255, 163], [255, 153, 0], [0, 255, 10], [255, 112, 0], + [143, 255, 0], [82, 0, 255], [163, 255, 0], [255, 235, 0], + [8, 184, 170], [133, 0, 255], [0, 255, 92], [184, 0, 255], + [255, 0, 31], [0, 184, 255], [0, 214, 255], [255, 0, 112], + [92, 255, 0], [0, 224, 255], [112, 224, 255], [70, 184, 160], + [163, 0, 255], [153, 0, 255], [71, 255, 0], [255, 0, 163], + [255, 204, 0], [255, 0, 143], [0, 255, 235], [133, 255, 0], + [255, 0, 235], [245, 0, 255], [255, 0, 122], [255, 245, 0], + [10, 190, 212], [214, 255, 0], [0, 204, 255], [20, 0, 255], + [255, 255, 0], [0, 153, 255], [0, 41, 255], [0, 255, 204], + [41, 0, 255], [41, 255, 0], [173, 0, 255], [0, 245, 255], + [71, 0, 255], [122, 0, 255], [0, 255, 184], [0, 92, 255], + [184, 255, 0], [0, 133, 255], [255, 214, 0], [25, 194, 194], + [102, 255, 0], [92, 0, 255]] + + +def voc_palette(): + """Pascal VOC palette for external use.""" + return [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0], [0, 0, 128], + [128, 0, 128], [0, 128, 128], [128, 128, 128], [64, 0, 0], + [192, 0, 0], [64, 128, 0], [192, 128, 0], [64, 0, 128], + [192, 0, 128], [64, 128, 128], [192, 128, 128], [0, 64, 0], + [128, 64, 0], [0, 192, 0], [128, 192, 0], [0, 64, 128]] + + +def pcontext_palette(): + """Pascal Context palette for external use.""" + return [[180, 120, 120], [6, 230, 230], [80, 50, 50], [4, 200, 3], + [120, 120, 80], [140, 140, 140], [204, 5, 255], [230, 230, 230], + [4, 250, 7], [224, 5, 255], [235, 255, 7], [150, 5, 61], + [120, 120, 70], [8, 255, 51], [255, 6, 82], [143, 255, 140], + [204, 255, 4], [255, 51, 7], [204, 70, 3], [0, 102, 200], + [61, 230, 250], [255, 6, 51], [11, 102, 255], [255, 7, 71], + [255, 9, 224], [9, 7, 230], [220, 220, 220], [255, 9, 92], + [112, 9, 255], [8, 255, 214], [7, 255, 224], [255, 184, 6], + [10, 255, 71], [255, 41, 10], [7, 255, 255], [224, 255, 8], + [102, 8, 255], [255, 61, 6], [255, 194, 7], [255, 122, 8], + [0, 255, 20], [255, 8, 41], [255, 5, 153], [6, 51, 255], + [235, 12, 255], [160, 150, 20], [0, 163, 255], [140, 140, 140], + [250, 10, 15], [20, 255, 0], [31, 255, 0], [255, 31, 0], + [255, 224, 0], [153, 255, 0], [0, 0, 255], [255, 71, 0], + [0, 235, 255], [0, 173, 255], [31, 0, 255]] + + +def cocostuff_palette(): + """CocoStuff palette for external use.""" + return [[0, 192, 64], [0, 192, 64], [0, 64, 96], [128, 192, 192], + [0, 64, 64], [0, 192, 224], [0, 192, 192], [128, 192, 64], + [0, 192, 96], [128, 192, 64], [128, 32, 192], [0, 0, 224], + [0, 0, 64], [0, 160, 192], [128, 0, 96], [128, 0, 192], + [0, 32, 192], [128, 128, 224], [0, 0, 192], [128, 160, 192], + [128, 128, 0], [128, 0, 32], [128, 32, 0], [128, 0, 128], + [64, 128, 32], [0, 160, 0], [0, 0, 0], [192, 128, 160], [0, 32, 0], + [0, 128, 128], [64, 128, 160], [128, 160, 0], [0, 128, 0], + [192, 128, 32], [128, 96, 128], [0, 0, 128], [64, 0, 32], + [0, 224, 128], [128, 0, 0], [192, 0, 160], [0, 96, 128], + [128, 128, 128], [64, 0, 160], [128, 224, 128], [128, 128, 64], + [192, 0, 32], [128, 96, 0], [128, 0, 192], [0, 128, 32], + [64, 224, 0], [0, 0, 64], [128, 128, 160], [64, 96, 0], + [0, 128, 192], [0, 128, 160], [192, 224, 0], [0, 128, 64], + [128, 128, 32], [192, 32, 128], [0, 64, 192], [0, 0, 32], + [64, 160, 128], [128, 64, 64], [128, 0, 160], [64, 32, 128], + [128, 192, 192], [0, 0, 160], [192, 160, 128], [128, 192, 0], + [128, 0, 96], [192, 32, 0], [128, 64, 128], [64, 128, 96], + [64, 160, 0], [0, 64, 0], [192, 128, 224], [64, 32, 0], + [0, 192, 128], [64, 128, 224], [192, 160, 0], [0, 192, 0], + [192, 128, 96], [192, 96, 128], [0, 64, 128], [64, 0, 96], + [64, 224, 128], [128, 64, 0], [192, 0, 224], [64, 96, 128], + [128, 192, 128], [64, 0, 224], [192, 224, 128], [128, 192, 64], + [192, 0, 96], [192, 96, 0], [128, 64, 192], [0, 128, 96], + [0, 224, 0], [64, 64, 64], [128, 128, 224], [0, 96, 0], + [64, 192, 192], [0, 128, 224], [128, 224, 0], [64, 192, 64], + [128, 128, 96], [128, 32, 128], [64, 0, 192], [0, 64, 96], + [0, 160, 128], [192, 0, 64], [128, 64, 224], [0, 32, 128], + [192, 128, 192], [0, 64, 224], [128, 160, 128], [192, 128, 0], + [128, 64, 32], [128, 32, 64], [192, 0, 128], [64, 192, 32], + [0, 160, 64], [64, 0, 0], [192, 192, 160], [0, 32, 64], + [64, 128, 128], [64, 192, 160], [128, 160, 64], [64, 128, 0], + [192, 192, 32], [128, 96, 192], [64, 0, 128], [64, 64, 32], + [0, 224, 192], [192, 0, 0], [192, 64, 160], [0, 96, 192], + [192, 128, 128], [64, 64, 160], [128, 224, 192], [192, 128, 64], + [192, 64, 32], [128, 96, 64], [192, 0, 192], [0, 192, 32], + [64, 224, 64], [64, 0, 64], [128, 192, 160], [64, 96, 64], + [64, 128, 192], [0, 192, 160], [192, 224, 64], [64, 128, 64], + [128, 192, 32], [192, 32, 192], [64, 64, 192], [0, 64, 32], + [64, 160, 192], [192, 64, 64], [128, 64, 160], [64, 32, 192], + [192, 192, 192], [0, 64, 160], [192, 160, 192], [192, 192, 0], + [128, 64, 96], [192, 32, 64], [192, 64, 128], [64, 192, 96], + [64, 160, 64], [64, 64, 0]] + + +def loveda_palette(): + """LoveDA palette for external use.""" + return [[255, 255, 255], [255, 0, 0], [255, 255, 0], [0, 0, 255], + [159, 129, 183], [0, 255, 0], [255, 195, 128]] + + +def potsdam_palette(): + """Potsdam palette for external use.""" + return [[255, 255, 255], [0, 0, 255], [0, 255, 255], [0, 255, 0], + [255, 255, 0], [255, 0, 0]] + + +def vaihingen_palette(): + """Vaihingen palette for external use.""" + return [[255, 255, 255], [0, 0, 255], [0, 255, 255], [0, 255, 0], + [255, 255, 0], [255, 0, 0]] + + +def isaid_palette(): + """iSAID palette for external use.""" + return [[0, 0, 0], [0, 0, 63], [0, 63, 63], [0, 63, 0], [0, 63, 127], + [0, 63, 191], [0, 63, 255], [0, 127, 63], [0, 127, + 127], [0, 0, 127], + [0, 0, 191], [0, 0, 255], [0, 191, 127], [0, 127, 191], + [0, 127, 255], [0, 100, 155]] + + +def stare_palette(): + """STARE palette for external use.""" + return [[120, 120, 120], [6, 230, 230]] + + +def synapse_palette(): + """Synapse palette for external use.""" + return [[0, 0, 0], [0, 0, 255], [0, 255, 0], [255, 0, 0], [0, 255, 255], + [255, 0, 255], [255, 255, 0], [60, 255, 255], [240, 240, 240]] + + +def synapse_classes(): + """Synapse class names for external use.""" + return [ + 'background', 'aorta', 'gallbladder', 'left_kidney', 'right_kidney', + 'liver', 'pancreas', 'spleen', 'stomach' + ] + + +def lip_classes(): + """LIP class names for external use.""" + return [ + 'background', 'hat', 'hair', 'glove', 'sunglasses', 'upperclothes', + 'dress', 'coat', 'socks', 'pants', 'jumpsuits', 'scarf', 'skirt', + 'face', 'leftArm', 'rightArm', 'leftLeg', 'rightLeg', 'leftShoe', + 'rightShoe' + ] + + +def lip_palette(): + """LIP palette for external use.""" + return [ + 'Background', 'Hat', 'Hair', 'Glove', 'Sunglasses', 'UpperClothes', + 'Dress', 'Coat', 'Socks', 'Pants', 'Jumpsuits', 'Scarf', 'Skirt', + 'Face', 'Left-arm', 'Right-arm', 'Left-leg', 'Right-leg', 'Left-shoe', + 'Right-shoe' + ] + + +def bdd100k_classes(): + """BDD100K class names for external use(the class name is compatible with + Cityscapes ).""" + return [ + 'road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', 'sky', + 'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle' + ] + + +def bdd100k_palette(): + """bdd100k palette for external use(same with cityscapes)""" + return [[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], [0, 60, 100], [0, 80, 100], + [0, 0, 230], [119, 11, 32]] + + +def hsidrive_classes(): + """HSI Drive 2.0 class names for external use.""" + return [ + 'unlabelled', 'road', 'road marks', 'vegetation', 'painted metal', + 'sky', 'concrete', 'pedestrian', 'water', 'unpainted metal', 'glass' + ] + + +def hsidrive_palette(): + """HSI Drive 2.0 palette for external use.""" + return [[0, 0, 0], [77, 77, 77], [255, 255, 255], [0, 255, 0], [255, 0, 0], + [0, 0, 255], [102, 51, 0], [255, 255, 0], [0, 207, 250], + [255, 166, 0], [0, 204, 204]] + + +dataset_aliases = { + 'cityscapes': ['cityscapes'], + 'ade': ['ade', 'ade20k'], + 'voc': ['voc', 'pascal_voc', 'voc12', 'voc12aug'], + 'pcontext': ['pcontext', 'pascal_context', 'voc2010'], + 'loveda': ['loveda'], + 'potsdam': ['potsdam'], + 'vaihingen': ['vaihingen'], + 'cocostuff': [ + 'cocostuff', 'cocostuff10k', 'cocostuff164k', 'coco-stuff', + 'coco-stuff10k', 'coco-stuff164k', 'coco_stuff', 'coco_stuff10k', + 'coco_stuff164k' + ], + 'isaid': ['isaid', 'iSAID'], + 'stare': ['stare', 'STARE'], + 'lip': ['LIP', 'lip'], + 'mapillary_v1': ['mapillary_v1'], + 'mapillary_v2': ['mapillary_v2'], + 'bdd100k': ['bdd100k'], + 'hsidrive': [ + 'hsidrive', 'HSIDrive', 'HSI-Drive', 'hsidrive20', 'HSIDrive20', + 'HSI-Drive20' + ] +} + + +def get_classes(dataset): + """Get class names of a dataset.""" + alias2name = {} + for name, aliases in dataset_aliases.items(): + for alias in aliases: + alias2name[alias] = name + + if is_str(dataset): + if dataset in alias2name: + labels = eval(alias2name[dataset] + '_classes()') + else: + raise ValueError(f'Unrecognized dataset: {dataset}') + else: + raise TypeError(f'dataset must a str, but got {type(dataset)}') + return labels + + +def get_palette(dataset): + """Get class palette (RGB) of a dataset.""" + alias2name = {} + for name, aliases in dataset_aliases.items(): + for alias in aliases: + alias2name[alias] = name + + if is_str(dataset): + if dataset in alias2name: + labels = eval(alias2name[dataset] + '_palette()') + else: + raise ValueError(f'Unrecognized dataset: {dataset}') + else: + raise TypeError(f'dataset must a str, but got {type(dataset)}') + return labels diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/class_names.py b/Seg_All_In_One_MMSeg/mmseg/utils/class_names.py new file mode 100644 index 0000000..6250e4e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/class_names.py @@ -0,0 +1,614 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.utils import is_str + + +def cityscapes_classes(): + """Cityscapes class names for external use.""" + return [ + 'road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', 'sky', + 'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle' + ] + + +def ade_classes(): + """ADE20K class names for external use.""" + return [ + 'wall', 'building', 'sky', 'floor', 'tree', 'ceiling', 'road', 'bed ', + 'windowpane', 'grass', 'cabinet', 'sidewalk', 'person', 'earth', + 'door', 'table', 'mountain', 'plant', 'curtain', 'chair', 'car', + 'water', 'painting', 'sofa', 'shelf', 'house', 'sea', 'mirror', 'rug', + 'field', 'armchair', 'seat', 'fence', 'desk', 'rock', 'wardrobe', + 'lamp', 'bathtub', 'railing', 'cushion', 'base', 'box', 'column', + 'signboard', 'chest of drawers', 'counter', 'sand', 'sink', + 'skyscraper', 'fireplace', 'refrigerator', 'grandstand', 'path', + 'stairs', 'runway', 'case', 'pool table', 'pillow', 'screen door', + 'stairway', 'river', 'bridge', 'bookcase', 'blind', 'coffee table', + 'toilet', 'flower', 'book', 'hill', 'bench', 'countertop', 'stove', + 'palm', 'kitchen island', 'computer', 'swivel chair', 'boat', 'bar', + 'arcade machine', 'hovel', 'bus', 'towel', 'light', 'truck', 'tower', + 'chandelier', 'awning', 'streetlight', 'booth', 'television receiver', + 'airplane', 'dirt track', 'apparel', 'pole', 'land', 'bannister', + 'escalator', 'ottoman', 'bottle', 'buffet', 'poster', 'stage', 'van', + 'ship', 'fountain', 'conveyer belt', 'canopy', 'washer', 'plaything', + 'swimming pool', 'stool', 'barrel', 'basket', 'waterfall', 'tent', + 'bag', 'minibike', 'cradle', 'oven', 'ball', 'food', 'step', 'tank', + 'trade name', 'microwave', 'pot', 'animal', 'bicycle', 'lake', + 'dishwasher', 'screen', 'blanket', 'sculpture', 'hood', 'sconce', + 'vase', 'traffic light', 'tray', 'ashcan', 'fan', 'pier', 'crt screen', + 'plate', 'monitor', 'bulletin board', 'shower', 'radiator', 'glass', + 'clock', 'flag' + ] + + +def voc_classes(): + """Pascal VOC class names for external use.""" + return [ + 'background', 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', + 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', + 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', + 'tvmonitor' + ] + + +def pcontext_classes(): + """Pascal Context class names for external use.""" + return [ + 'aeroplane', 'bag', 'bed', 'bedclothes', 'bench', 'bicycle', 'bird', + 'boat', 'book', 'bottle', 'building', 'bus', 'cabinet', 'car', 'cat', + 'ceiling', 'chair', 'cloth', 'computer', 'cow', 'cup', 'curtain', + 'dog', 'door', 'fence', 'floor', 'flower', 'food', 'grass', 'ground', + 'horse', 'keyboard', 'light', 'motorbike', 'mountain', 'mouse', + 'person', 'plate', 'platform', 'pottedplant', 'road', 'rock', 'sheep', + 'shelves', 'sidewalk', 'sign', 'sky', 'snow', 'sofa', 'table', 'track', + 'train', 'tree', 'truck', 'tvmonitor', 'wall', 'water', 'window', + 'wood' + ] + + +def cocostuff_classes(): + """CocoStuff class names for external use.""" + return [ + 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', + 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', + 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', + 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', + 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', + 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', + 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', + 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', + 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', + 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', + 'scissors', 'teddy bear', 'hair drier', 'toothbrush', 'banner', + 'blanket', 'branch', 'bridge', 'building-other', 'bush', 'cabinet', + 'cage', 'cardboard', 'carpet', 'ceiling-other', 'ceiling-tile', + 'cloth', 'clothes', 'clouds', 'counter', 'cupboard', 'curtain', + 'desk-stuff', 'dirt', 'door-stuff', 'fence', 'floor-marble', + 'floor-other', 'floor-stone', 'floor-tile', 'floor-wood', 'flower', + 'fog', 'food-other', 'fruit', 'furniture-other', 'grass', 'gravel', + 'ground-other', 'hill', 'house', 'leaves', 'light', 'mat', 'metal', + 'mirror-stuff', 'moss', 'mountain', 'mud', 'napkin', 'net', 'paper', + 'pavement', 'pillow', 'plant-other', 'plastic', 'platform', + 'playingfield', 'railing', 'railroad', 'river', 'road', 'rock', 'roof', + 'rug', 'salad', 'sand', 'sea', 'shelf', 'sky-other', 'skyscraper', + 'snow', 'solid-other', 'stairs', 'stone', 'straw', 'structural-other', + 'table', 'tent', 'textile-other', 'towel', 'tree', 'vegetable', + 'wall-brick', 'wall-concrete', 'wall-other', 'wall-panel', + 'wall-stone', 'wall-tile', 'wall-wood', 'water-other', 'waterdrops', + 'window-blind', 'window-other', 'wood' + ] + + +def loveda_classes(): + """LoveDA class names for external use.""" + return [ + 'background', 'building', 'road', 'water', 'barren', 'forest', + 'agricultural' + ] + + +def potsdam_classes(): + """Potsdam class names for external use.""" + return [ + 'impervious_surface', 'building', 'low_vegetation', 'tree', 'car', + 'clutter' + ] + + +def vaihingen_classes(): + """Vaihingen class names for external use.""" + return [ + 'impervious_surface', 'building', 'low_vegetation', 'tree', 'car', + 'clutter' + ] + + +def isaid_classes(): + """iSAID class names for external use.""" + return [ + 'background', 'ship', 'store_tank', 'baseball_diamond', 'tennis_court', + 'basketball_court', 'Ground_Track_Field', 'Bridge', 'Large_Vehicle', + 'Small_Vehicle', 'Helicopter', 'Swimming_pool', 'Roundabout', + 'Soccer_ball_field', 'plane', 'Harbor' + ] + + +def stare_classes(): + """stare class names for external use.""" + return ['background', 'vessel'] + + +def mapillary_v1_classes(): + """mapillary_v1 class names for external use.""" + return [ + 'Bird', 'Ground Animal', 'Curb', 'Fence', 'Guard Rail', 'Barrier', + 'Wall', 'Bike Lane', 'Crosswalk - Plain', 'Curb Cut', 'Parking', + 'Pedestrian Area', 'Rail Track', 'Road', 'Service Lane', 'Sidewalk', + 'Bridge', 'Building', 'Tunnel', 'Person', 'Bicyclist', 'Motorcyclist', + 'Other Rider', 'Lane Marking - Crosswalk', 'Lane Marking - General', + 'Mountain', 'Sand', 'Sky', 'Snow', 'Terrain', 'Vegetation', 'Water', + 'Banner', 'Bench', 'Bike Rack', 'Billboard', 'Catch Basin', + 'CCTV Camera', 'Fire Hydrant', 'Junction Box', 'Mailbox', 'Manhole', + 'Phone Booth', 'Pothole', 'Street Light', 'Pole', 'Traffic Sign Frame', + 'Utility Pole', 'Traffic Light', 'Traffic Sign (Back)', + 'Traffic Sign (Front)', 'Trash Can', 'Bicycle', 'Boat', 'Bus', 'Car', + 'Caravan', 'Motorcycle', 'On Rails', 'Other Vehicle', 'Trailer', + 'Truck', 'Wheeled Slow', 'Car Mount', 'Ego Vehicle', 'Unlabeled' + ] + + +def mapillary_v1_palette(): + """mapillary_v1_ palette for external use.""" + return [[165, 42, 42], [0, 192, 0], [196, 196, 196], [190, 153, 153], + [180, 165, 180], [90, 120, 150], [102, 102, 156], [128, 64, 255], + [140, 140, 200], [170, 170, 170], [250, 170, 160], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], [244, 35, 232], + [150, 100, 100], [70, 70, 70], [150, 120, 90], [220, 20, 60], + [255, 0, 0], [255, 0, 100], [255, 0, 200], [200, 128, 128], + [255, 255, 255], [64, 170, 64], [230, 160, 50], [70, 130, 180], + [190, 255, 255], [152, 251, 152], [107, 142, 35], [0, 170, 30], + [255, 255, 128], [250, 0, 30], [100, 140, 180], [220, 220, 220], + [220, 128, 128], [222, 40, 40], [100, 170, 30], [40, 40, 40], + [33, 33, 33], [100, 128, 160], [142, 0, 0], [70, 100, 150], + [210, 170, 100], [153, 153, 153], [128, 128, 128], [0, 0, 80], + [250, 170, 30], [192, 192, 192], [220, 220, 0], [140, 140, 20], + [119, 11, 32], [150, 0, 255], [0, 60, 100], [0, 0, 142], + [0, 0, 90], [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 192], [32, 32, 32], [120, 10, 10], [0, 0, 0]] + + +def mapillary_v2_classes(): + """mapillary_v2 class names for external use.""" + return [ + 'Bird', 'Ground Animal', 'Ambiguous Barrier', 'Concrete Block', 'Curb', + 'Fence', 'Guard Rail', 'Barrier', 'Road Median', 'Road Side', + 'Lane Separator', 'Temporary Barrier', 'Wall', 'Bike Lane', + 'Crosswalk - Plain', 'Curb Cut', 'Driveway', 'Parking', + 'Parking Aisle', 'Pedestrian Area', 'Rail Track', 'Road', + 'Road Shoulder', 'Service Lane', 'Sidewalk', 'Traffic Island', + 'Bridge', 'Building', 'Garage', 'Tunnel', 'Person', 'Person Group', + 'Bicyclist', 'Motorcyclist', 'Other Rider', + 'Lane Marking - Dashed Line', 'Lane Marking - Straight Line', + 'Lane Marking - Zigzag Line', 'Lane Marking - Ambiguous', + 'Lane Marking - Arrow (Left)', 'Lane Marking - Arrow (Other)', + 'Lane Marking - Arrow (Right)', + 'Lane Marking - Arrow (Split Left or Straight)', + 'Lane Marking - Arrow (Split Right or Straight)', + 'Lane Marking - Arrow (Straight)', 'Lane Marking - Crosswalk', + 'Lane Marking - Give Way (Row)', 'Lane Marking - Give Way (Single)', + 'Lane Marking - Hatched (Chevron)', + 'Lane Marking - Hatched (Diagonal)', 'Lane Marking - Other', + 'Lane Marking - Stop Line', 'Lane Marking - Symbol (Bicycle)', + 'Lane Marking - Symbol (Other)', 'Lane Marking - Text', + 'Lane Marking (only) - Dashed Line', 'Lane Marking (only) - Crosswalk', + 'Lane Marking (only) - Other', 'Lane Marking (only) - Test', + 'Mountain', 'Sand', 'Sky', 'Snow', 'Terrain', 'Vegetation', 'Water', + 'Banner', 'Bench', 'Bike Rack', 'Catch Basin', 'CCTV Camera', + 'Fire Hydrant', 'Junction Box', 'Mailbox', 'Manhole', 'Parking Meter', + 'Phone Booth', 'Pothole', 'Signage - Advertisement', + 'Signage - Ambiguous', 'Signage - Back', 'Signage - Information', + 'Signage - Other', 'Signage - Store', 'Street Light', 'Pole', + 'Pole Group', 'Traffic Sign Frame', 'Utility Pole', 'Traffic Cone', + 'Traffic Light - General (Single)', 'Traffic Light - Pedestrians', + 'Traffic Light - General (Upright)', + 'Traffic Light - General (Horizontal)', 'Traffic Light - Cyclists', + 'Traffic Light - Other', 'Traffic Sign - Ambiguous', + 'Traffic Sign (Back)', 'Traffic Sign - Direction (Back)', + 'Traffic Sign - Direction (Front)', 'Traffic Sign (Front)', + 'Traffic Sign - Parking', 'Traffic Sign - Temporary (Back)', + 'Traffic Sign - Temporary (Front)', 'Trash Can', 'Bicycle', 'Boat', + 'Bus', 'Car', 'Caravan', 'Motorcycle', 'On Rails', 'Other Vehicle', + 'Trailer', 'Truck', 'Vehicle Group', 'Wheeled Slow', 'Water Valve', + 'Car Mount', 'Dynamic', 'Ego Vehicle', 'Ground', 'Static', 'Unlabeled' + ] + + +def mapillary_v2_palette(): + """mapillary_v2_ palette for external use.""" + return [[165, 42, 42], [0, 192, 0], [250, 170, 31], [250, 170, 32], + [196, 196, 196], [190, 153, 153], [180, 165, 180], [90, 120, 150], + [250, 170, 33], [250, 170, 34], [128, 128, 128], [250, 170, 35], + [102, 102, 156], [128, 64, 255], [140, 140, 200], [170, 170, 170], + [250, 170, 36], [250, 170, 160], [250, 170, 37], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], [110, 110, 110], + [244, 35, 232], [128, 196, 128], [150, 100, 100], [70, 70, 70], + [150, 150, 150], [150, 120, 90], [220, 20, 60], [220, 20, 60], + [255, 0, 0], [255, 0, 100], [255, 0, 200], [255, 255, 255], + [255, 255, 255], [250, 170, 29], [250, 170, 28], [250, 170, 26], + [250, 170, 25], [250, 170, 24], [250, 170, 22], [250, 170, 21], + [250, 170, 20], [255, 255, 255], [250, 170, 19], [250, 170, 18], + [250, 170, 12], [250, 170, 11], [255, 255, 255], [255, 255, 255], + [250, 170, 16], [250, 170, 15], [250, 170, 15], [255, 255, 255], + [255, 255, 255], [255, 255, 255], [255, 255, 255], [64, 170, 64], + [230, 160, 50], [70, 130, 180], [190, 255, 255], [152, 251, 152], + [107, 142, 35], [0, 170, 30], [255, 255, 128], [250, 0, 30], + [100, 140, 180], [220, 128, 128], [222, 40, 40], [100, 170, 30], + [40, 40, 40], [33, 33, 33], [100, 128, 160], [20, 20, 255], + [142, 0, 0], [70, 100, 150], [250, 171, 30], [250, 172, 30], + [250, 173, 30], [250, 174, 30], [250, 175, 30], [250, 176, 30], + [210, 170, 100], [153, 153, 153], [153, 153, 153], [128, 128, 128], + [0, 0, 80], [210, 60, 60], [250, 170, 30], [250, 170, 30], + [250, 170, 30], [250, 170, 30], [250, 170, 30], [250, 170, 30], + [192, 192, 192], [192, 192, 192], [192, 192, 192], [220, 220, 0], + [220, 220, 0], [0, 0, 196], [192, 192, 192], [220, 220, 0], + [140, 140, 20], [119, 11, 32], [150, 0, 255], [0, 60, 100], + [0, 0, 142], [0, 0, 90], [0, 0, 230], [0, 80, 100], [128, 64, 64], + [0, 0, 110], [0, 0, 70], [0, 0, 142], [0, 0, 192], [170, 170, 170], + [32, 32, 32], [111, 74, 0], [120, 10, 10], [81, 0, 81], + [111, 111, 0], [0, 0, 0]] + + +def cityscapes_palette(): + """Cityscapes palette for external use.""" + return [[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], [0, 60, 100], [0, 80, 100], + [0, 0, 230], [119, 11, 32]] + + +def ade_palette(): + """ADE20K palette for external use.""" + return [[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50], + [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255], + [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7], + [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82], + [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3], + [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255], + [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220], + [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224], + [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255], + [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7], + [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153], + [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255], + [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0], + [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255], + [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255], + [11, 200, 200], [255, 82, 0], [0, 255, 245], [0, 61, 255], + [0, 255, 112], [0, 255, 133], [255, 0, 0], [255, 163, 0], + [255, 102, 0], [194, 255, 0], [0, 143, 255], [51, 255, 0], + [0, 82, 255], [0, 255, 41], [0, 255, 173], [10, 0, 255], + [173, 255, 0], [0, 255, 153], [255, 92, 0], [255, 0, 255], + [255, 0, 245], [255, 0, 102], [255, 173, 0], [255, 0, 20], + [255, 184, 184], [0, 31, 255], [0, 255, 61], [0, 71, 255], + [255, 0, 204], [0, 255, 194], [0, 255, 82], [0, 10, 255], + [0, 112, 255], [51, 0, 255], [0, 194, 255], [0, 122, 255], + [0, 255, 163], [255, 153, 0], [0, 255, 10], [255, 112, 0], + [143, 255, 0], [82, 0, 255], [163, 255, 0], [255, 235, 0], + [8, 184, 170], [133, 0, 255], [0, 255, 92], [184, 0, 255], + [255, 0, 31], [0, 184, 255], [0, 214, 255], [255, 0, 112], + [92, 255, 0], [0, 224, 255], [112, 224, 255], [70, 184, 160], + [163, 0, 255], [153, 0, 255], [71, 255, 0], [255, 0, 163], + [255, 204, 0], [255, 0, 143], [0, 255, 235], [133, 255, 0], + [255, 0, 235], [245, 0, 255], [255, 0, 122], [255, 245, 0], + [10, 190, 212], [214, 255, 0], [0, 204, 255], [20, 0, 255], + [255, 255, 0], [0, 153, 255], [0, 41, 255], [0, 255, 204], + [41, 0, 255], [41, 255, 0], [173, 0, 255], [0, 245, 255], + [71, 0, 255], [122, 0, 255], [0, 255, 184], [0, 92, 255], + [184, 255, 0], [0, 133, 255], [255, 214, 0], [25, 194, 194], + [102, 255, 0], [92, 0, 255]] + + +def voc_palette(): + """Pascal VOC palette for external use.""" + return [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0], [0, 0, 128], + [128, 0, 128], [0, 128, 128], [128, 128, 128], [64, 0, 0], + [192, 0, 0], [64, 128, 0], [192, 128, 0], [64, 0, 128], + [192, 0, 128], [64, 128, 128], [192, 128, 128], [0, 64, 0], + [128, 64, 0], [0, 192, 0], [128, 192, 0], [0, 64, 128]] + + +def pcontext_palette(): + """Pascal Context palette for external use.""" + return [[180, 120, 120], [6, 230, 230], [80, 50, 50], [4, 200, 3], + [120, 120, 80], [140, 140, 140], [204, 5, 255], [230, 230, 230], + [4, 250, 7], [224, 5, 255], [235, 255, 7], [150, 5, 61], + [120, 120, 70], [8, 255, 51], [255, 6, 82], [143, 255, 140], + [204, 255, 4], [255, 51, 7], [204, 70, 3], [0, 102, 200], + [61, 230, 250], [255, 6, 51], [11, 102, 255], [255, 7, 71], + [255, 9, 224], [9, 7, 230], [220, 220, 220], [255, 9, 92], + [112, 9, 255], [8, 255, 214], [7, 255, 224], [255, 184, 6], + [10, 255, 71], [255, 41, 10], [7, 255, 255], [224, 255, 8], + [102, 8, 255], [255, 61, 6], [255, 194, 7], [255, 122, 8], + [0, 255, 20], [255, 8, 41], [255, 5, 153], [6, 51, 255], + [235, 12, 255], [160, 150, 20], [0, 163, 255], [140, 140, 140], + [250, 10, 15], [20, 255, 0], [31, 255, 0], [255, 31, 0], + [255, 224, 0], [153, 255, 0], [0, 0, 255], [255, 71, 0], + [0, 235, 255], [0, 173, 255], [31, 0, 255]] + + +def cocostuff_palette(): + """CocoStuff palette for external use.""" + return [[0, 192, 64], [0, 192, 64], [0, 64, 96], [128, 192, 192], + [0, 64, 64], [0, 192, 224], [0, 192, 192], [128, 192, 64], + [0, 192, 96], [128, 192, 64], [128, 32, 192], [0, 0, 224], + [0, 0, 64], [0, 160, 192], [128, 0, 96], [128, 0, 192], + [0, 32, 192], [128, 128, 224], [0, 0, 192], [128, 160, 192], + [128, 128, 0], [128, 0, 32], [128, 32, 0], [128, 0, 128], + [64, 128, 32], [0, 160, 0], [0, 0, 0], [192, 128, 160], [0, 32, 0], + [0, 128, 128], [64, 128, 160], [128, 160, 0], [0, 128, 0], + [192, 128, 32], [128, 96, 128], [0, 0, 128], [64, 0, 32], + [0, 224, 128], [128, 0, 0], [192, 0, 160], [0, 96, 128], + [128, 128, 128], [64, 0, 160], [128, 224, 128], [128, 128, 64], + [192, 0, 32], [128, 96, 0], [128, 0, 192], [0, 128, 32], + [64, 224, 0], [0, 0, 64], [128, 128, 160], [64, 96, 0], + [0, 128, 192], [0, 128, 160], [192, 224, 0], [0, 128, 64], + [128, 128, 32], [192, 32, 128], [0, 64, 192], [0, 0, 32], + [64, 160, 128], [128, 64, 64], [128, 0, 160], [64, 32, 128], + [128, 192, 192], [0, 0, 160], [192, 160, 128], [128, 192, 0], + [128, 0, 96], [192, 32, 0], [128, 64, 128], [64, 128, 96], + [64, 160, 0], [0, 64, 0], [192, 128, 224], [64, 32, 0], + [0, 192, 128], [64, 128, 224], [192, 160, 0], [0, 192, 0], + [192, 128, 96], [192, 96, 128], [0, 64, 128], [64, 0, 96], + [64, 224, 128], [128, 64, 0], [192, 0, 224], [64, 96, 128], + [128, 192, 128], [64, 0, 224], [192, 224, 128], [128, 192, 64], + [192, 0, 96], [192, 96, 0], [128, 64, 192], [0, 128, 96], + [0, 224, 0], [64, 64, 64], [128, 128, 224], [0, 96, 0], + [64, 192, 192], [0, 128, 224], [128, 224, 0], [64, 192, 64], + [128, 128, 96], [128, 32, 128], [64, 0, 192], [0, 64, 96], + [0, 160, 128], [192, 0, 64], [128, 64, 224], [0, 32, 128], + [192, 128, 192], [0, 64, 224], [128, 160, 128], [192, 128, 0], + [128, 64, 32], [128, 32, 64], [192, 0, 128], [64, 192, 32], + [0, 160, 64], [64, 0, 0], [192, 192, 160], [0, 32, 64], + [64, 128, 128], [64, 192, 160], [128, 160, 64], [64, 128, 0], + [192, 192, 32], [128, 96, 192], [64, 0, 128], [64, 64, 32], + [0, 224, 192], [192, 0, 0], [192, 64, 160], [0, 96, 192], + [192, 128, 128], [64, 64, 160], [128, 224, 192], [192, 128, 64], + [192, 64, 32], [128, 96, 64], [192, 0, 192], [0, 192, 32], + [64, 224, 64], [64, 0, 64], [128, 192, 160], [64, 96, 64], + [64, 128, 192], [0, 192, 160], [192, 224, 64], [64, 128, 64], + [128, 192, 32], [192, 32, 192], [64, 64, 192], [0, 64, 32], + [64, 160, 192], [192, 64, 64], [128, 64, 160], [64, 32, 192], + [192, 192, 192], [0, 64, 160], [192, 160, 192], [192, 192, 0], + [128, 64, 96], [192, 32, 64], [192, 64, 128], [64, 192, 96], + [64, 160, 64], [64, 64, 0]] + + +def loveda_palette(): + """LoveDA palette for external use.""" + return [[255, 255, 255], [255, 0, 0], [255, 255, 0], [0, 0, 255], + [159, 129, 183], [0, 255, 0], [255, 195, 128]] + + +def potsdam_palette(): + """Potsdam palette for external use.""" + return [[255, 255, 255], [0, 0, 255], [0, 255, 255], [0, 255, 0], + [255, 255, 0], [255, 0, 0]] + + +def vaihingen_palette(): + """Vaihingen palette for external use.""" + return [[255, 255, 255], [0, 0, 255], [0, 255, 255], [0, 255, 0], + [255, 255, 0], [255, 0, 0]] + + +def isaid_palette(): + """iSAID palette for external use.""" + return [[0, 0, 0], [0, 0, 63], [0, 63, 63], [0, 63, 0], [0, 63, 127], + [0, 63, 191], [0, 63, 255], [0, 127, 63], [0, 127, + 127], [0, 0, 127], + [0, 0, 191], [0, 0, 255], [0, 191, 127], [0, 127, 191], + [0, 127, 255], [0, 100, 155]] + + +def stare_palette(): + """STARE palette for external use.""" + return [[120, 120, 120], [6, 230, 230]] + + +def synapse_palette(): + """Synapse palette for external use.""" + return [[0, 0, 0], [0, 0, 255], [0, 255, 0], [255, 0, 0], [0, 255, 255], + [255, 0, 255], [255, 255, 0], [60, 255, 255], [240, 240, 240]] + + +def synapse_classes(): + """Synapse class names for external use.""" + return [ + 'background', 'aorta', 'gallbladder', 'left_kidney', 'right_kidney', + 'liver', 'pancreas', 'spleen', 'stomach' + ] + + +def lip_classes(): + """LIP class names for external use.""" + return [ + 'background', 'hat', 'hair', 'glove', 'sunglasses', 'upperclothes', + 'dress', 'coat', 'socks', 'pants', 'jumpsuits', 'scarf', 'skirt', + 'face', 'leftArm', 'rightArm', 'leftLeg', 'rightLeg', 'leftShoe', + 'rightShoe' + ] + + +def lip_palette(): + """LIP palette for external use.""" + return [ + 'Background', 'Hat', 'Hair', 'Glove', 'Sunglasses', 'UpperClothes', + 'Dress', 'Coat', 'Socks', 'Pants', 'Jumpsuits', 'Scarf', 'Skirt', + 'Face', 'Left-arm', 'Right-arm', 'Left-leg', 'Right-leg', 'Left-shoe', + 'Right-shoe' + ] + + +def bdd100k_classes(): + """BDD100K class names for external use(the class name is compatible with + Cityscapes ).""" + return [ + 'road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', 'sky', + 'person', 'rider', 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle' + ] + + +def bdd100k_palette(): + """bdd100k palette for external use(same with cityscapes)""" + return [[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], [0, 60, 100], [0, 80, 100], + [0, 0, 230], [119, 11, 32]] + + +def hsidrive_classes(): + """HSI Drive 2.0 class names for external use.""" + return [ + 'unlabelled', 'road', 'road marks', 'vegetation', 'painted metal', + 'sky', 'concrete', 'pedestrian', 'water', 'unpainted metal', 'glass' + ] + + +def hsidrive_palette(): + """HSI Drive 2.0 palette for external use.""" + return [[0, 0, 0], [77, 77, 77], [255, 255, 255], [0, 255, 0], [255, 0, 0], + [0, 0, 255], [102, 51, 0], [255, 255, 0], [0, 207, 250], + [255, 166, 0], [0, 204, 204]] + + +def publicdataset_cholecseg8k_classes(): # TODO + """publicdataset_cholecseg8k class names for external use.""" + return ['背景', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] + + +def publicdataset_cholecseg8k_palette(): # TODO + """publicdataset_cholecseg8k palette for external use.""" + return [[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233], [52, 184, 178], [90, 120, 41]] + + +def my_dataset_model_classes(): # TODO + """my_dataset_model class names for external use.""" + return ['背景', '肝脏', '胆囊', '分离钳', '止血海绵', '肝总管', '胆总管', '吸引器', '剪刀', '止血纱布', '生物夹', '无损伤钳', '喷洒', '胆囊管', '胆囊动脉', '电凝', '标本袋', '引流管', '纱布', '金属钛夹', '术中超声', '吻合器', '乳胶管', '推结器', '肝带', '钳夹', '超声刀', '脂肪', '双极电凝', '棉球', '血管阻断夹', '肿瘤', '针', '线', '韧带', '胆囊静脉'] + + +def my_dataset_model_palette(): # TODO + """my_dataset_model palette for external use.""" + return [[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233], [52, 184, 178], [90, 120, 41], [255, 0, 0], [177, 0, 0], [167, 24, 233], [112, 113, 150], [0, 255, 0], [255, 255, 255], [0, 255, 255], [138, 251, 213], [136, 162, 196], [197, 83, 181], [202, 202, 200], [113, 102, 140], [66, 115, 82], [240, 16, 116], [155, 132, 0], [155, 62, 0], [146, 175, 236], [255, 172, 159], [245, 161, 0], [134, 124, 118], [0, 157, 142], [181, 85, 105], [42, 8, 66]] + + +def publicdataset_autolaparo_classes(): # TODO + """publicdataset_autolaparo class names for external use.""" + return ['背景', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + + +def publicdataset_autolaparo_palette(): # TODO + """publicdataset_autolaparo palette for external use.""" + return [[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95]] + + +def publicdataset_endovis_2017_classes(): # TODO + """publicdataset_endovis_2017 class names for external use.""" + return ['背景', '1', '2', '3', '4', '5', '6', '7'] + + +def publicdataset_endovis_2017_palette(): # TODO + """publicdataset_endovis_2017 palette for external use.""" + return [[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255]] + + +def publicdataset_dresden_classes(): # TODO + """publicdataset_dresden class names for external use.""" + return ['背景', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] + + +def publicdataset_dresden_palette(): # TODO + """publicdataset_dresden palette for external use.""" + return [[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255], [29, 32, 136], [160, 15, 95], [0, 160, 233]] + + +def publicdataset_endovis_2018_classes(): # TODO + """publicdataset_endovis_2018 class names for external use.""" + return ['背景', '1', '2', '3', '4', '5', '6', '7'] + + +def publicdataset_endovis_2018_palette(): # TODO + """publicdataset_endovis_2018 palette for external use.""" + return [[0, 0, 0], [255, 91, 0], [255, 234, 0], [85, 111, 181], [181, 227, 14], [72, 0, 255], [0, 155, 33], [255, 0, 255]] + + +dataset_aliases = { + 'publicdataset_cholecseg8k': ['publicdataset_cholecseg8k'], # TODO + 'my_dataset_model': ['my_dataset_model'], # TODO + 'publicdataset_autolaparo': ['publicdataset_autolaparo'], # TODO + 'publicdataset_endovis_2017': ['publicdataset_endovis_2017'], # TODO + 'publicdataset_dresden': ['publicdataset_dresden'], # TODO + 'publicdataset_endovis_2018': ['publicdataset_endovis_2018'], # TODO + 'cityscapes': ['cityscapes'], + 'ade': ['ade', 'ade20k'], + 'voc': ['voc', 'pascal_voc', 'voc12', 'voc12aug'], + 'pcontext': ['pcontext', 'pascal_context', 'voc2010'], + 'loveda': ['loveda'], + 'potsdam': ['potsdam'], + 'vaihingen': ['vaihingen'], + 'cocostuff': [ + 'cocostuff', 'cocostuff10k', 'cocostuff164k', 'coco-stuff', + 'coco-stuff10k', 'coco-stuff164k', 'coco_stuff', 'coco_stuff10k', + 'coco_stuff164k' + ], + 'isaid': ['isaid', 'iSAID'], + 'stare': ['stare', 'STARE'], + 'lip': ['LIP', 'lip'], + 'mapillary_v1': ['mapillary_v1'], + 'mapillary_v2': ['mapillary_v2'], + 'bdd100k': ['bdd100k'], + 'hsidrive': [ + 'hsidrive', 'HSIDrive', 'HSI-Drive', 'hsidrive20', 'HSIDrive20', + 'HSI-Drive20' + ] +} + + +def get_classes(dataset): + """Get class names of a dataset.""" + alias2name = {} + for name, aliases in dataset_aliases.items(): + for alias in aliases: + alias2name[alias] = name + + if isinstance(dataset, str): + if dataset in alias2name: + labels = eval(alias2name[dataset] + '_classes()') + else: + raise ValueError(f'Unrecognized dataset: {dataset}') + else: + raise TypeError(f'dataset must a str, but got {type(dataset)}') + return labels + + +def get_palette(dataset): + """Get class palette (RGB) of a dataset.""" + alias2name = {} + for name, aliases in dataset_aliases.items(): + for alias in aliases: + alias2name[alias] = name + + if isinstance(dataset, str): + if dataset in alias2name: + labels = eval(alias2name[dataset] + '_palette()') + else: + raise ValueError(f'Unrecognized dataset: {dataset}') + else: + raise TypeError(f'dataset must a str, but got {type(dataset)}') + return labels diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/collect_env.py b/Seg_All_In_One_MMSeg/mmseg/utils/collect_env.py new file mode 100644 index 0000000..d5d6ea2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/collect_env.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine.utils import get_git_hash +from mmengine.utils.dl_utils import collect_env as collect_base_env + +import mmseg + + +def collect_env(): + """Collect the information of the running environments.""" + env_info = collect_base_env() + env_info['MMSegmentation'] = f'{mmseg.__version__}+{get_git_hash()[:7]}' + + return env_info + + +if __name__ == '__main__': + for name, val in collect_env().items(): + print(f'{name}: {val}') diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/get_templates.py b/Seg_All_In_One_MMSeg/mmseg/utils/get_templates.py new file mode 100644 index 0000000..7e9032b --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/get_templates.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +PREDEFINED_TEMPLATES = { + 'imagenet': [ + 'a bad photo of a {}.', + 'a photo of many {}.', + 'a sculpture of a {}.', + 'a photo of the hard to see {}.', + 'a low resolution photo of the {}.', + 'a rendering of a {}.', + 'graffiti of a {}.', + 'a bad photo of the {}.', + 'a cropped photo of the {}.', + 'a tattoo of a {}.', + 'the embroidered {}.', + 'a photo of a hard to see {}.', + 'a bright photo of a {}.', + 'a photo of a clean {}.', + 'a photo of a dirty {}.', + 'a dark photo of the {}.', + 'a drawing of a {}.', + 'a photo of my {}.', + 'the plastic {}.', + 'a photo of the cool {}.', + 'a close-up photo of a {}.', + 'a black and white photo of the {}.', + 'a painting of the {}.', + 'a painting of a {}.', + 'a pixelated photo of the {}.', + 'a sculpture of the {}.', + 'a bright photo of the {}.', + 'a cropped photo of a {}.', + 'a plastic {}.', + 'a photo of the dirty {}.', + 'a jpeg corrupted photo of a {}.', + 'a blurry photo of the {}.', + 'a photo of the {}.', + 'a good photo of the {}.', + 'a rendering of the {}.', + 'a {} in a video game.', + 'a photo of one {}.', + 'a doodle of a {}.', + 'a close-up photo of the {}.', + 'a photo of a {}.', + 'the origami {}.', + 'the {} in a video game.', + 'a sketch of a {}.', + 'a doodle of the {}.', + 'a origami {}.', + 'a low resolution photo of a {}.', + 'the toy {}.', + 'a rendition of the {}.', + 'a photo of the clean {}.', + 'a photo of a large {}.', + 'a rendition of a {}.', + 'a photo of a nice {}.', + 'a photo of a weird {}.', + 'a blurry photo of a {}.', + 'a cartoon {}.', + 'art of a {}.', + 'a sketch of the {}.', + 'a embroidered {}.', + 'a pixelated photo of a {}.', + 'itap of the {}.', + 'a jpeg corrupted photo of the {}.', + 'a good photo of a {}.', + 'a plushie {}.', + 'a photo of the nice {}.', + 'a photo of the small {}.', + 'a photo of the weird {}.', + 'the cartoon {}.', + 'art of the {}.', + 'a drawing of the {}.', + 'a photo of the large {}.', + 'a black and white photo of a {}.', + 'the plushie {}.', + 'a dark photo of a {}.', + 'itap of a {}.', + 'graffiti of the {}.', + 'a toy {}.', + 'itap of my {}.', + 'a photo of a cool {}.', + 'a photo of a small {}.', + 'a tattoo of the {}.', + ], + 'vild': [ + 'a photo of a {}.', + 'This is a photo of a {}', + 'There is a {} in the scene', + 'There is the {} in the scene', + 'a photo of a {} in the scene', + 'a photo of a small {}.', + 'a photo of a medium {}.', + 'a photo of a large {}.', + 'This is a photo of a small {}.', + 'This is a photo of a medium {}.', + 'This is a photo of a large {}.', + 'There is a small {} in the scene.', + 'There is a medium {} in the scene.', + 'There is a large {} in the scene.', + ], +} + + +def get_predefined_templates(template_set_name: str) -> List[str]: + if template_set_name not in PREDEFINED_TEMPLATES: + raise ValueError(f'Template set {template_set_name} not found') + return PREDEFINED_TEMPLATES[template_set_name] diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/io.py b/Seg_All_In_One_MMSeg/mmseg/utils/io.py new file mode 100644 index 0000000..7029c3c --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/io.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import gzip +import io +import pickle + +import cv2 +import numpy as np + + +def datafrombytes(content: bytes, backend: str = 'numpy') -> np.ndarray: + """Data decoding from bytes. + + Args: + content (bytes): The data bytes got from files or other streams. + backend (str): The data decoding backend type. Options are 'numpy', + 'nifti', 'cv2' and 'pickle'. Defaults to 'numpy'. + + Returns: + numpy.ndarray: Loaded data array. + """ + if backend == 'pickle': + data = pickle.loads(content) + else: + with io.BytesIO(content) as f: + if backend == 'nifti': + f = gzip.open(f) + try: + from nibabel import FileHolder, Nifti1Image + except ImportError: + print('nifti files io depends on nibabel, please run' + '`pip install nibabel` to install it') + fh = FileHolder(fileobj=f) + data = Nifti1Image.from_file_map({'header': fh, 'image': fh}) + data = Nifti1Image.from_bytes(data.to_bytes()).get_fdata() + elif backend == 'numpy': + data = np.load(f) + elif backend == 'cv2': + data = np.frombuffer(f.read(), dtype=np.uint8) + data = cv2.imdecode(data, cv2.IMREAD_UNCHANGED) + else: + raise ValueError + return data diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/mask_classification.py b/Seg_All_In_One_MMSeg/mmseg/utils/mask_classification.py new file mode 100644 index 0000000..205d525 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/mask_classification.py @@ -0,0 +1,205 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +from mmcv.ops import point_sample +from mmengine.structures import InstanceData +from torch import Tensor + +from mmseg.registry import TASK_UTILS +from mmseg.utils import ConfigType, SampleList + + +def seg_data_to_instance_data(ignore_index: int, + batch_data_samples: SampleList): + """Convert the paradigm of ground truth from semantic segmentation to + instance segmentation. + + Args: + ignore_index (int): The label index to be ignored. + batch_data_samples (List[SegDataSample]): The Data + Samples. It usually includes information such as + `gt_sem_seg`. + + Returns: + tuple[Tensor]: A tuple contains two lists. + - batch_gt_instances (List[InstanceData]): Batch of + gt_instance. It usually includes ``labels``, each is + unique ground truth label id of images, with + shape (num_gt, ) and ``masks``, each is ground truth + masks of each instances of a image, shape (num_gt, h, w). + - batch_img_metas (List[Dict]): List of image meta information. + """ + batch_gt_instances = [] + + for data_sample in batch_data_samples: + gt_sem_seg = data_sample.gt_sem_seg.data + classes = torch.unique( + gt_sem_seg, + sorted=False, + return_inverse=False, + return_counts=False) + + # remove ignored region + gt_labels = classes[classes != ignore_index] + + masks = [] + for class_id in gt_labels: + masks.append(gt_sem_seg == class_id) + + if len(masks) == 0: + gt_masks = torch.zeros( + (0, gt_sem_seg.shape[-2], + gt_sem_seg.shape[-1])).to(gt_sem_seg).long() + else: + gt_masks = torch.stack(masks).squeeze(1).long() + + instance_data = InstanceData(labels=gt_labels, masks=gt_masks) + batch_gt_instances.append(instance_data) + return batch_gt_instances + + +class MatchMasks: + """Match the predictions to category labels. + + Args: + num_points (int): the number of sampled points to compute cost. + num_queries (int): the number of prediction masks. + num_classes (int): the number of classes. + assigner (BaseAssigner): the assigner to compute matching. + """ + + def __init__(self, + num_points: int, + num_queries: int, + num_classes: int, + assigner: ConfigType = None): + assert assigner is not None, "\'assigner\' in decode_head.train_cfg" \ + 'cannot be None' + assert num_points > 0, 'num_points should be a positive integer.' + self.num_points = num_points + self.num_queries = num_queries + self.num_classes = num_classes + self.assigner = TASK_UTILS.build(assigner) + + def get_targets(self, cls_scores: List[Tensor], mask_preds: List[Tensor], + batch_gt_instances: List[InstanceData]) -> Tuple: + """Compute best mask matches for all images for a decoder layer. + + Args: + cls_scores (List[Tensor]): Mask score logits from a single + decoder layer for all images. Each with shape (num_queries, + cls_out_channels). + mask_preds (List[Tensor]): Mask logits from a single decoder + layer for all images. Each with shape (num_queries, h, w). + batch_gt_instances (List[InstanceData]): each contains + ``labels`` and ``masks``. + + Returns: + tuple: a tuple containing the following targets. + + - labels (List[Tensor]): Labels of all images.\ + Each with shape (num_queries, ). + - mask_targets (List[Tensor]): Mask targets of\ + all images. Each with shape (num_queries, h, w). + - mask_weights (List[Tensor]): Mask weights of\ + all images. Each with shape (num_queries, ). + - avg_factor (int): Average factor that is used to + average the loss. `avg_factor` is usually equal + to the number of positive priors. + """ + batch_size = cls_scores.shape[0] + results = dict({ + 'labels': [], + 'mask_targets': [], + 'mask_weights': [], + }) + for i in range(batch_size): + labels, mask_targets, mask_weights\ + = self._get_targets_single(cls_scores[i], + mask_preds[i], + batch_gt_instances[i]) + results['labels'].append(labels) + results['mask_targets'].append(mask_targets) + results['mask_weights'].append(mask_weights) + + # shape (batch_size, num_queries) + labels = torch.stack(results['labels'], dim=0) + # shape (batch_size, num_gts, h, w) + mask_targets = torch.cat(results['mask_targets'], dim=0) + # shape (batch_size, num_queries) + mask_weights = torch.stack(results['mask_weights'], dim=0) + + avg_factor = sum( + [len(gt_instances.labels) for gt_instances in batch_gt_instances]) + + res = (labels, mask_targets, mask_weights, avg_factor) + + return res + + def _get_targets_single(self, cls_score: Tensor, mask_pred: Tensor, + gt_instances: InstanceData) \ + -> Tuple[Tensor, Tensor, Tensor]: + """Compute a set of best mask matches for one image. + + Args: + cls_score (Tensor): Mask score logits from a single decoder layer + for one image. Shape (num_queries, cls_out_channels). + mask_pred (Tensor): Mask logits for a single decoder layer for one + image. Shape (num_queries, h, w). + gt_instances (:obj:`InstanceData`): It contains ``labels`` and + ``masks``. + + Returns: + tuple[Tensor]: A tuple containing the following for one image. + + - labels (Tensor): Labels of each image. \ + shape (num_queries, ). + - mask_targets (Tensor): Mask targets of each image. \ + shape (num_queries, h, w). + - mask_weights (Tensor): Mask weights of each image. \ + shape (num_queries, ). + """ + gt_labels = gt_instances.labels + gt_masks = gt_instances.masks + # when "gt_labels" is empty, classify all queries to background + if len(gt_labels) == 0: + labels = gt_labels.new_full((self.num_queries, ), + self.num_classes, + dtype=torch.long) + mask_targets = gt_labels + mask_weights = gt_labels.new_zeros((self.num_queries, )) + return labels, mask_targets, mask_weights + # sample points + num_queries = cls_score.shape[0] + num_gts = gt_labels.shape[0] + + point_coords = torch.rand((1, self.num_points, 2), + device=cls_score.device) + # shape (num_queries, num_points) + mask_points_pred = point_sample( + mask_pred.unsqueeze(1), point_coords.repeat(num_queries, 1, + 1)).squeeze(1) + # shape (num_gts, num_points) + gt_points_masks = point_sample( + gt_masks.unsqueeze(1).float(), point_coords.repeat(num_gts, 1, + 1)).squeeze(1) + + sampled_gt_instances = InstanceData( + labels=gt_labels, masks=gt_points_masks) + sampled_pred_instances = InstanceData( + scores=cls_score, masks=mask_points_pred) + # assign and sample + matched_quiery_inds, matched_label_inds = self.assigner.assign( + pred_instances=sampled_pred_instances, + gt_instances=sampled_gt_instances) + labels = gt_labels.new_full((self.num_queries, ), + self.num_classes, + dtype=torch.long) + labels[matched_quiery_inds] = gt_labels[matched_label_inds] + + mask_weights = gt_labels.new_zeros((self.num_queries, )) + mask_weights[matched_quiery_inds] = 1 + mask_targets = gt_masks[matched_label_inds] + + return labels, mask_targets, mask_weights diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/misc.py b/Seg_All_In_One_MMSeg/mmseg/utils/misc.py new file mode 100644 index 0000000..dfc469e --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/misc.py @@ -0,0 +1,128 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Optional, Union + +import numpy as np +import torch +import torch.nn.functional as F + +from .typing_utils import SampleList + + +def add_prefix(inputs, prefix): + """Add prefix for dict. + + Args: + inputs (dict): The input dict with str keys. + prefix (str): The prefix to add. + + Returns: + + dict: The dict with keys updated with ``prefix``. + """ + + outputs = dict() + for name, value in inputs.items(): + outputs[f'{prefix}.{name}'] = value + + return outputs + + +def stack_batch(inputs: List[torch.Tensor], + data_samples: Optional[SampleList] = None, + size: Optional[tuple] = None, + size_divisor: Optional[int] = None, + pad_val: Union[int, float] = 0, + seg_pad_val: Union[int, float] = 255) -> torch.Tensor: + """Stack multiple inputs to form a batch and pad the images and gt_sem_segs + to the max shape use the right bottom padding mode. + + Args: + inputs (List[Tensor]): The input multiple tensors. each is a + CHW 3D-tensor. + data_samples (list[:obj:`SegDataSample`]): The list of data samples. + It usually includes information such as `gt_sem_seg`. + size (tuple, optional): Fixed padding size. + size_divisor (int, optional): The divisor of padded size. + pad_val (int, float): The padding value. Defaults to 0 + seg_pad_val (int, float): The padding value. Defaults to 255 + + Returns: + Tensor: The 4D-tensor. + List[:obj:`SegDataSample`]: After the padding of the gt_seg_map. + """ + assert isinstance(inputs, list), \ + f'Expected input type to be list, but got {type(inputs)}' + assert len({tensor.ndim for tensor in inputs}) == 1, \ + f'Expected the dimensions of all inputs must be the same, ' \ + f'but got {[tensor.ndim for tensor in inputs]}' + assert inputs[0].ndim == 3, f'Expected tensor dimension to be 3, ' \ + f'but got {inputs[0].ndim}' + assert len({tensor.shape[0] for tensor in inputs}) == 1, \ + f'Expected the channels of all inputs must be the same, ' \ + f'but got {[tensor.shape[0] for tensor in inputs]}' + + # only one of size and size_divisor should be valid + assert (size is not None) ^ (size_divisor is not None), \ + 'only one of size and size_divisor should be valid' + + padded_inputs = [] + padded_samples = [] + inputs_sizes = [(img.shape[-2], img.shape[-1]) for img in inputs] + max_size = np.stack(inputs_sizes).max(0) + if size_divisor is not None and size_divisor > 1: + # the last two dims are H,W, both subject to divisibility requirement + max_size = (max_size + + (size_divisor - 1)) // size_divisor * size_divisor + + for i in range(len(inputs)): + tensor = inputs[i] + if size is not None: + width = max(size[-1] - tensor.shape[-1], 0) + height = max(size[-2] - tensor.shape[-2], 0) + # (padding_left, padding_right, padding_top, padding_bottom) + padding_size = (0, width, 0, height) + elif size_divisor is not None: + width = max(max_size[-1] - tensor.shape[-1], 0) + height = max(max_size[-2] - tensor.shape[-2], 0) + padding_size = (0, width, 0, height) + else: + padding_size = [0, 0, 0, 0] + + # pad img + pad_img = F.pad(tensor, padding_size, value=pad_val) + padded_inputs.append(pad_img) + # pad gt_sem_seg + if data_samples is not None: + data_sample = data_samples[i] + pad_shape = None + if 'gt_sem_seg' in data_sample: + gt_sem_seg = data_sample.gt_sem_seg.data + del data_sample.gt_sem_seg.data + data_sample.gt_sem_seg.data = F.pad( + gt_sem_seg, padding_size, value=seg_pad_val) + pad_shape = data_sample.gt_sem_seg.shape + if 'gt_edge_map' in data_sample: + gt_edge_map = data_sample.gt_edge_map.data + del data_sample.gt_edge_map.data + data_sample.gt_edge_map.data = F.pad( + gt_edge_map, padding_size, value=seg_pad_val) + pad_shape = data_sample.gt_edge_map.shape + if 'gt_depth_map' in data_sample: + gt_depth_map = data_sample.gt_depth_map.data + del data_sample.gt_depth_map.data + data_sample.gt_depth_map.data = F.pad( + gt_depth_map, padding_size, value=seg_pad_val) + pad_shape = data_sample.gt_depth_map.shape + data_sample.set_metainfo({ + 'img_shape': tensor.shape[-2:], + 'pad_shape': pad_shape, + 'padding_size': padding_size + }) + padded_samples.append(data_sample) + else: + padded_samples.append( + dict( + img_padding_size=padding_size, + pad_shape=pad_img.shape[-2:])) + + return torch.stack(padded_inputs, dim=0), padded_samples diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/set_env.py b/Seg_All_In_One_MMSeg/mmseg/utils/set_env.py new file mode 100644 index 0000000..c948950 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/set_env.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import datetime +import warnings + +from mmengine import DefaultScope + + +def register_all_modules(init_default_scope: bool = True) -> None: + """Register all modules in mmseg into the registries. + + Args: + init_default_scope (bool): Whether initialize the mmseg default scope. + When `init_default_scope=True`, the global default scope will be + set to `mmseg`, and all registries will build modules from mmseg's + registry node. To understand more about the registry, please refer + to https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/registry.md + Defaults to True. + """ # noqa + import mmseg.datasets # noqa: F401,F403 + import mmseg.engine # noqa: F401,F403 + import mmseg.evaluation # noqa: F401,F403 + import mmseg.models # noqa: F401,F403 + import mmseg.structures # noqa: F401,F403 + + if init_default_scope: + never_created = DefaultScope.get_current_instance() is None \ + or not DefaultScope.check_instance_created('mmseg') + if never_created: + DefaultScope.get_instance('mmseg', scope_name='mmseg') + return + current_scope = DefaultScope.get_current_instance() + if current_scope.scope_name != 'mmseg': + warnings.warn('The current default scope ' + f'"{current_scope.scope_name}" is not "mmseg", ' + '`register_all_modules` will force the current' + 'default scope to be "mmseg". If this is not ' + 'expected, please set `init_default_scope=False`.') + # avoid name conflict + new_instance_name = f'mmseg-{datetime.datetime.now()}' + DefaultScope.get_instance(new_instance_name, scope_name='mmseg') diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/tokenizer.py b/Seg_All_In_One_MMSeg/mmseg/utils/tokenizer.py new file mode 100644 index 0000000..d56f5fa --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/tokenizer.py @@ -0,0 +1,240 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""CLIP tokenizer. + +Copied from https://github.com/openai/CLIP. Originally MIT License, Copyright +(c) 2021 OpenAI. +""" +import gzip +import html +import os +from functools import lru_cache +from typing import List, Union + +import ftfy +import regex as re +import torch + +os.environ['TOKENIZERS_PARALLELISM'] = 'false' + + +@lru_cache() +def default_bpe(): + return os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'bpe_simple_vocab_16e6.txt.gz') + + +@lru_cache() +def bytes_to_unicode(): + """Returns list of utf-8 byte and a corresponding list of unicode strings. + + The reversible bpe codes work on unicode strings. This means you need a + large # of unicode characters in your vocab if you want to avoid UNKs. When + you're at something like a 10B token dataset you end up needing around 5K + for decent coverage. This is a significant percentage of your normal, say, + 32K bpe vocab. To avoid that, we want lookup tables between utf-8 bytes and + unicode strings. And avoids mapping to whitespace/control characters the + bpe code barfs on. + """ + bs = list(range(ord('!'), + ord('~') + 1)) + list(range( + ord('¡'), + ord('¬') + 1)) + list(range(ord('®'), + ord('ÿ') + 1)) + cs = bs[:] + n = 0 + for b in range(2**8): + if b not in bs: + bs.append(b) + cs.append(2**8 + n) + n += 1 + cs = [chr(n) for n in cs] + return dict(zip(bs, cs)) + + +def get_pairs(word): + """Return set of symbol pairs in a word. + + Word is represented as tuple of symbols (symbols being variable-length + strings). + """ + pairs = set() + prev_char = word[0] + for char in word[1:]: + pairs.add((prev_char, char)) + prev_char = char + return pairs + + +def basic_clean(text): + text = ftfy.fix_text(text) + text = html.unescape(html.unescape(text)) + return text.strip() + + +def whitespace_clean(text): + text = re.sub(r'\s+', ' ', text) + text = text.strip() + return text + + +class SimpleTokenizer: + + def __init__(self, bpe_path: str = default_bpe(), special_tokens=None): + self.byte_encoder = bytes_to_unicode() + self.byte_decoder = {v: k for k, v in self.byte_encoder.items()} + merges = gzip.open(bpe_path).read().decode('utf-8').split('\n') + merges = merges[1:49152 - 256 - 2 + 1] + merges = [tuple(merge.split()) for merge in merges] + vocab = list(bytes_to_unicode().values()) + vocab = vocab + [v + '' for v in vocab] + for merge in merges: + vocab.append(''.join(merge)) + if not special_tokens: + special_tokens = ['', ''] + else: + special_tokens = ['', '' + ] + special_tokens + vocab.extend(special_tokens) + self.encoder = dict(zip(vocab, range(len(vocab)))) + self.decoder = {v: k for k, v in self.encoder.items()} + self.bpe_ranks = dict(zip(merges, range(len(merges)))) + self.cache = {t: t for t in special_tokens} + special = '|'.join(special_tokens) + self.pat = re.compile( + special + + r"""|'s|'t|'re|'ve|'m|'ll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+""", + re.IGNORECASE) + + self.vocab_size = len(self.encoder) + self.all_special_ids = [self.encoder[t] for t in special_tokens] + + def bpe(self, token): + if token in self.cache: + return self.cache[token] + word = tuple(token[:-1]) + (token[-1] + '', ) + pairs = get_pairs(word) + + if not pairs: + return token + '' + + while True: + bigram = min( + pairs, key=lambda pair: self.bpe_ranks.get(pair, float('inf'))) + if bigram not in self.bpe_ranks: + break + first, second = bigram + new_word = [] + i = 0 + while i < len(word): + try: + j = word.index(first, i) + new_word.extend(word[i:j]) + i = j + except: # noqa: E722, E261 + new_word.extend(word[i:]) + break + + if word[i] == first and i < len(word) - 1 and word[ + i + 1] == second: + new_word.append(first + second) + i += 2 + else: + new_word.append(word[i]) + i += 1 + new_word = tuple(new_word) + word = new_word + if len(word) == 1: + break + else: + pairs = get_pairs(word) + word = ' '.join(word) + self.cache[token] = word + return word + + def encode(self, text): + bpe_tokens = [] + text = whitespace_clean(basic_clean(text)).lower() + for token in re.findall(self.pat, text): + token = ''.join(self.byte_encoder[b] + for b in token.encode('utf-8')) + bpe_tokens.extend(self.encoder[bpe_token] + for bpe_token in self.bpe(token).split(' ')) + return bpe_tokens + + def decode(self, tokens): + text = ''.join([self.decoder[token] for token in tokens]) + text = bytearray([self.byte_decoder[c] for c in text]).decode( + 'utf-8', errors='replace').replace('', ' ') + return text + + +_tokenizer = SimpleTokenizer() + + +def decode(output_ids: torch.Tensor): + output_ids = output_ids.cpu().numpy() + return _tokenizer.decode(output_ids) + + +def tokenize(texts: Union[str, List[str]], + context_length: int = 77) -> torch.LongTensor: + """Returns the tokenized representation of given input string(s) + + Parameters + ---------- + texts : Union[str, List[str]] + An input string or a list of input strings to tokenize + context_length : int + The context length to use; all CLIP models use 77 as the context length + + Returns + ------- + A two-dimensional tensor containing the resulting tokens, + shape = [number of input strings, context_length] + """ + if isinstance(texts, str): + texts = [texts] + + sot_token = _tokenizer.encoder[''] + eot_token = _tokenizer.encoder[''] + all_tokens = [[sot_token] + _tokenizer.encode(text) + [eot_token] + for text in texts] + result = torch.zeros(len(all_tokens), context_length, dtype=torch.long) + + for i, tokens in enumerate(all_tokens): + if len(tokens) > context_length: + tokens = tokens[:context_length] # Truncate + tokens[-1] = eot_token + result[i, :len(tokens)] = torch.tensor(tokens) + + return result + + +class HFTokenizer: + """HuggingFace tokenizer wrapper.""" + + def __init__(self, tokenizer_name: str): + from transformers import AutoTokenizer + self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_name) + + def save_pretrained(self, dest): + self.tokenizer.save_pretrained(dest) + + def __call__(self, + texts: Union[str, List[str]], + context_length: int = 77) -> torch.Tensor: + # same cleaning as for default tokenizer, except lowercasing + # adding lower (for case-sensitive tokenizers) will make it + # more robust but less sensitive to nuance + if isinstance(texts, str): + texts = [texts] + texts = [whitespace_clean(basic_clean(text)) for text in texts] + input_ids = self.tokenizer( + texts, + return_tensors='pt', + max_length=context_length, + padding='max_length', + truncation=True, + ).input_ids + return input_ids diff --git a/Seg_All_In_One_MMSeg/mmseg/utils/typing_utils.py b/Seg_All_In_One_MMSeg/mmseg/utils/typing_utils.py new file mode 100644 index 0000000..fba7d3b --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/utils/typing_utils.py @@ -0,0 +1,25 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Collecting some commonly used type hint in mmflow.""" +from typing import Dict, List, Optional, Sequence, Tuple, Union + +import torch +from mmengine.config import ConfigDict + +from mmseg.structures import SegDataSample + +# Type hint of config data +ConfigType = Union[ConfigDict, dict] +OptConfigType = Optional[ConfigType] +# Type hint of one or more config data +MultiConfig = Union[ConfigType, Sequence[ConfigType]] +OptMultiConfig = Optional[MultiConfig] + +SampleList = Sequence[SegDataSample] +OptSampleList = Optional[SampleList] + +# Type hint of Tensor +TensorDict = Dict[str, torch.Tensor] +TensorList = Sequence[torch.Tensor] + +ForwardResults = Union[Dict[str, torch.Tensor], List[SegDataSample], + Tuple[torch.Tensor], torch.Tensor] diff --git a/Seg_All_In_One_MMSeg/mmseg/version.py b/Seg_All_In_One_MMSeg/mmseg/version.py new file mode 100644 index 0000000..b76bb45 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/version.py @@ -0,0 +1,18 @@ +# Copyright (c) Open-MMLab. All rights reserved. + +__version__ = '1.2.2' + + +def parse_version_info(version_str): + version_info = [] + for x in version_str.split('.'): + if x.isdigit(): + version_info.append(int(x)) + elif x.find('rc') != -1: + patch_version = x.split('rc') + version_info.append(int(patch_version[0])) + version_info.append(f'rc{patch_version[1]}') + return tuple(version_info) + + +version_info = parse_version_info(__version__) diff --git a/Seg_All_In_One_MMSeg/mmseg/visualization/__init__.py b/Seg_All_In_One_MMSeg/mmseg/visualization/__init__.py new file mode 100644 index 0000000..8cbb211 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/visualization/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .local_visualizer import SegLocalVisualizer + +__all__ = ['SegLocalVisualizer'] diff --git a/Seg_All_In_One_MMSeg/mmseg/visualization/local_visualizer.py b/Seg_All_In_One_MMSeg/mmseg/visualization/local_visualizer.py new file mode 100644 index 0000000..ee3d652 --- /dev/null +++ b/Seg_All_In_One_MMSeg/mmseg/visualization/local_visualizer.py @@ -0,0 +1,349 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Dict, List, Optional + +import cv2 +import mmcv +import numpy as np +import torch +from mmengine.dist import master_only +from mmengine.structures import PixelData +from mmengine.visualization import Visualizer + +from mmseg.registry import VISUALIZERS +from mmseg.structures import SegDataSample +from mmseg.utils import get_classes, get_palette + + +@VISUALIZERS.register_module() +class SegLocalVisualizer(Visualizer): + """Local Visualizer. + + Args: + name (str): Name of the instance. Defaults to 'visualizer'. + image (np.ndarray, optional): the origin image to draw. The format + should be RGB. Defaults to None. + vis_backends (list, optional): Visual backend config list. + Defaults to None. + save_dir (str, optional): Save file dir for all storage backends. + If it is None, the backend storage will not save any data. + classes (list, optional): Input classes for result rendering, as the + prediction of segmentation model is a segment map with label + indices, `classes` is a list which includes items responding to the + label indices. If classes is not defined, visualizer will take + `cityscapes` classes by default. Defaults to None. + palette (list, optional): Input palette for result rendering, which is + a list of color palette responding to the classes. Defaults to None. + dataset_name (str, optional): `Dataset name or alias `_ + visulizer will use the meta information of the dataset i.e. classes + and palette, but the `classes` and `palette` have higher priority. + Defaults to None. + alpha (int, float): The transparency of segmentation mask. + Defaults to 0.8. + + Examples: + >>> import numpy as np + >>> import torch + >>> from mmengine.structures import PixelData + >>> from mmseg.structures import SegDataSample + >>> from mmseg.visualization import SegLocalVisualizer + + >>> seg_local_visualizer = SegLocalVisualizer() + >>> image = np.random.randint(0, 256, + ... size=(10, 12, 3)).astype('uint8') + >>> gt_sem_seg_data = dict(data=torch.randint(0, 2, (1, 10, 12))) + >>> gt_sem_seg = PixelData(**gt_sem_seg_data) + >>> gt_seg_data_sample = SegDataSample() + >>> gt_seg_data_sample.gt_sem_seg = gt_sem_seg + >>> seg_local_visualizer.dataset_meta = dict( + >>> classes=('background', 'foreground'), + >>> palette=[[120, 120, 120], [6, 230, 230]]) + >>> seg_local_visualizer.add_datasample('visualizer_example', + ... image, gt_seg_data_sample) + >>> seg_local_visualizer.add_datasample( + ... 'visualizer_example', image, + ... gt_seg_data_sample, show=True) + """ # noqa + + def __init__(self, + name: str = 'visualizer', + image: Optional[np.ndarray] = None, + vis_backends: Optional[Dict] = None, + save_dir: Optional[str] = None, + classes: Optional[List] = None, + palette: Optional[List] = None, + dataset_name: Optional[str] = None, + alpha: float = 0.8, + **kwargs): + super().__init__(name, image, vis_backends, save_dir, **kwargs) + self.alpha: float = alpha + self.set_dataset_meta(palette, classes, dataset_name) + + def _get_center_loc(self, mask: np.ndarray) -> np.ndarray: + """Get semantic seg center coordinate. + + Args: + mask: np.ndarray: get from sem_seg + """ + loc = np.argwhere(mask == 1) + + loc_sort = np.array( + sorted(loc.tolist(), key=lambda row: (row[0], row[1]))) + y_list = loc_sort[:, 0] + unique, indices, counts = np.unique( + y_list, return_index=True, return_counts=True) + y_loc = unique[counts.argmax()] + y_most_freq_loc = loc[loc_sort[:, 0] == y_loc] + center_num = len(y_most_freq_loc) // 2 + x = y_most_freq_loc[center_num][1] + y = y_most_freq_loc[center_num][0] + return np.array([x, y]) + + def _draw_sem_seg(self, + image: np.ndarray, + sem_seg: PixelData, + classes: Optional[List], + palette: Optional[List], + with_labels: Optional[bool] = True) -> np.ndarray: + """Draw semantic seg of GT or prediction. + + Args: + image (np.ndarray): The image to draw. + sem_seg (:obj:`PixelData`): Data structure for pixel-level + annotations or predictions. + classes (list, optional): Input classes for result rendering, as + the prediction of segmentation model is a segment map with + label indices, `classes` is a list which includes items + responding to the label indices. If classes is not defined, + visualizer will take `cityscapes` classes by default. + Defaults to None. + palette (list, optional): Input palette for result rendering, which + is a list of color palette responding to the classes. + Defaults to None. + with_labels(bool, optional): Add semantic labels in visualization + result, Default to True. + + Returns: + np.ndarray: the drawn image which channel is RGB. + """ + num_classes = len(classes) + + sem_seg = sem_seg.cpu().data + ids = np.unique(sem_seg)[::-1] + legal_indices = ids < num_classes + ids = ids[legal_indices] + labels = np.array(ids, dtype=np.int64) + + colors = [palette[label] for label in labels] + + mask = np.zeros_like(image, dtype=np.uint8) + for label, color in zip(labels, colors): + mask[sem_seg[0] == label, :] = color + + if with_labels: + font = cv2.FONT_HERSHEY_SIMPLEX + # (0,1] to change the size of the text relative to the image + scale = 0.05 + fontScale = min(image.shape[0], image.shape[1]) / (25 / scale) + fontColor = (255, 255, 255) + if image.shape[0] < 300 or image.shape[1] < 300: + thickness = 1 + rectangleThickness = 1 + else: + thickness = 2 + rectangleThickness = 2 + lineType = 2 + + if isinstance(sem_seg[0], torch.Tensor): + masks = sem_seg[0].numpy() == labels[:, None, None] + else: + masks = sem_seg[0] == labels[:, None, None] + masks = masks.astype(np.uint8) + for mask_num in range(len(labels)): + classes_id = labels[mask_num] + classes_color = colors[mask_num] + loc = self._get_center_loc(masks[mask_num]) + text = classes[classes_id] + (label_width, label_height), baseline = cv2.getTextSize( + text, font, fontScale, thickness) + mask = cv2.rectangle(mask, loc, + (loc[0] + label_width + baseline, + loc[1] + label_height + baseline), + classes_color, -1) + mask = cv2.rectangle(mask, loc, + (loc[0] + label_width + baseline, + loc[1] + label_height + baseline), + (0, 0, 0), rectangleThickness) + mask = cv2.putText(mask, text, (loc[0], loc[1] + label_height), + font, fontScale, fontColor, thickness, + lineType) + color_seg = (image * (1 - self.alpha) + mask * self.alpha).astype( + np.uint8) + self.set_image(color_seg) + return color_seg + + def _draw_depth_map(self, image: np.ndarray, + depth_map: PixelData) -> np.ndarray: + """Draws a depth map on a given image. + + This function takes an image and a depth map as input, + renders the depth map, and concatenates it with the original image. + Finally, it updates the internal image state of the visualizer with + the concatenated result. + + Args: + image (np.ndarray): The original image where the depth map will + be drawn. The array should be in the format HxWx3 where H is + the height, W is the width. + + depth_map (PixelData): Depth map to be drawn. The depth map + should be in the form of a PixelData object. It will be + converted to a torch tensor if it is a numpy array. + + Returns: + np.ndarray: The concatenated image with the depth map drawn. + + Example: + >>> depth_map_data = PixelData(data=torch.rand(1, 10, 10)) + >>> image = np.random.randint(0, 256, + >>> size=(10, 10, 3)).astype('uint8') + >>> visualizer = SegLocalVisualizer() + >>> visualizer._draw_depth_map(image, depth_map_data) + """ + depth_map = depth_map.cpu().data + if isinstance(depth_map, np.ndarray): + depth_map = torch.from_numpy(depth_map) + if depth_map.ndim == 2: + depth_map = depth_map[None] + + depth_map = self.draw_featmap(depth_map, resize_shape=image.shape[:2]) + out_image = np.concatenate((image, depth_map), axis=0) + self.set_image(out_image) + return out_image + + def set_dataset_meta(self, + classes: Optional[List] = None, + palette: Optional[List] = None, + dataset_name: Optional[str] = None) -> None: + """Set meta information to visualizer. + + Args: + classes (list, optional): Input classes for result rendering, as + the prediction of segmentation model is a segment map with + label indices, `classes` is a list which includes items + responding to the label indices. If classes is not defined, + visualizer will take `cityscapes` classes by default. + Defaults to None. + palette (list, optional): Input palette for result rendering, which + is a list of color palette responding to the classes. + Defaults to None. + dataset_name (str, optional): `Dataset name or alias `_ + visulizer will use the meta information of the dataset i.e. + classes and palette, but the `classes` and `palette` have + higher priority. Defaults to None. + """ # noqa + # Set default value. When calling + # `SegLocalVisualizer().dataset_meta=xxx`, + # it will override the default value. + if dataset_name is None: + dataset_name = 'cityscapes' + classes = classes if classes else get_classes(dataset_name) + palette = palette if palette else get_palette(dataset_name) + assert len(classes) == len( + palette), 'The length of classes should be equal to palette' + self.dataset_meta: dict = {'classes': classes, 'palette': palette} + + @master_only + def add_datasample( + self, + name: str, + image: np.ndarray, + data_sample: Optional[SegDataSample] = None, + draw_gt: bool = True, + draw_pred: bool = True, + show: bool = False, + wait_time: float = 0, + # TODO: Supported in mmengine's Viusalizer. + out_file: Optional[str] = None, + step: int = 0, + with_labels: Optional[bool] = True) -> None: + """Draw datasample and save to all backends. + + - If GT and prediction are plotted at the same time, they are + displayed in a stitched image where the left image is the + ground truth and the right image is the prediction. + - If ``show`` is True, all storage backends are ignored, and + the images will be displayed in a local window. + - If ``out_file`` is specified, the drawn image will be + saved to ``out_file``. it is usually used when the display + is not available. + + Args: + name (str): The image identifier. + image (np.ndarray): The image to draw. + gt_sample (:obj:`SegDataSample`, optional): GT SegDataSample. + Defaults to None. + pred_sample (:obj:`SegDataSample`, optional): Prediction + SegDataSample. Defaults to None. + draw_gt (bool): Whether to draw GT SegDataSample. Default to True. + draw_pred (bool): Whether to draw Prediction SegDataSample. + Defaults to True. + show (bool): Whether to display the drawn image. Default to False. + wait_time (float): The interval of show (s). Defaults to 0. + out_file (str): Path to output file. Defaults to None. + step (int): Global step value to record. Defaults to 0. + with_labels(bool, optional): Add semantic labels in visualization + result, Defaults to True. + """ + classes = self.dataset_meta.get('classes', None) + palette = self.dataset_meta.get('palette', None) + + gt_img_data = None + pred_img_data = None + + if draw_gt and data_sample is not None: + if 'gt_sem_seg' in data_sample: + assert classes is not None, 'class information is ' \ + 'not provided when ' \ + 'visualizing semantic ' \ + 'segmentation results.' + gt_img_data = self._draw_sem_seg(image, data_sample.gt_sem_seg, + classes, palette, with_labels) + + if 'gt_depth_map' in data_sample: + gt_img_data = gt_img_data if gt_img_data is not None else image + gt_img_data = self._draw_depth_map(gt_img_data, + data_sample.gt_depth_map) + + if draw_pred and data_sample is not None: + + if 'pred_sem_seg' in data_sample: + + assert classes is not None, 'class information is ' \ + 'not provided when ' \ + 'visualizing semantic ' \ + 'segmentation results.' + pred_img_data = self._draw_sem_seg(image, + data_sample.pred_sem_seg, + classes, palette, + with_labels) + + if 'pred_depth_map' in data_sample: + pred_img_data = pred_img_data if pred_img_data is not None \ + else image + pred_img_data = self._draw_depth_map( + pred_img_data, data_sample.pred_depth_map) + + if gt_img_data is not None and pred_img_data is not None: + drawn_img = np.concatenate((gt_img_data, pred_img_data), axis=1) + elif gt_img_data is not None: + drawn_img = gt_img_data + else: + drawn_img = pred_img_data + + if show: + self.show(drawn_img, win_name=name, wait_time=wait_time) + + if out_file is not None: + mmcv.imwrite(mmcv.rgb2bgr(drawn_img), out_file) + else: + self.add_image(name, drawn_img, step) diff --git a/Seg_All_In_One_MMSeg/model-index.yml b/Seg_All_In_One_MMSeg/model-index.yml new file mode 100644 index 0000000..4026bb9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/model-index.yml @@ -0,0 +1,53 @@ +Import: +- configs/ann/metafile.yaml +- configs/apcnet/metafile.yaml +- configs/beit/metafile.yaml +- configs/bisenetv1/metafile.yaml +- configs/bisenetv2/metafile.yaml +- configs/ccnet/metafile.yaml +- configs/cgnet/metafile.yaml +- configs/convnext/metafile.yaml +- configs/danet/metafile.yaml +- configs/ddrnet/metafile.yaml +- configs/deeplabv3/metafile.yaml +- configs/deeplabv3plus/metafile.yaml +- configs/dmnet/metafile.yaml +- configs/dnlnet/metafile.yaml +- configs/dpt/metafile.yaml +- configs/emanet/metafile.yaml +- configs/encnet/metafile.yaml +- configs/erfnet/metafile.yaml +- configs/fastfcn/metafile.yaml +- configs/fastscnn/metafile.yaml +- configs/fcn/metafile.yaml +- configs/gcnet/metafile.yaml +- configs/hrnet/metafile.yaml +- configs/icnet/metafile.yaml +- configs/isanet/metafile.yaml +- configs/knet/metafile.yaml +- configs/mae/metafile.yaml +- configs/mask2former/metafile.yaml +- configs/maskformer/metafile.yaml +- configs/mobilenet_v2/metafile.yaml +- configs/mobilenet_v3/metafile.yaml +- configs/nonlocal_net/metafile.yaml +- configs/ocrnet/metafile.yaml +- configs/pidnet/metafile.yaml +- configs/point_rend/metafile.yaml +- configs/poolformer/metafile.yaml +- configs/psanet/metafile.yaml +- configs/pspnet/metafile.yaml +- configs/resnest/metafile.yaml +- configs/san/metafile.yaml +- configs/segformer/metafile.yaml +- configs/segmenter/metafile.yaml +- configs/segnext/metafile.yaml +- configs/sem_fpn/metafile.yaml +- configs/setr/metafile.yaml +- configs/stdc/metafile.yaml +- configs/swin/metafile.yaml +- configs/twins/metafile.yaml +- configs/unet/metafile.yaml +- configs/upernet/metafile.yaml +- configs/vit/metafile.yaml +- configs/vpd/metafile.yaml diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/README.md b/Seg_All_In_One_MMSeg/projects/Adabins/README.md new file mode 100644 index 0000000..8a23e92 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/README.md @@ -0,0 +1,46 @@ +# AdaBins: Depth Estimation Using Adaptive Bins + +## Reference + +> [AdaBins: Depth Estimation Using Adaptive Bins](https://arxiv.org/abs/2011.14141) + +## Introduction + +Official Repo + +Code Snippet + +## Abstract + +We address the problem of estimating a high quality dense depth map from a single RGB input image. We start out with a baseline encoder-decoder convolutional neural network architecture and pose the question of how the global processing of information can help improve overall depth estimation. To this end, we propose a transformer-based architecture block that divides the depth range into bins whose center value is estimated adaptively per image. The final depth values are estimated as linear combinations of the bin centers. We call our new building block AdaBins. Our results show a decisive improvement over the state-of-the-art on several popular depth datasets across all metrics.We also validate the effectiveness of the proposed block with an ablation study and provide the code and corresponding pre-trained weights of the new state-of-the-art model. + +Our main contributions are the following: + +- We propose an architecture building block that performs global processing of the scene’s information.We propose to divide the predicted depth range into bins where the bin widths change per image. The final depth estimation is a linear combination of the bin center values. +- We show a decisive improvement for supervised single image depth estimation across all metrics for the two most popular datasets, NYU and KITTI. +- We analyze our findings and investigate different modifications on the proposed AdaBins block and study their effect on the accuracy of the depth estimation. + +

+ +
+ +## Performance + +### NYU and KITTI + +| Model | Encoder | Training epoch | Batchsize | Train Resolution | δ1 | δ2 | δ3 | REL | RMS | RMS log | params(M) | Links | +| ------------- | --------------- | -------------- | --------- | ---------------- | ----- | ----- | ----- | ----- | ----- | ------- | --------- | ----------------------------------------------------------------------------------------------------------------------- | +| AdaBins_nyu | EfficientNet-B5 | 25 | 16 | 416x544 | 0.903 | 0.984 | 0.997 | 0.103 | 0.364 | 0.044 | 78 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/adabins/adabins_efficient_b5_nyu_third-party-f68d6bd3.pth) | +| AdaBins_kitti | EfficientNet-B5 | 25 | 16 | 352x764 | 0.964 | 0.995 | 0.999 | 0.058 | 2.360 | 0.088 | 78 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/adabins/adabins_efficient-b5_kitty_third-party-a1aa6f36.pth) | + +## Citation + +```bibtex +@article{10.1109/cvpr46437.2021.00400, + author = {Bhat, S. A. and Alhashim, I. and Wonka, P.}, + title = {Adabins: depth estimation using adaptive bins}, + journal = {2021 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, + year = {2021}, + doi = {10.1109/cvpr46437.2021.00400} +} +``` diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/backbones/__init__.py b/Seg_All_In_One_MMSeg/projects/Adabins/backbones/__init__.py new file mode 100644 index 0000000..04ae180 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/backbones/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .adabins_backbone import AdabinsBackbone + +__all__ = ['AdabinsBackbone'] diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/backbones/adabins_backbone.py b/Seg_All_In_One_MMSeg/projects/Adabins/backbones/adabins_backbone.py new file mode 100644 index 0000000..07d7380 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/backbones/adabins_backbone.py @@ -0,0 +1,141 @@ +import timm +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, build_conv_layer +from mmengine.model import BaseModule + +from mmseg.registry import MODELS + + +class UpSampleBN(nn.Module): + """ UpSample module + Args: + skip_input (int): the input feature + output_features (int): the output feature + norm_cfg (dict, optional): Config dict for normalization layer. + Default: dict(type='BN', requires_grad=True). + act_cfg (dict, optional): The activation layer of AAM: + Aggregate Attention Module. + """ + + def __init__(self, + skip_input, + output_features, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='LeakyReLU')): + super().__init__() + + self._net = nn.Sequential( + ConvModule( + in_channels=skip_input, + out_channels=output_features, + kernel_size=3, + stride=1, + padding=1, + bias=True, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ), + ConvModule( + in_channels=output_features, + out_channels=output_features, + kernel_size=3, + stride=1, + padding=1, + bias=True, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + )) + + def forward(self, x, concat_with): + up_x = F.interpolate( + x, + size=[concat_with.size(2), + concat_with.size(3)], + mode='bilinear', + align_corners=True) + f = torch.cat([up_x, concat_with], dim=1) + return self._net(f) + + +class Encoder(nn.Module): + """ the efficientnet_b5 model + Args: + basemodel_name (str): the name of base model + """ + + def __init__(self, basemodel_name): + super().__init__() + self.original_model = timm.create_model( + basemodel_name, pretrained=True) + # Remove last layer + self.original_model.global_pool = nn.Identity() + self.original_model.classifier = nn.Identity() + + def forward(self, x): + features = [x] + for k, v in self.original_model._modules.items(): + if k == 'blocks': + for ki, vi in v._modules.items(): + features.append(vi(features[-1])) + else: + features.append(v(features[-1])) + return features + + +@MODELS.register_module() +class AdabinsBackbone(BaseModule): + """ the backbone of the adabins + Args: + basemodel_name (str):the name of base model + num_features (int): the middle feature + num_classes (int): the classes number + bottleneck_features (int): the bottleneck features + conv_cfg (dict): Config dict for convolution layer. + """ + + def __init__(self, + basemodel_name, + num_features=2048, + num_classes=128, + bottleneck_features=2048, + conv_cfg=dict(type='Conv')): + super().__init__() + self.encoder = Encoder(basemodel_name) + features = int(num_features) + self.conv2 = build_conv_layer( + conv_cfg, + bottleneck_features, + features, + kernel_size=1, + stride=1, + padding=1) + self.up1 = UpSampleBN( + skip_input=features // 1 + 112 + 64, output_features=features // 2) + self.up2 = UpSampleBN( + skip_input=features // 2 + 40 + 24, output_features=features // 4) + self.up3 = UpSampleBN( + skip_input=features // 4 + 24 + 16, output_features=features // 8) + self.up4 = UpSampleBN( + skip_input=features // 8 + 16 + 8, output_features=features // 16) + + self.conv3 = build_conv_layer( + conv_cfg, + features // 16, + num_classes, + kernel_size=3, + stride=1, + padding=1) + + def forward(self, x): + features = self.encoder(x) + x_block0, x_block1, x_block2, x_block3, x_block4 = features[ + 3], features[4], features[5], features[7], features[10] + x_d0 = self.conv2(x_block4) + x_d1 = self.up1(x_d0, x_block3) + x_d2 = self.up2(x_d1, x_block2) + x_d3 = self.up3(x_d2, x_block1) + x_d4 = self.up4(x_d3, x_block0) + out = self.conv3(x_d4) + return out diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/datasets/nyu.py b/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/datasets/nyu.py new file mode 100644 index 0000000..1b49ec7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/datasets/nyu.py @@ -0,0 +1,32 @@ +dataset_type = 'NYUDataset' +data_root = 'data/nyu' + +test_pipeline = [ + dict(dict(type='LoadImageFromFile', to_float32=True)), + dict(dict(type='LoadDepthAnnotation', depth_rescale_factor=1e-3)), + dict( + type='PackSegInputs', + meta_keys=('img_path', 'depth_map_path', 'ori_shape', 'img_shape', + 'pad_shape', 'scale_factor', 'flip', 'flip_direction', + 'category_id')) +] + +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + test_mode=True, + data_prefix=dict( + img_path='images/test', depth_map_path='annotations/test'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict( + type='DepthMetric', max_depth_eval=10.0, crop_type='nyu_crop') +test_evaluator = val_evaluator +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/default_runtime.py b/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/default_runtime.py new file mode 100644 index 0000000..272b4d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/default_runtime.py @@ -0,0 +1,15 @@ +default_scope = 'mmseg' +env_cfg = dict( + cudnn_benchmark=True, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict(by_epoch=False) +log_level = 'INFO' +load_from = None +resume = False + +tta_model = dict(type='SegTTAModel') diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/models/Adabins.py b/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/models/Adabins.py new file mode 100644 index 0000000..35cbd8c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/configs/_base_/models/Adabins.py @@ -0,0 +1,35 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='DepthEstimator', + data_preprocessor=data_preprocessor, + # pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='AdabinsBackbone', + basemodel_name='tf_efficientnet_b5_ap', + num_features=2048, + num_classes=128, + bottleneck_features=2048, + ), + decode_head=dict( + type='AdabinsHead', + in_channels=128, + n_query_channels=128, + patch_size=16, + embedding_dim=128, + num_heads=4, + n_bins=256, + min_val=0.001, + max_val=10, + norm='linear'), + + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/configs/adabins/adabins_efficient_b5_4x16_25e_NYU_416x544.py b/Seg_All_In_One_MMSeg/projects/Adabins/configs/adabins/adabins_efficient_b5_4x16_25e_NYU_416x544.py new file mode 100644 index 0000000..5c00ea1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/configs/adabins/adabins_efficient_b5_4x16_25e_NYU_416x544.py @@ -0,0 +1,15 @@ +_base_ = [ + '../_base_/models/Adabins.py', '../_base_/datasets/nyu.py', + '../_base_/default_runtime.py' +] +custom_imports = dict( + imports=['projects.Adabins.backbones', 'projects.Adabins.decode_head'], + allow_failed_imports=False) +crop_size = (416, 544) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(), + decode_head=dict(), +) diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/configs/adabins/adabins_efficient_b5_4x16_25e_kitti_352x704.py b/Seg_All_In_One_MMSeg/projects/Adabins/configs/adabins/adabins_efficient_b5_4x16_25e_kitti_352x704.py new file mode 100644 index 0000000..330cdf4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/configs/adabins/adabins_efficient_b5_4x16_25e_kitti_352x704.py @@ -0,0 +1,12 @@ +_base_ = ['../_base_/models/Adabins.py'] +custom_imports = dict( + imports=['projects.Adabins.backbones', 'projects.Adabins.decode_head'], + allow_failed_imports=False) +crop_size = (352, 704) +data_preprocessor = dict(size=crop_size) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(), + decode_head=dict(min_val=0.001, max_val=80), +) diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/decode_head/__init__.py b/Seg_All_In_One_MMSeg/projects/Adabins/decode_head/__init__.py new file mode 100644 index 0000000..c7d62df --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/decode_head/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .adabins_head import AdabinsHead + +__all__ = ['AdabinsHead'] diff --git a/Seg_All_In_One_MMSeg/projects/Adabins/decode_head/adabins_head.py b/Seg_All_In_One_MMSeg/projects/Adabins/decode_head/adabins_head.py new file mode 100644 index 0000000..ee04317 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/Adabins/decode_head/adabins_head.py @@ -0,0 +1,179 @@ +from typing import List, Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_conv_layer +from torch import Tensor + +from mmseg.registry import MODELS + + +class PatchTransformerEncoder(nn.Module): + """the Patch Transformer Encoder. + + Args: + in_channels (int): the channels of input + patch_size (int): the path size + embedding_dim (int): The feature dimension. + num_heads (int): the number of encoder head + conv_cfg (dict): Config dict for convolution layer. + """ + + def __init__(self, + in_channels, + patch_size=10, + embedding_dim=128, + num_heads=4, + conv_cfg=dict(type='Conv')): + super().__init__() + encoder_layers = nn.TransformerEncoderLayer( + embedding_dim, num_heads, dim_feedforward=1024) + self.transformer_encoder = nn.TransformerEncoder( + encoder_layers, num_layers=4) # takes shape S,N,E + + self.embedding_convPxP = build_conv_layer( + conv_cfg, + in_channels, + embedding_dim, + kernel_size=patch_size, + stride=patch_size) + self.positional_encodings = nn.Parameter( + torch.rand(500, embedding_dim), requires_grad=True) + + def forward(self, x): + embeddings = self.embedding_convPxP(x).flatten( + 2) # .shape = n,c,s = n, embedding_dim, s + embeddings = embeddings + self.positional_encodings[:embeddings.shape[ + 2], :].T.unsqueeze(0) + + # change to S,N,E format required by transformer + embeddings = embeddings.permute(2, 0, 1) + x = self.transformer_encoder(embeddings) # .shape = S, N, E + return x + + +class PixelWiseDotProduct(nn.Module): + """the pixel wise dot product.""" + + def __init__(self): + super().__init__() + + def forward(self, x, K): + n, c, h, w = x.size() + _, cout, ck = K.size() + assert c == ck, 'Number of channels in x and Embedding dimension ' \ + '(at dim 2) of K matrix must match' + y = torch.matmul( + x.view(n, c, h * w).permute(0, 2, 1), + K.permute(0, 2, 1)) # .shape = n, hw, cout + return y.permute(0, 2, 1).view(n, cout, h, w) + + +@MODELS.register_module() +class AdabinsHead(nn.Module): + """the head of the adabins,include mViT. + + Args: + in_channels (int):the channels of the input + n_query_channels (int):the channels of the query + patch_size (int): the patch size + embedding_dim (int):The feature dimension. + num_heads (int):the number of head + n_bins (int):the number of bins + min_val (float): the min width of bin + max_val (float): the max width of bin + conv_cfg (dict): Config dict for convolution layer. + norm (str): the activate method + align_corners (bool, optional): Geometrically, we consider the pixels + of the input and output as squares rather than points. + """ + + def __init__(self, + in_channels, + n_query_channels=128, + patch_size=16, + embedding_dim=128, + num_heads=4, + n_bins=100, + min_val=0.1, + max_val=10, + conv_cfg=dict(type='Conv'), + norm='linear', + align_corners=False, + threshold=0): + super().__init__() + self.out_channels = n_bins + self.align_corners = align_corners + self.norm = norm + self.num_classes = n_bins + self.min_val = min_val + self.max_val = max_val + self.n_query_channels = n_query_channels + self.patch_transformer = PatchTransformerEncoder( + in_channels, patch_size, embedding_dim, num_heads) + self.dot_product_layer = PixelWiseDotProduct() + self.threshold = threshold + self.conv3x3 = build_conv_layer( + conv_cfg, + in_channels, + embedding_dim, + kernel_size=3, + stride=1, + padding=1) + self.regressor = nn.Sequential( + nn.Linear(embedding_dim, 256), nn.LeakyReLU(), nn.Linear(256, 256), + nn.LeakyReLU(), nn.Linear(256, n_bins)) + self.conv_out = nn.Sequential( + build_conv_layer(conv_cfg, in_channels, n_bins, kernel_size=1), + nn.Softmax(dim=1)) + + def forward(self, x): + # n, c, h, w = x.size() + tgt = self.patch_transformer(x.clone()) # .shape = S, N, E + + x = self.conv3x3(x) + + regression_head, queries = tgt[0, + ...], tgt[1:self.n_query_channels + 1, + ...] + + # Change from S, N, E to N, S, E + queries = queries.permute(1, 0, 2) + range_attention_maps = self.dot_product_layer( + x, queries) # .shape = n, n_query_channels, h, w + + y = self.regressor(regression_head) # .shape = N, dim_out + if self.norm == 'linear': + y = torch.relu(y) + eps = 0.1 + y = y + eps + elif self.norm == 'softmax': + return torch.softmax(y, dim=1), range_attention_maps + else: + y = torch.sigmoid(y) + bin_widths_normed = y / y.sum(dim=1, keepdim=True) + out = self.conv_out(range_attention_maps) + + bin_widths = (self.max_val - + self.min_val) * bin_widths_normed # .shape = N, dim_out + bin_widths = F.pad( + bin_widths, (1, 0), mode='constant', value=self.min_val) + bin_edges = torch.cumsum(bin_widths, dim=1) + + centers = 0.5 * (bin_edges[:, :-1] + bin_edges[:, 1:]) + n, dim_out = centers.size() + centers = centers.view(n, dim_out, 1, 1) + + pred = torch.sum(out * centers, dim=1, keepdim=True) + return bin_edges, pred + + def predict(self, inputs: Tuple[Tensor], batch_img_metas: List[dict], + test_cfg, **kwargs) -> Tensor: + """Forward function for testing, only ``pam_cam`` is used.""" + pred = self.forward(inputs)[-1] + final = torch.clamp(pred, self.min_val, self.max_val) + + final[torch.isinf(final)] = self.max_val + final[torch.isnan(final)] = self.min_val + return final diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/README.md b/Seg_All_In_One_MMSeg/projects/CAT-Seg/README.md new file mode 100644 index 0000000..890e461 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/README.md @@ -0,0 +1,92 @@ +# CAT-Seg + +> [CAT-Seg: Cost Aggregation for Open-Vocabulary Semantic Segmentation](https://arxiv.org/abs/2303.11797) + +## Introduction + + + +Official Repo + +Code Snippet + +## Abstract + + + +Existing works on open-vocabulary semantic segmentation have utilized large-scale vision-language models, such as CLIP, to leverage their exceptional open-vocabulary recognition capabilities. However, the problem of transferring these capabilities learned from image-level supervision to the pixel-level task of segmentation and addressing arbitrary unseen categories at inference makes this task challenging. To address these issues, we aim to attentively relate objects within an image to given categories by leveraging relational information among class categories and visual semantics through aggregation, while also adapting the CLIP representations to the pixel-level task. However, we observe that direct optimization of the CLIP embeddings can harm its open-vocabulary capabilities. In this regard, we propose an alternative approach to optimize the imagetext similarity map, i.e. the cost map, using a novel cost aggregation-based method. Our framework, namely CATSeg, achieves state-of-the-art performance across all benchmarks. We provide extensive ablation studies to validate our choices. [Project page](https://ku-cvlab.github.io/CAT-Seg). + + + +
+CAT-Seg +CAT-Seg model structure +
+ +## Usage + +CAT-Seg model training needs pretrained `CLIP` model. We have implemented `ViT-B` and `ViT-L` based `CLIP` model. To further use `ViT-bigG` or `ViT-H` ones, you need additional dependencies. Please install [open_clip](https://github.com/mlfoundations/open_clip) first. The pretrained `CLIP` model state dicts are loaded from [Huggingface-OpenCLIP](https://huggingface.co/models?library=open_clip). **If you come up with `ConnectionError` when downloading CLIP weights**, you can manually download them from the given repo and use `custom_clip_weights=/path/to/you/folder` of backbone in config file. Related tools are as shown in [requirements/optional.txt](requirements/optional.txt): + +```shell +pip install ftfy==6.0.1 +pip install huggingface-hub +pip install regex +``` + +In addition to the necessary [data preparation](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/en/user_guides/2_dataset_prepare.md), you also need class texts for clip text encoder. Please download the class text json file first [cls_texts](https://github.com/open-mmlab/mmsegmentation/files/11714914/cls_texts.zip) and arrange the folder as follows: + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── VOCdevkit +│ │ ├── VOC2012 +│ │ ├── VOC2010 +│ │ ├── VOCaug +│ ├── ade +│ ├── coco_stuff164k +│ ├── coco.json +│ ├── pc59.json +│ ├── pc459.json +│ ├── ade150.json +│ ├── ade847.json +│ ├── voc20b.json +│ ├── voc20.json +``` + +```shell +# setup PYTHONPATH +export PYTHONPATH=`pwd`:$PYTHONPATH +# run evaluation +mim test mmsegmentation ${CONFIG} --checkpoint ${CHECKPOINT} --launcher pytorch --gpus=8 +``` + +## Results and models + +### ADE20K-150-ZeroShot + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | Device | mIoU | mIoU(ms+flip) | config | download | +| ------- | ------------- | --------- | ------- | -------: | -------------- | ------- | ---- | ------------: | ------------------------------------------------------------------------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------- | +| CAT-Seg | R-101 & ViT-B | 384x384 | 80000 | - | - | RTX3090 | 27.2 | - | [config](./configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_ade20k-384x384.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_ade20k-384x384-54194d72.pth) | + +Note: + +- All experiments of CAT-Seg are implemented with 4 RTX3090 GPUs, except the last one with pretrained ViT-bigG CLIP model (GPU Memory insufficient, you may need A100). +- Due to the feature size bottleneck of the CLIP image encoder, the inference and testing can only be done under `slide` mode, the inference time is longer since the test size is much more bigger that training size of `(384, 384)`. +- The ResNet backbones utilized in CAT-Seg models are standard `ResNet` rather than `ResNetV1c`. +- The zero-shot segmentation results on PASCAL VOC and ADE20K are from the original paper. Our results are coming soon. We appreatiate your contribution! +- In additional to zero-shot segmentation performance results, we also provided the evaluation results on the `val2017` set of **COCO-stuff164k** for reference, which is the training dataset of CAT-Seg. The testing was done **without TTA**. +- The number behind the dataset name is the category number for segmentation evaluation (except training data **COCO-stuff 164k**). **PASCAL VOC-20b** defines the "background" as classes present in **PASCAL-Context-59** but not in **PASCAL VOC-20**. + +## Citation + +```bibtex +@inproceedings{cheng2021mask2former, + title={CAT-Seg: Cost Aggregation for Open-Vocabulary Semantic Segmentation}, + author={Seokju Cho and Heeseong Shin and Sunghwan Hong and Seungjun An and Seungjun Lee and Anurag Arnab and Paul Hongsuck Seo and Seungryong Kim}, + journal={CVPR}, + year={2023} +} +``` diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/__init__.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/__init__.py new file mode 100644 index 0000000..2c51fba --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/__init__.py @@ -0,0 +1,2 @@ +from .models import * # noqa: F401,F403 +from .utils import * # noqa: F401,F403 diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/__init__.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/__init__.py new file mode 100644 index 0000000..cd0e15d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .cat_aggregator import (AggregatorLayer, CATSegAggregator, + ClassAggregateLayer, SpatialAggregateLayer) +from .cat_head import CATSegHead +from .clip_ovseg import CLIPOVCATSeg + +__all__ = [ + 'AggregatorLayer', 'CATSegAggregator', 'ClassAggregateLayer', + 'SpatialAggregateLayer', 'CATSegHead', 'CLIPOVCATSeg' +] diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/cat_aggregator.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/cat_aggregator.py new file mode 100644 index 0000000..a0483fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/cat_aggregator.py @@ -0,0 +1,763 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import build_norm_layer +from mmcv.cnn.bricks.transformer import FFN, build_dropout +from mmengine.model import BaseModule +from mmengine.utils import to_2tuple + +from mmseg.registry import MODELS +from ..utils import FullAttention, LinearAttention + + +class AGWindowMSA(BaseModule): + """Appearance Guidance Window based multi-head self-attention (W-MSA) + module with relative position bias. + + Args: + embed_dims (int): Number of input channels. + appearance_dims (int): Number of appearance guidance feature channels. + num_heads (int): Number of attention heads. + window_size (tuple[int]): The height and width of the window. + qkv_bias (bool, optional): If True, add a learnable bias to q, k, v. + Default: True. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + attn_drop_rate (float, optional): Dropout ratio of attention weight. + Default: 0.0 + proj_drop_rate (float, optional): Dropout ratio of output. Default: 0. + init_cfg (dict | None, optional): The Config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + appearance_dims, + num_heads, + window_size, + qkv_bias=True, + qk_scale=None, + attn_drop_rate=0., + proj_drop_rate=0., + init_cfg=None): + + super().__init__(init_cfg=init_cfg) + self.embed_dims = embed_dims + self.appearance_dims = appearance_dims + self.window_size = window_size # Wh, Ww + self.num_heads = num_heads + head_embed_dims = embed_dims // num_heads + self.scale = qk_scale or head_embed_dims**-0.5 + + # About 2x faster than original impl + Wh, Ww = self.window_size + rel_index_coords = self.double_step_seq(2 * Ww - 1, Wh, 1, Ww) + rel_position_index = rel_index_coords + rel_index_coords.T + rel_position_index = rel_position_index.flip(1).contiguous() + self.register_buffer('relative_position_index', rel_position_index) + + self.qk = nn.Linear( + embed_dims + appearance_dims, embed_dims * 2, bias=qkv_bias) + self.v = nn.Linear(embed_dims, embed_dims, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop_rate) + self.proj = nn.Linear(embed_dims, embed_dims) + self.proj_drop = nn.Dropout(proj_drop_rate) + + self.softmax = nn.Softmax(dim=-1) + + def forward(self, x, mask=None): + """ + Args: + x (tensor): input features with shape of (num_windows*B, N, C), + C = embed_dims + appearance_dims. + mask (tensor | None, Optional): mask with shape of (num_windows, + Wh*Ww, Wh*Ww), value should be between (-inf, 0]. + """ + B, N, _ = x.shape + qk = self.qk(x).reshape(B, N, 2, self.num_heads, + self.embed_dims // self.num_heads).permute( + 2, 0, 3, 1, + 4) # 2 B NUM_HEADS N embed_dims//NUM_HEADS + v = self.v(x[:, :, :self.embed_dims]).reshape( + B, N, self.num_heads, self.embed_dims // self.num_heads).permute( + 0, 2, 1, 3) # B NUM_HEADS N embed_dims//NUM_HEADS + # make torchscript happy (cannot use tensor as tuple) + q, k = qk[0], qk[1] + + q = q * self.scale + attn = (q @ k.transpose(-2, -1)) + + if mask is not None: + nW = mask.shape[0] + attn = attn.view(B // nW, nW, self.num_heads, N, + N) + mask.unsqueeze(1).unsqueeze(0) + attn = attn.view(-1, self.num_heads, N, N) + attn = self.softmax(attn) + + attn = self.attn_drop(attn) + + x = (attn @ v).transpose(1, 2).reshape(B, N, self.embed_dims) + x = self.proj(x) + x = self.proj_drop(x) + return x + + @staticmethod + def double_step_seq(step1, len1, step2, len2): + """Double step sequence.""" + seq1 = torch.arange(0, step1 * len1, step1) + seq2 = torch.arange(0, step2 * len2, step2) + return (seq1[:, None] + seq2[None, :]).reshape(1, -1) + + +class AGShiftWindowMSA(BaseModule): + """Appearance Guidance Shifted Window Multihead Self-Attention Module. + + Args: + embed_dims (int): Number of input channels. + appearance_dims (int): Number of appearance guidance channels + num_heads (int): Number of attention heads. + window_size (int): The height and width of the window. + shift_size (int, optional): The shift step of each window towards + right-bottom. If zero, act as regular window-msa. Defaults to 0. + qkv_bias (bool, optional): If True, add a learnable bias to q, k, v. + Default: True + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Defaults: None. + attn_drop_rate (float, optional): Dropout ratio of attention weight. + Defaults: 0. + proj_drop_rate (float, optional): Dropout ratio of output. + Defaults: 0. + dropout_layer (dict, optional): The dropout_layer used before output. + Defaults: dict(type='DropPath', drop_prob=0.). + init_cfg (dict, optional): The extra config for initialization. + Default: None. + """ + + def __init__(self, + embed_dims, + appearance_dims, + num_heads, + window_size, + shift_size=0, + qkv_bias=True, + qk_scale=None, + attn_drop_rate=0, + proj_drop_rate=0, + dropout_layer=dict(type='DropPath', drop_prob=0.), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + + self.window_size = window_size + self.shift_size = shift_size + assert 0 <= self.shift_size < self.window_size + + self.w_msa = AGWindowMSA( + embed_dims=embed_dims, + appearance_dims=appearance_dims, + num_heads=num_heads, + window_size=to_2tuple(window_size), + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop_rate=attn_drop_rate, + proj_drop_rate=proj_drop_rate, + init_cfg=None) + + self.drop = build_dropout(dropout_layer) + + def forward(self, query, hw_shape): + """ + Args: + query: The input query. + hw_shape: The shape of the feature height and width. + """ + B, L, C = query.shape + H, W = hw_shape + assert L == H * W, 'input feature has wrong size' + query = query.view(B, H, W, C) + + # pad feature maps to multiples of window size + pad_r = (self.window_size - W % self.window_size) % self.window_size + pad_b = (self.window_size - H % self.window_size) % self.window_size + query = F.pad(query, (0, 0, 0, pad_r, 0, pad_b)) + H_pad, W_pad = query.shape[1], query.shape[2] + + # cyclic shift + if self.shift_size > 0: + shifted_query = torch.roll( + query, + shifts=(-self.shift_size, -self.shift_size), + dims=(1, 2)) + + # calculate attention mask for SW-MSA + img_mask = torch.zeros((1, H_pad, W_pad, 1), device=query.device) + h_slices = (slice(0, -self.window_size), + slice(-self.window_size, + -self.shift_size), slice(-self.shift_size, None)) + w_slices = (slice(0, -self.window_size), + slice(-self.window_size, + -self.shift_size), slice(-self.shift_size, None)) + cnt = 0 + for h in h_slices: + for w in w_slices: + img_mask[:, h, w, :] = cnt + cnt += 1 + + # nW, window_size, window_size, 1 + mask_windows = self.window_partition(img_mask) + mask_windows = mask_windows.view( + -1, self.window_size * self.window_size) + attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) + attn_mask = attn_mask.masked_fill(attn_mask != 0, + float(-100.0)).masked_fill( + attn_mask == 0, float(0.0)) + else: + shifted_query = query + attn_mask = None + + # nW*B, window_size, window_size, C + query_windows = self.window_partition(shifted_query) + # nW*B, window_size*window_size, C + query_windows = query_windows.view(-1, self.window_size**2, C) + + # W-MSA/SW-MSA (nW*B, window_size*window_size, C) + attn_windows = self.w_msa(query_windows, mask=attn_mask) + + # merge windows + attn_windows = attn_windows.view(-1, self.window_size, + self.window_size, + self.w_msa.embed_dims) + + # B H' W' self.w_msa.embed_dims + shifted_x = self.window_reverse(attn_windows, H_pad, W_pad) + # reverse cyclic shift + if self.shift_size > 0: + x = torch.roll( + shifted_x, + shifts=(self.shift_size, self.shift_size), + dims=(1, 2)) + else: + x = shifted_x + + if pad_r > 0 or pad_b: + x = x[:, :H, :W, :].contiguous() + + x = x.view(B, H * W, self.w_msa.embed_dims) + + x = self.drop(x) + return x + + def window_reverse(self, windows, H, W): + """ + Args: + windows: (num_windows*B, window_size, window_size, C) + H (int): Height of image + W (int): Width of image + Returns: + x: (B, H, W, C) + """ + window_size = self.window_size + B = int(windows.shape[0] / (H * W / window_size / window_size)) + x = windows.view(B, H // window_size, W // window_size, window_size, + window_size, -1) + x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1) + return x + + def window_partition(self, x): + """ + Args: + x: (B, H, W, C) + Returns: + windows: (num_windows*B, window_size, window_size, C) + """ + B, H, W, C = x.shape + window_size = self.window_size + x = x.view(B, H // window_size, window_size, W // window_size, + window_size, C) + windows = x.permute(0, 1, 3, 2, 4, 5).contiguous() + windows = windows.view(-1, window_size, window_size, C) + return windows + + +class AGSwinBlock(BaseModule): + """Appearance Guidance Swin Transformer Block. + + Args: + embed_dims (int): The feature dimension. + appearance_dims (int): The appearance guidance dimension. + num_heads (int): Parallel attention heads. + mlp_ratios (int): The hidden dimension ratio w.r.t. embed_dims + for FFNs. + window_size (int, optional): The local window scale. + Default: 7. + shift (bool, optional): whether to shift window or not. + Default False. + qkv_bias (bool, optional): enable bias for qkv if True. + Default: True. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + drop_rate (float, optional): Dropout rate. Default: 0. + attn_drop_rate (float, optional): Attention dropout rate. + Default: 0. + drop_path_rate (float, optional): Stochastic depth rate. + Default: 0. + act_cfg (dict, optional): The config dict of activation function. + Default: dict(type='GELU'). + norm_cfg (dict, optional): The config dict of normalization. + Default: dict(type='LN'). + init_cfg (dict | list | None, optional): The init config. + Default: None. + """ + + def __init__(self, + embed_dims, + appearance_dims, + num_heads, + mlp_ratios=4, + window_size=7, + shift=False, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0., + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='LN'), + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.norm1 = build_norm_layer(norm_cfg, embed_dims)[1] + self.attn = AGShiftWindowMSA( + embed_dims=embed_dims, + appearance_dims=appearance_dims, + num_heads=num_heads, + window_size=window_size, + shift_size=window_size // 2 if shift else 0, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop_rate=attn_drop_rate, + proj_drop_rate=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + init_cfg=None) + + self.norm2 = build_norm_layer(norm_cfg, embed_dims)[1] + self.ffn = FFN( + embed_dims=embed_dims, + feedforward_channels=embed_dims * mlp_ratios, + num_fcs=2, + ffn_drop=drop_rate, + dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate), + act_cfg=act_cfg, + add_identity=True, + init_cfg=None) + + def forward(self, inputs, hw_shape): + """ + Args: + inputs (list[Tensor]): appearance_guidance (B, H, W, C); + x (B, L, C) + hw_shape (tuple[int]): shape of feature. + """ + x, appearance_guidance = inputs + B, L, C = x.shape + H, W = hw_shape + assert L == H * W, 'input feature has wrong size' + + identity = x + x = self.norm1(x) + + # appearance guidance + x = x.view(B, H, W, C) + if appearance_guidance is not None: + x = torch.cat([x, appearance_guidance], dim=-1).flatten(1, 2) + + x = self.attn(x, hw_shape) + + x = x + identity + + identity = x + x = self.norm2(x) + x = self.ffn(x, identity=identity) + + return x + + +@MODELS.register_module() +class SpatialAggregateLayer(BaseModule): + """Spatial aggregation layer of CAT-Seg. + + Args: + embed_dims (int): The feature dimension. + appearance_dims (int): The appearance guidance dimension. + num_heads (int): Parallel attention heads. + mlp_ratios (int): The hidden dimension ratio w.r.t. embed_dims + for FFNs. + window_size (int, optional): The local window scale. Default: 7. + qk_scale (float | None, optional): Override default qk scale of + head_dim ** -0.5 if set. Default: None. + init_cfg (dict | list | None, optional): The init config. + Default: None. + """ + + def __init__(self, + embed_dims, + appearance_dims, + num_heads, + mlp_ratios, + window_size=7, + qk_scale=None, + init_cfg=None): + super().__init__(init_cfg=init_cfg) + self.block_1 = AGSwinBlock( + embed_dims, + appearance_dims, + num_heads, + mlp_ratios, + window_size=window_size, + shift=False, + qk_scale=qk_scale) + self.block_2 = AGSwinBlock( + embed_dims, + appearance_dims, + num_heads, + mlp_ratios, + window_size=window_size, + shift=True, + qk_scale=qk_scale) + self.guidance_norm = nn.LayerNorm( + appearance_dims) if appearance_dims > 0 else None + + def forward(self, x, appearance_guidance): + """ + Args: + x (torch.Tensor): B C T H W. + appearance_guidance (torch.Tensor): B C H W. + """ + B, C, T, H, W = x.shape + x = x.permute(0, 2, 3, 4, 1).flatten(0, 1).flatten(1, 2) # BT, HW, C + if appearance_guidance is not None: + appearance_guidance = appearance_guidance.repeat( + T, 1, 1, 1).permute(0, 2, 3, 1) # BT, HW, C + appearance_guidance = self.guidance_norm(appearance_guidance) + else: + assert self.appearance_dims == 0 + x = self.block_1((x, appearance_guidance), (H, W)) + x = self.block_2((x, appearance_guidance), (H, W)) + x = x.transpose(1, 2).reshape(B, T, C, -1) + x = x.transpose(1, 2).reshape(B, C, T, H, W) + return x + + +class AttentionLayer(nn.Module): + """Attention layer for ClassAggregration of CAT-Seg. + + Source: https://github.com/KU-CVLAB/CAT-Seg/blob/main/cat_seg/modeling/transformer/model.py#L310 # noqa + """ + + def __init__(self, + hidden_dim, + guidance_dim, + nheads=8, + attention_type='linear'): + super().__init__() + self.nheads = nheads + self.q = nn.Linear(hidden_dim + guidance_dim, hidden_dim) + self.k = nn.Linear(hidden_dim + guidance_dim, hidden_dim) + self.v = nn.Linear(hidden_dim, hidden_dim) + + if attention_type == 'linear': + self.attention = LinearAttention() + elif attention_type == 'full': + self.attention = FullAttention() + else: + raise NotImplementedError + + def forward(self, x, guidance=None): + """ + Args: + x: B*H_p*W_p, T, C + guidance: B*H_p*W_p, T, C + """ + B, L, _ = x.shape + q = self.q(torch.cat([x, guidance], + dim=-1)) if guidance is not None else self.q(x) + k = self.k(torch.cat([x, guidance], + dim=-1)) if guidance is not None else self.k(x) + v = self.v(x) + + q = q.reshape(B, L, self.nheads, -1) + k = k.reshape(B, L, self.nheads, -1) + v = v.reshape(B, L, self.nheads, -1) + + out = self.attention(q, k, v) + out = out.reshape(B, L, -1) + return out + + +@MODELS.register_module() +class ClassAggregateLayer(BaseModule): + """Class aggregation layer of CAT-Seg. + + Args: + hidden_dims (int): The feature dimension. + guidance_dims (int): The appearance guidance dimension. + num_heads (int): Parallel attention heads. + attention_type (str): Type of attention layer. Default: 'linear'. + pooling_size (tuple[int] | list[int]): Pooling size. + init_cfg (dict | list | None, optional): The init config. + Default: None. + """ + + def __init__( + self, + hidden_dims=64, + guidance_dims=64, + num_heads=8, + attention_type='linear', + pooling_size=(4, 4), + init_cfg=None, + ): + super().__init__(init_cfg=init_cfg) + self.pool = nn.AvgPool2d(pooling_size) + self.attention = AttentionLayer( + hidden_dims, + guidance_dims, + nheads=num_heads, + attention_type=attention_type) + self.MLP = FFN( + embed_dims=hidden_dims, + feedforward_channels=hidden_dims * 4, + num_fcs=2) + self.norm1 = nn.LayerNorm(hidden_dims) + self.norm2 = nn.LayerNorm(hidden_dims) + + def pool_features(self, x): + """Intermediate pooling layer for computational efficiency. + + Args: + x: B, C, T, H, W + """ + B, C, T, H, W = x.shape + x = x.transpose(1, 2).reshape(-1, C, H, W) + x = self.pool(x) + *_, H_, W_ = x.shape + x = x.reshape(B, T, C, H_, W_).transpose(1, 2) + return x + + def forward(self, x, guidance): + """ + Args: + x: B, C, T, H, W + guidance: B, T, C + """ + B, C, T, H, W = x.size() + x_pool = self.pool_features(x) + *_, H_pool, W_pool = x_pool.size() + + x_pool = x_pool.permute(0, 3, 4, 2, 1).reshape(-1, T, C) + # B*H_p*W_p T C + if guidance is not None: + guidance = guidance.repeat(H_pool * W_pool, 1, 1) + + x_pool = x_pool + self.attention(self.norm1(x_pool), + guidance) # Attention + x_pool = x_pool + self.MLP(self.norm2(x_pool)) # MLP + + x_pool = x_pool.reshape(B, H_pool * W_pool, T, + C).permute(0, 2, 3, 1).reshape( + B, T, C, H_pool, + W_pool).flatten(0, 1) # BT C H_p W_p + x_pool = F.interpolate( + x_pool, size=(H, W), mode='bilinear', align_corners=True) + x_pool = x_pool.reshape(B, T, C, H, W).transpose(1, 2) # B C T H W + x = x + x_pool # Residual + + return x + + +@MODELS.register_module() +class AggregatorLayer(BaseModule): + """Single Aggregator Layer of CAT-Seg.""" + + def __init__(self, + embed_dims=64, + text_guidance_dims=512, + appearance_guidance_dims=512, + num_heads=4, + mlp_ratios=4, + window_size=7, + attention_type='linear', + pooling_size=(2, 2), + init_cfg=None) -> None: + super().__init__(init_cfg=init_cfg) + self.spatial_agg = SpatialAggregateLayer( + embed_dims, + appearance_guidance_dims, + num_heads=num_heads, + mlp_ratios=mlp_ratios, + window_size=window_size) + self.class_agg = ClassAggregateLayer( + embed_dims, + text_guidance_dims, + num_heads=num_heads, + attention_type=attention_type, + pooling_size=pooling_size) + + def forward(self, x, appearance_guidance, text_guidance): + """ + Args: + x: B C T H W + """ + x = self.spatial_agg(x, appearance_guidance) + x = self.class_agg(x, text_guidance) + return x + + +@MODELS.register_module() +class CATSegAggregator(BaseModule): + """CATSeg Aggregator. + + This Aggregator is the mmseg implementation of + `CAT-Seg `_. + + Args: + text_guidance_dim (int): Text guidance dimensions. Default: 512. + text_guidance_proj_dim (int): Text guidance projection dimensions. + Default: 128. + appearance_guidance_dim (int): Appearance guidance dimensions. + Default: 512. + appearance_guidance_proj_dim (int): Appearance guidance projection + dimensions. Default: 128. + num_layers (int): Aggregator layer number. Default: 4. + num_heads (int): Attention layer head number. Default: 4. + embed_dims (int): Input feature dimensions. Default: 128. + pooling_size (tuple | list): Pooling size of the class aggregator + layer. Default: (6, 6). + mlp_ratios (int): The hidden dimension ratio w.r.t. input dimension. + Default: 4. + window_size (int): Swin block window size. Default:12. + attention_type (str): Attention type of class aggregator layer. + Default:'linear'. + prompt_channel (int): Prompt channels. Default: 80. + """ + + def __init__(self, + text_guidance_dim=512, + text_guidance_proj_dim=128, + appearance_guidance_dim=512, + appearance_guidance_proj_dim=128, + num_layers=4, + num_heads=4, + embed_dims=128, + pooling_size=(6, 6), + mlp_ratios=4, + window_size=12, + attention_type='linear', + prompt_channel=80, + **kwargs): + super().__init__(**kwargs) + self.num_layers = num_layers + self.embed_dims = embed_dims + + self.layers = nn.ModuleList([ + AggregatorLayer( + embed_dims=embed_dims, + text_guidance_dims=text_guidance_proj_dim, + appearance_guidance_dims=appearance_guidance_proj_dim, + num_heads=num_heads, + mlp_ratios=mlp_ratios, + window_size=window_size, + attention_type=attention_type, + pooling_size=pooling_size) for _ in range(num_layers) + ]) + + self.conv1 = nn.Conv2d( + prompt_channel, embed_dims, kernel_size=7, stride=1, padding=3) + + self.guidance_projection = nn.Sequential( + nn.Conv2d( + appearance_guidance_dim, + appearance_guidance_proj_dim, + kernel_size=3, + stride=1, + padding=1), + nn.ReLU(), + ) if appearance_guidance_dim > 0 else None + + self.text_guidance_projection = nn.Sequential( + nn.Linear(text_guidance_dim, text_guidance_proj_dim), + nn.ReLU(), + ) if text_guidance_dim > 0 else None + + def feature_map(self, img_feats, text_feats): + """Concatenation type cost volume. + + For ablation study of cost volume type. + """ + img_feats = F.normalize(img_feats, dim=1) # B C H W + img_feats = img_feats.unsqueeze(2).repeat(1, 1, text_feats.shape[1], 1, + 1) + text_feats = F.normalize(text_feats, dim=-1) # B T P C + text_feats = text_feats.mean(dim=-2) + text_feats = F.normalize(text_feats, dim=-1) # B T C + text_feats = text_feats.unsqueeze(-1).unsqueeze(-1).repeat( + 1, 1, 1, img_feats.shape[-2], img_feats.shape[-1]).transpose(1, 2) + return torch.cat((img_feats, text_feats), dim=1) # B 2C T H W + + def correlation(self, img_feats, text_feats): + """Correlation of image features and text features.""" + img_feats = F.normalize(img_feats, dim=1) # B C H W + text_feats = F.normalize(text_feats, dim=-1) # B T P C + corr = torch.einsum('bchw, btpc -> bpthw', img_feats, text_feats) + return corr + + def corr_embed(self, x): + """Correlation embeddings encoding.""" + B = x.shape[0] + corr_embed = x.permute(0, 2, 1, 3, 4).flatten(0, 1) + corr_embed = self.conv1(corr_embed) + corr_embed = corr_embed.reshape(B, -1, self.embed_dims, x.shape[-2], + x.shape[-1]).transpose(1, 2) + return corr_embed + + def forward(self, inputs): + """ + Args: + inputs (dict): including the following keys, + 'appearance_feat': list[torch.Tensor], w.r.t. out_indices of + `self.feature_extractor`. + 'clip_text_feat': the text feature extracted by clip text + encoder. + 'clip_text_feat_test': the text feature extracted by clip text + encoder for testing. + 'clip_img_feat': the image feature extracted clip image + encoder. + """ + img_feats = inputs['clip_img_feat'] + B = img_feats.size(0) + appearance_guidance = inputs[ + 'appearance_feat'][::-1] # order (out_indices) 2, 1, 0 + text_feats = inputs['clip_text_feat'] if self.training else inputs[ + 'clip_text_feat_test'] + text_feats = text_feats.repeat(B, 1, 1, 1) + + corr = self.correlation(img_feats, text_feats) + # corr = self.feature_map(img_feats, text_feats) + corr_embed = self.corr_embed(corr) + + projected_guidance, projected_text_guidance = None, None + + if self.guidance_projection is not None: + projected_guidance = self.guidance_projection( + appearance_guidance[0]) + + if self.text_guidance_projection is not None: + text_feats = text_feats.mean(dim=-2) + text_feats = text_feats / text_feats.norm(dim=-1, keepdim=True) + projected_text_guidance = self.text_guidance_projection(text_feats) + + for layer in self.layers: + corr_embed = layer(corr_embed, projected_guidance, + projected_text_guidance) + + return dict( + corr_embed=corr_embed, appearance_feats=appearance_guidance[1:]) diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/cat_head.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/cat_head.py new file mode 100644 index 0000000..36bb1c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/cat_head.py @@ -0,0 +1,116 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule + +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.registry import MODELS + + +class UpBlock(nn.Module): + """Upsample Block with two consecutive convolution layers.""" + + def __init__(self, in_channels, out_channels, guidance_channels): + super().__init__() + self.up = nn.ConvTranspose2d( + in_channels, + in_channels - guidance_channels, + kernel_size=2, + stride=2) + self.conv1 = ConvModule( + in_channels, + out_channels, + 3, + padding=1, + bias=False, + norm_cfg=dict(type='GN', num_groups=out_channels // 16)) + self.conv2 = ConvModule( + out_channels, + out_channels, + 3, + padding=1, + bias=False, + norm_cfg=dict(type='GN', num_groups=out_channels // 16)) + + def forward(self, x, guidance=None): + """Forward function with visual guidance.""" + x = self.up(x) + if guidance is not None: + T = x.size(0) // guidance.size(0) + # guidance = repeat(guidance, "B C H W -> (B T) C H W", T=T) + guidance = guidance.repeat(T, 1, 1, 1) + x = torch.cat([x, guidance], dim=1) + x = self.conv1(x) + + return self.conv2(x) + + +@MODELS.register_module() +class CATSegHead(BaseDecodeHead): + """CATSeg Head. + + This segmentation head is the mmseg implementation of + `CAT-Seg `_. + + Args: + embed_dims (int): The number of input dimensions. + decoder_dims (list): The number of decoder dimensions. + decoder_guidance_proj_dims (list): The number of appearance + guidance dimensions. + init_cfg + """ + + def __init__(self, + embed_dims=128, + decoder_dims=(64, 32), + decoder_guidance_dims=(256, 128), + decoder_guidance_proj_dims=(32, 16), + **kwargs): + super().__init__(**kwargs) + self.decoder_guidance_projection = nn.ModuleList([ + nn.Sequential( + nn.Conv2d( + dec_dims, + dec_dims_proj, + kernel_size=3, + stride=1, + padding=1), + nn.ReLU(), + ) for dec_dims, dec_dims_proj in zip(decoder_guidance_dims, + decoder_guidance_proj_dims) + ]) if decoder_guidance_dims[0] > 0 else None + + self.decoder1 = UpBlock(embed_dims, decoder_dims[0], + decoder_guidance_proj_dims[0]) + self.decoder2 = UpBlock(decoder_dims[0], decoder_dims[1], + decoder_guidance_proj_dims[1]) + self.conv_seg = nn.Conv2d( + decoder_dims[1], 1, kernel_size=3, stride=1, padding=1) + + def forward(self, inputs): + """Forward function. + + Args: + inputs (dict): Input features including the following features, + corr_embed: aggregated correlation embeddings. + appearance_feats: decoder appearance feature guidance. + """ + # decoder guidance projection + if self.decoder_guidance_projection is not None: + projected_decoder_guidance = [ + proj(g) for proj, g in zip(self.decoder_guidance_projection, + inputs['appearance_feats']) + ] + + # decoder layers + B = inputs['corr_embed'].size(0) + corr_embed = inputs['corr_embed'].transpose(1, 2).flatten(0, 1) + corr_embed = self.decoder1(corr_embed, projected_decoder_guidance[0]) + corr_embed = self.decoder2(corr_embed, projected_decoder_guidance[1]) + + output = self.cls_seg(corr_embed) + + # rearrange the output to (B, T, H, W) + H_ori, W_ori = output.shape[-2:] + output = output.reshape(B, -1, H_ori, W_ori) + return output diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/clip_ovseg.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/clip_ovseg.py new file mode 100644 index 0000000..cb67744 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/models/clip_ovseg.py @@ -0,0 +1,293 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import json +import os +from typing import List + +import torch +import torch.nn.functional as F +from huggingface_hub.utils._errors import LocalEntryNotFoundError +from mmengine.model import BaseModule + +from mmseg.registry import MODELS +from mmseg.utils import ConfigType +from ..utils import clip_wrapper +from ..utils.clip_templates import (IMAGENET_TEMPLATES, + IMAGENET_TEMPLATES_SELECT) + + +@MODELS.register_module() +class CLIPOVCATSeg(BaseModule): + """CLIP based Open Vocabulary CAT-Seg model backbone. + + This backbone is the modified implementation of `CAT-Seg Backbone + `_. It combines the CLIP model and + another feature extractor, a.k.a the appearance guidance extractor + in the original `CAT-Seg`. + + Args: + feature_extractor (ConfigType): Appearance guidance extractor + config dict. + train_class_json (str): The training class json file. + test_class_json (str): The path to test class json file. + clip_pretrained (str): The pre-trained clip type. + clip_finetune (str): The finetuning settings of clip model. + custom_clip_weights (str): The custmized clip weights directory. When + encountering huggingface model download errors, you can manually + download the pretrained weights. + backbone_multiplier (float): The learning rate multiplier. + Default: 0.01. + prompt_depth (int): The prompt depth. Default: 0. + prompt_length (int): The prompt length. Default: 0. + prompt_ensemble_type (str): The prompt ensemble type. + Default: "imagenet". + pixel_mean (List[float]): The pixel mean for feature extractor. + pxiel_std (List[float]): The pixel std for feature extractor. + clip_pixel_mean (List[float]): The pixel mean for clip model. + clip_pxiel_std (List[float]): The pixel std for clip model. + clip_img_feat_size: (List[int]: Clip image embedding size from + image encoder. + init_cfg (dict or list[dict], optional): Initialization config dict. + Default: None. + """ + + def __init__( + self, + feature_extractor: ConfigType, + train_class_json: str, + test_class_json: str, + clip_pretrained: str, + clip_finetune: str, + custom_clip_weights: str = None, + backbone_multiplier=0.01, + prompt_depth: int = 0, + prompt_length: int = 0, + prompt_ensemble_type: str = 'imagenet', + pixel_mean: List[float] = [123.675, 116.280, 103.530], + pixel_std: List[float] = [58.395, 57.120, 57.375], + clip_pixel_mean: List[float] = [ + 122.7709383, 116.7460125, 104.09373615 + ], + clip_pixel_std: List[float] = [68.5005327, 66.6321579, 70.3231630], + clip_img_feat_size: List[int] = [24, 24], + init_cfg=None): + super().__init__(init_cfg=init_cfg) + # normalization parameters + self.register_buffer('pixel_mean', + torch.Tensor(pixel_mean).view(1, -1, 1, 1), False) + self.register_buffer('pixel_std', + torch.Tensor(pixel_std).view(1, -1, 1, 1), False) + self.register_buffer('clip_pixel_mean', + torch.Tensor(clip_pixel_mean).view(1, -1, 1, 1), + False) + self.register_buffer('clip_pixel_std', + torch.Tensor(clip_pixel_std).view(1, -1, 1, 1), + False) + self.clip_resolution = ( + 384, 384) if clip_pretrained == 'ViT-B/16' else (336, 336) + # modified clip image encoder with fixed size dense output + self.clip_img_feat_size = clip_img_feat_size + + # prepare clip templates + self.prompt_ensemble_type = prompt_ensemble_type + if self.prompt_ensemble_type == 'imagenet_select': + prompt_templates = IMAGENET_TEMPLATES_SELECT + elif self.prompt_ensemble_type == 'imagenet': + prompt_templates = IMAGENET_TEMPLATES + elif self.prompt_ensemble_type == 'single': + prompt_templates = [ + 'A photo of a {} in the scene', + ] + else: + raise NotImplementedError + self.prompt_templates = prompt_templates + + # build the feature extractor + self.feature_extractor = MODELS.build(feature_extractor) + + # build CLIP model + with open(train_class_json) as f_in: + self.class_texts = json.load(f_in) + with open(test_class_json) as f_in: + self.test_class_texts = json.load(f_in) + assert self.class_texts is not None + if self.test_class_texts is None: + self.test_class_texts = self.class_texts + device = 'cuda' if torch.cuda.is_available() else 'cpu' + self.tokenizer = None + if clip_pretrained == 'ViT-G' or clip_pretrained == 'ViT-H': + # for OpenCLIP models + import open_clip + name, pretrain = ( + 'ViT-H-14', + 'laion2b_s32b_b79k') if clip_pretrained == 'ViT-H' else ( + 'ViT-bigG-14', 'laion2b_s39b_b160k') + try: + open_clip_model = open_clip.create_model_and_transforms( + name, + pretrained=pretrain, + device=device, + force_image_size=336, + ) + clip_model, _, clip_preprocess = open_clip_model + except ConnectionError or LocalEntryNotFoundError as e: + print(f'Has {e} when loading weights from huggingface!') + print( + f'Will load {pretrain} weights from {custom_clip_weights}.' + ) + assert custom_clip_weights is not None, 'Please specify custom weights directory.' # noqa + assert os.path.exists( + os.path.join(custom_clip_weights, + 'open_clip_pytorch_model.bin') + ), 'Please provide a valid directory for manually downloaded model.' # noqa + open_clip_model = open_clip.create_model_and_transforms( + name, + pretrained=None, + device='cpu', + force_image_size=336, + ) + clip_model, _, clip_preprocess = open_clip_model + + open_clip.load_checkpoint( + clip_model, + os.path.expanduser( + os.path.join(custom_clip_weights, + 'open_clip_pytorch_model.bin'))) + clip_model.to(torch.device(device)) + + self.tokenizer = open_clip.get_tokenizer(name) + else: + # for OpenAI models + clip_model, clip_preprocess = clip_wrapper.load( + clip_pretrained, + device=device, + jit=False, + prompt_depth=prompt_depth, + prompt_length=prompt_length) + + # pre-encode classes text prompts + text_features = self.class_embeddings(self.class_texts, + prompt_templates, clip_model, + device).permute(1, 0, 2).float() + text_features_test = self.class_embeddings(self.test_class_texts, + prompt_templates, + clip_model, + device).permute(1, 0, + 2).float() + self.register_buffer('text_features', text_features, False) + self.register_buffer('text_features_test', text_features_test, False) + + # prepare CLIP model finetune + self.clip_finetune = clip_finetune + self.clip_model = clip_model.float() + self.clip_preprocess = clip_preprocess + + for name, params in self.clip_model.named_parameters(): + if 'visual' in name: + if clip_finetune == 'prompt': + params.requires_grad = True if 'prompt' in name else False + elif clip_finetune == 'attention': + if 'attn' in name or 'position' in name: + params.requires_grad = True + else: + params.requires_grad = False + elif clip_finetune == 'full': + params.requires_grad = True + else: + params.requires_grad = False + else: + params.requires_grad = False + + finetune_backbone = backbone_multiplier > 0. + for name, params in self.feature_extractor.named_parameters(): + if 'norm0' in name: + params.requires_grad = False + else: + params.requires_grad = finetune_backbone + + @torch.no_grad() + def class_embeddings(self, + classnames, + templates, + clip_model, + device='cpu'): + """Convert class names to text embeddings by clip model. + + Args: + classnames (list): loaded from json file. + templates (dict): text template. + clip_model (nn.Module): prepared clip model. + device (str | torch.device): loading device of text + encoder results. + """ + zeroshot_weights = [] + for classname in classnames: + if ', ' in classname: + classname_splits = classname.split(', ') + texts = [] + for template in templates: + for cls_split in classname_splits: + texts.append(template.format(cls_split)) + else: + texts = [template.format(classname) + for template in templates] # format with class + if self.tokenizer is not None: + texts = self.tokenizer(texts).to(device) + else: + texts = clip_wrapper.tokenize(texts).to(device) + class_embeddings = clip_model.encode_text(texts) + class_embeddings /= class_embeddings.norm(dim=-1, keepdim=True) + if len(templates) != class_embeddings.shape[0]: + class_embeddings = class_embeddings.reshape( + len(templates), -1, class_embeddings.shape[-1]).mean(dim=1) + class_embeddings /= class_embeddings.norm(dim=-1, keepdim=True) + class_embedding = class_embeddings + zeroshot_weights.append(class_embedding) + zeroshot_weights = torch.stack(zeroshot_weights, dim=1).to(device) + return zeroshot_weights + + def custom_normalize(self, inputs): + """Input normalization for clip model and feature extractor + respectively. + + Args: + inputs: batched input images. + """ + # clip images + batched_clip = (inputs - self.clip_pixel_mean) / self.clip_pixel_std + batched_clip = F.interpolate( + batched_clip, + size=self.clip_resolution, + mode='bilinear', + align_corners=False) + # feature extractor images + batched = (inputs - self.pixel_mean) / self.pixel_std + return batched, batched_clip + + def forward(self, inputs): + """ + Args: + inputs: minibatch image. (B, 3, H, W) + Returns: + outputs (dict): + 'appearance_feat': list[torch.Tensor], w.r.t. out_indices of + `self.feature_extractor`. + 'clip_text_feat': the text feature extracted by clip text encoder. + 'clip_text_feat_test': the text feature extracted by clip text + encoder for testing. + 'clip_img_feat': the image feature extracted clip image encoder. + """ + inputs, clip_inputs = self.custom_normalize(inputs) + outputs = dict() + # extract appearance guidance feature + outputs['appearance_feat'] = self.feature_extractor(inputs) + + # extract clip features + outputs['clip_text_feat'] = self.text_features + outputs['clip_text_feat_test'] = self.text_features_test + clip_features = self.clip_model.encode_image( + clip_inputs, dense=True) # B, 577(24x24+1), C + B = clip_features.size(0) + outputs['clip_img_feat'] = clip_features[:, 1:, :].permute( + 0, 2, 1).reshape(B, -1, *self.clip_img_feat_size) + + return outputs diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/__init__.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/__init__.py new file mode 100644 index 0000000..88746b2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .clip_templates import (IMAGENET_TEMPLATES, IMAGENET_TEMPLATES_SELECT, + IMAGENET_TEMPLATES_SELECT_CLIP, ViLD_templates) +from .self_attention_block import FullAttention, LinearAttention + +__all__ = [ + 'FullAttention', 'LinearAttention', 'IMAGENET_TEMPLATES', + 'IMAGENET_TEMPLATES_SELECT', 'IMAGENET_TEMPLATES_SELECT_CLIP', + 'ViLD_templates' +] diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/bpe_vocab/bpe_simple_vocab_16e6.txt.gz b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/bpe_vocab/bpe_simple_vocab_16e6.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..7b5088a527f720063f044eb928eee315f63b2fc0 GIT binary patch literal 1356917 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%0D-8anuzg!0hrX~L}M*$l~SOpZ7=I+%ag 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

Rp5@H-63rPkl?5M!`%e$q5M^(XmdeOwiW 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%0D+{l&4Elf+RUJ>DPlT<0vI!uxp-LitG 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

nj;X-C@gua07)qPBmS6kxKbsrp_1VbHQ%kr&^ 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�J#6HekB- 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%0DQu49$GwTP{0?>4l_B{}_v8!N|W%zC&9> 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

brXZX@VYhc!=SFxwhp z35L9L)9~j~4N&#DdIB@B$p?;5#`+0waAk#qv}U0S9KqM*qre1sP(IG#r1_ynOMv9G zQpHjXDzn4dA{|PMnMQz^P2vO(iyi?HZCbYzM&_2v+yT%sBw&gO7>0CXNkNktaHbU7 zP+Xvg7gqvrTJUe5wmuWsCQ<#2)H_y>)iGU|fMtAAyI1~XwUC53jlyIFzh1O8ZPglV zhgIE#J~QzFUB@|6l27GL434ICM z)?xP`e<3Gj$2wirrgnPPH32~M{g7I^KMD9tq}-55E!w`E+b$~%%nk!H5O$q|9|R{s z4^N^6=_QEYNYc15>_VoKP7#PdEye&$)O6Wb4sTc^a83?8vIe0I4RP_OKz8yJh^tJK zJ465iAs28PdD7gX8YShZT12==VhCn^b@Y?jLSIY3%c#w$V>O2<6~rWAOJgqzbnJ70 z|IE+@e+l1y;qvv5-v09T=VT8k$G>_1)z`3m=Zx>u_pjc*?RfgHZ@&s}zq!0Z!EWM| zRlDwZdYPj>)w3sqrJuf8z(uiB8nHVTY*o*J_}h3T;dPn?$$b; zMFJ(iCbjDo!^TqWLGu7Y^0!qY=cr9NqNQ{sWyP`LzA$V+f;a;PGSq=ar43? zJpoKm#Yzj+?Ib?G$~SE!EP?hZzhz(8jbHY=dQc5g=~FaV{=nJ3s<>c^c&gA{LlmuP zKUoSWWi1>jD}BrcNfBqzO1z$eAo(yYbL;%Q84aAX2Qq~EYO`IEz;RM=sh<)l8Gyor z;+#?q#fA(r*@)N6@b&lJeh?On`Fcr_ja_^MQe-j30%MkvcrXV|hb1aNHu7DK0dU`EX74AqQ2U zktOd-c_$RJgWX0Zx}7@rg^r9YUM*4olQSfx7qixW4wii@ zmRj;yNTy&?7!yxwW3++B+8ppR&UTf-9oGjtqQk1i-Jp}sMAcCw*i0U#6ngVzZdlyz36C`KLi zF_ru^ouvXYwcT(M7PiXzz!BS6!M@KimQ$`jk;%9hcM|RmcpBM2mzSg{=yG~i`FMSQrWG5s}Tcr|2<9heUP3*lz8PZhwZA=pc&XEX}^BFjWQuWiI+iVJM6&D?xl*&ok!S(+j|Sl|ol9t*Ce5D~_R_HBttoc`)^blm zQR!>RWIR`hEg)qCT$GS2?=wh1u6XIiA6mN^K`=vY@1nEMl4CzGQZJC$4GF_&@lejG zWQVQ)Qb^K?#FT?IBxX&}mSlQikqr;B<93FtV_yLrC}z73XsftzORpwt)PP3i_RT+i z{p0tap=SH+{YT8crqEiJa6pjOE;CRXA*n?r&)90rMvM{>RTspWQV@?adCg7Ja1cUO zWK(u3zmc1AS}kKjT0~IFrs-u)fi}$eovEhF`J9KZFsITj8*(gF-;VD0$#B$z)2>Knr^kfI`1(L^H>CbtBdVVG*il z4+$k*x9yg96fG_>BiAe}&I@%kfe|8KPM#hD+V@bK>fmiEkOF%o zyvf(7!S^&gCz@Obnss}DRa^9w&Zbi%88$a~m$r^Ou&BH7y1LuK|`Pe2OLAZ%0on! zrBpWox`dZn(WNQoVOO&>tPSPNz$k$=wrHX?Io?6*D^GwBiJ^heZ9aDZ)h#lBsR#i( z+X`PuiFWanO*s;YF4vUqnXp{m!OG=bgPXBb$gez^sm^&Qr>-ZyMuET9uaY!g!UsOl zE_F~JFU|=8w*sQnPI8k0n(9(wOZo=Y=JZkl;SXX**RmK@ZRZ-tD`D8?3iuNj73QM}nZ*58e6 zd}C+Km^vxYSUAd9N0s+Q%wtPzhZ9wfP>r)qt`NPZxDBQRuLRg5K*C6mE+#dNeWbQ@ z@j~OwIP7XydhzB=LVX?;m|{Ul)rRHT-lxUqq3RFm@Tz3C!R%`YTjXO?>@X{0Jep{S zTI8x;V}iAn(AN&{ze>lSzW+YF|M~=OfF~Y4CC`M{ZdP+(WfQl7H@DENBL2)V- z;zuVdSU57dO57zLo88K4{aB#9IX+lB2n3gSa3o#2gO19ARB{+ub+WDxhlj06h|7)x z^>7yjCR3VsL7j@(+qQ3R`GTE~P7cs?$A@N${DVJ)R?Vsu%aa!Ov}(bN10d)hY(Gtp z?vTxL&D#xxUV#tlRv-`4OKS-aMc9N**0NEN@TRqB;Qjw`>ejQV@p$tFdW}YEUC&0B z8H*n3RS>gNG`2dQ%7J{nC?M+L z3%!_%A{@Q-vRyR~`bf6+k=VPk93IKZ=6Yb+fr1iWvSGVKZ=$M{td56%aHoTb^y3P%S9oSjhz?KL%^6tEf~Uk4yF791az5P`Ha# zcan4g70pv1^A>*-(<{t^?u=h@eVj0Xq=Q8(rr1zz&{jIAT)RauxdWvVisk?Ohy5X2 zU+v{~KkZ%Gt0=$uP^%11vd1QNZ`E$LFl6sLs( z{EI~!i3M44y+H%>uyzz^9cFcec9hXFh|;QTF4V+aUSG2Y;59z)nfxc0$&_5La?{2n zmvpJ+-@(=15ep@Yn+o+FtD>sYnNyZ)Lzk(}A}P=?zf$ZMtdq`z7NGY9AdVKdYUps6 zNiz|x2%4Lf@hQ=$l1p4F^^AkrQk%6qnZNLy=!m2(jx)I1EX%@Fz#GlJ12!QR?%-&$ zGwZ329KliZ4kqae#CRR7`&g(F7clliw=Ax(loZvV2!R~eBH-%cj^>`7jz{ka$Np>{ zAF;5(5g&=SBm#SH}&8Z81^6ph$NmHv;xiQgn=6}UF#3WFV7v^|y zqM@or!t2`JE2HHVojT-4libZODwq|*oumq2Jm#2qMotCHkdd4c1J?7?O&wX6SXm%70QzN+P(tep~5 z7CwKi&L>Ne;Ur8l*maQLXJ*40_4-ZexaP>FEF{H`?|T670W0r22y`9F95=&#!oEe8n?B9n zeOPo?XMF={|IHOjFw5H3L=N-LE}~k`EM8gt-hKe13}Unxw31=8A`zuM58l&wdfg;7$kGhTQJU0VP9DrUavp*|8#CiP`WmA0@3w={2ThH~#IzwU zI(b}}HvqS=cI;_L=;w%k4qo9XR}uR4R=|XM;1l91->yG5Mr)kI36zZv-TYalph;q- z$RyEFq6hw@N)?sT%epk^`)%O^+md=HXq2ce5TKVejp@lu%O6x}TS0e3DS{}$J==l9 zv^x&fCj&mF;VGG{voBHY0LV3wYNb+-z6NM#dGR2`mH@C;6so(YCfdUKOfVHffR=HBuBLpnWs~3V=P!e}yX^KVP{Nu@L>HUjf-5ElX z5|{Ste|`HhnAhdk|MLEMkPn7iL~YjhhFCW;%0XLTw526|e8Lh@olc+|GFerzuYrpS zA~_XbG{!En?v7Fboy{Jw;tRT%FY?VLSzmA}DA{h<+9l*hZ?QKCVq`6bt#*TS*=!s9 zUrn*y!f9;ot+#IAz+n}ej>^2>RG!2NpbOJ&dCjd6k2S4FR>|cpK?+yvRmvh@TgN3) zs52h?&0iS?4k?3e>=2< zYboFg(4z=$0-_68xC{$|>iijVDc9%7WgM<7l&vk$A{X#6a#fdrm{b(V1zNV^@y#A0 z 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

B0o?DtF!VT!YjrDC^S2?RV?P>**rUM?tSk)et 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 literal 0 HcmV?d00001 diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_model.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_model.py new file mode 100644 index 0000000..977444f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_model.py @@ -0,0 +1,651 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from collections import OrderedDict +from typing import Tuple, Union + +import torch +import torch.nn.functional as F +from torch import nn + + +class Bottleneck(nn.Module): + """Custom implementation of Bottleneck in ResNet.""" + expansion = 4 + + def __init__(self, inplanes, planes, stride=1): + super().__init__() + # all conv layers have stride 1. + # an avgpool is performed after the second convolution when stride > 1 + self.conv1 = nn.Conv2d(inplanes, planes, 1, bias=False) + self.bn1 = nn.BatchNorm2d(planes) + + self.conv2 = nn.Conv2d(planes, planes, 3, padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(planes) + + self.avgpool = nn.AvgPool2d(stride) if stride > 1 else nn.Identity() + + self.conv3 = nn.Conv2d(planes, planes * self.expansion, 1, bias=False) + self.bn3 = nn.BatchNorm2d(planes * self.expansion) + + self.relu = nn.ReLU(inplace=True) + self.downsample = None + self.stride = stride + + if stride > 1 or inplanes != planes * Bottleneck.expansion: + # downsampling layer is prepended with an avgpool, + # and the subsequent convolution has stride 1 + self.downsample = nn.Sequential( + OrderedDict([('-1', nn.AvgPool2d(stride)), + ('0', + nn.Conv2d( + inplanes, + planes * self.expansion, + 1, + stride=1, + bias=False)), + ('1', nn.BatchNorm2d(planes * self.expansion))])) + + def forward(self, x: torch.Tensor): + """ + Args: + x (torch.Tensor): the input feature. + """ + identity = x + + out = self.relu(self.bn1(self.conv1(x))) + out = self.relu(self.bn2(self.conv2(out))) + out = self.avgpool(out) + out = self.bn3(self.conv3(out)) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + return out + + +class AttentionPool2d(nn.Module): + """Attention Pool2d.""" + + def __init__(self, + spacial_dim: int, + embed_dim: int, + num_heads: int, + output_dim: int = None): + super().__init__() + self.positional_embedding = nn.Parameter( + torch.randn(spacial_dim**2 + 1, embed_dim) / embed_dim**0.5) + self.k_proj = nn.Linear(embed_dim, embed_dim) + self.q_proj = nn.Linear(embed_dim, embed_dim) + self.v_proj = nn.Linear(embed_dim, embed_dim) + self.c_proj = nn.Linear(embed_dim, output_dim or embed_dim) + self.num_heads = num_heads + + def forward(self, x): + """ + Args: + x (torch.Tensor): the input feature. + """ + x = x.flatten(start_dim=2).permute(2, 0, 1) # NCHW -> (HW)NC + x = torch.cat([x.mean(dim=0, keepdim=True), x], dim=0) # (HW+1)NC + x = x + self.positional_embedding[:, None, :].to(x.dtype) # (HW+1)NC + x, _ = F.multi_head_attention_forward( + query=x[:1], + key=x, + value=x, + embed_dim_to_check=x.shape[-1], + num_heads=self.num_heads, + q_proj_weight=self.q_proj.weight, + k_proj_weight=self.k_proj.weight, + v_proj_weight=self.v_proj.weight, + in_proj_weight=None, + in_proj_bias=torch.cat( + [self.q_proj.bias, self.k_proj.bias, self.v_proj.bias]), + bias_k=None, + bias_v=None, + add_zero_attn=False, + dropout_p=0, + out_proj_weight=self.c_proj.weight, + out_proj_bias=self.c_proj.bias, + use_separate_proj_weight=True, + training=self.training, + need_weights=False) + return x.squeeze(0) + + +class ModifiedResNet(nn.Module): + """A ResNet class that is similar to torchvision's but contains the + following changes: + + - There are now 3 "stem" convolutions as opposed to 1, with an average + pool instead of a max pool. + - Performs anti-aliasing strided convolutions, where an avgpool is + prepended to convolutions with stride > 1 + - The final pooling layer is a QKV attention instead of an average pool + """ + + def __init__(self, + layers, + output_dim, + heads, + input_resolution=224, + width=64): + super().__init__() + self.output_dim = output_dim + self.input_resolution = input_resolution + + # the 3-layer stem + self.conv1 = nn.Conv2d( + 3, width // 2, kernel_size=3, stride=2, padding=1, bias=False) + self.bn1 = nn.BatchNorm2d(width // 2) + self.relu1 = nn.ReLU(inplace=True) + self.conv2 = nn.Conv2d( + width // 2, width // 2, kernel_size=3, padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(width // 2) + self.relu2 = nn.ReLU(inplace=True) + self.conv3 = nn.Conv2d( + width // 2, width, kernel_size=3, padding=1, bias=False) + self.bn3 = nn.BatchNorm2d(width) + self.relu3 = nn.ReLU(inplace=True) + self.avgpool = nn.AvgPool2d(2) + + # residual layers + # this is a *mutable* variable used during construction + self._inplanes = width + self.layer1 = self._make_layer(width, layers[0]) + self.layer2 = self._make_layer(width * 2, layers[1], stride=2) + self.layer3 = self._make_layer(width * 4, layers[2], stride=2) + self.layer4 = self._make_layer(width * 8, layers[3], stride=2) + + embed_dim = width * 32 # the ResNet feature dimension + self.attnpool = AttentionPool2d(input_resolution // 32, embed_dim, + heads, output_dim) + + def _make_layer(self, planes, blocks, stride=1): + """Build resnet layers.""" + layers = [Bottleneck(self._inplanes, planes, stride)] + + self._inplanes = planes * Bottleneck.expansion + for _ in range(1, blocks): + layers.append(Bottleneck(self._inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + """ + Args: + x (torch.Tensor): the input mini-batch images. + """ + + def stem(x): + x = self.relu1(self.bn1(self.conv1(x))) + x = self.relu2(self.bn2(self.conv2(x))) + x = self.relu3(self.bn3(self.conv3(x))) + x = self.avgpool(x) + return x + + x = x.type(self.conv1.weight.dtype) + x = stem(x) + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + x = self.attnpool(x) + + return x + + +class LayerNorm(nn.LayerNorm): + """Subclass torch's LayerNorm to handle fp16.""" + + def forward(self, x: torch.Tensor): + """ + Args: + x (torch.Tensor): the input feature. + """ + orig_type = x.dtype + ret = super().forward(x.type(torch.float32)) + return ret.type(orig_type) + + +class QuickGELU(nn.Module): + """Wrapper of GELU activation layer.""" + + def forward(self, x: torch.Tensor): + """ + Args: + x (torch.Tensor): the input feature. + """ + return x * torch.sigmoid(1.702 * x) + + +class ResidualAttentionBlock(nn.Module): + """Attention block with residual connection.""" + + def __init__(self, + d_model: int, + n_head: int, + attn_mask: torch.Tensor = None): + super().__init__() + + self.attn = nn.MultiheadAttention(d_model, n_head) + self.ln_1 = LayerNorm(d_model) + self.mlp = nn.Sequential( + OrderedDict([('c_fc', nn.Linear(d_model, d_model * 4)), + ('gelu', QuickGELU()), + ('c_proj', nn.Linear(d_model * 4, d_model))])) + self.ln_2 = LayerNorm(d_model) + self.attn_mask = attn_mask + self.mask_pre_mlp = True + + def attention(self, x: torch.Tensor): + """Calculate mask multi-head-attention.""" + self.attn_mask = self.attn_mask.to( + dtype=x.dtype, + device=x.device) if self.attn_mask is not None else None + return self.attn( + x, x, x, need_weights=False, attn_mask=self.attn_mask)[0] + + def forward(self, x: torch.Tensor): + """ + Args: + x (torch.Tensor): the input feature. + """ + x = x + self.attention(self.ln_1(x)) + x = x + self.mlp(self.ln_2(x)) + return x + + def forward_dense(self, x: torch.Tensor): + """Reinplementation of forward function for dense prediction of image + encoder in CLIP model. + + Args: + x (torch.Tensor): the input feature. + """ + y = self.ln_1(x) + y = F.linear(y, self.attn.in_proj_weight, self.attn.in_proj_bias) + L, N, D = y.shape # L N 3D + + y = y.reshape(L, N, 3, D // 3).permute(2, 1, 0, + 3).reshape(3 * N, L, D // 3) + y = F.linear(y, self.attn.out_proj.weight, self.attn.out_proj.bias) + + q, k, v = y.tensor_split(3, dim=0) + v = v.transpose(1, 0) + x # L N D + + v = v + self.mlp(self.ln_2(v)) + return v + + +class Transformer(nn.Module): + """General Transformer Architecture for both image and text encoder.""" + + def __init__(self, + width: int, + layers: int, + heads: int, + attn_mask: torch.Tensor = None, + prompt_length=0, + prompt_depth=0): + super().__init__() + self.width = width + self.layers = layers + self.resblocks = nn.Sequential(*[ + ResidualAttentionBlock(width, heads, attn_mask) + for _ in range(layers) + ]) + + self.prompt_length = prompt_length + self.prompt_depth = prompt_depth + self.prompt_tokens = nn.Parameter( + torch.zeros(prompt_depth, prompt_length, + width)) if prompt_length > 0 else None + if self.prompt_tokens is not None: + nn.init.xavier_uniform_(self.prompt_tokens) + + def forward(self, x: torch.Tensor, dense=False): + """ + Args: + x (torch.Tensor): input features. + dense (bool): whether use reimplemented dense forward + function in the last layer. + """ + for i, resblock in enumerate(self.resblocks): + if self.prompt_length > 0 and i < self.prompt_depth: + length = self.prompt_length + 1 if i > 0 else 1 + x = torch.cat((x[0:1, :, :], self.prompt_tokens[i].repeat( + x.shape[1], 1, 1).permute(1, 0, 2), x[length:, :, :])) + + if i == self.layers - 1 and dense: + x = resblock.forward_dense(x) + x = torch.cat((x[0:1, :, :], x[self.prompt_length + 1::, :]), + dim=0) + else: + x = resblock(x) + + return x + + +class VisualTransformer(nn.Module): + """Visual encoder for CLIP model.""" + + def __init__(self, input_resolution: int, patch_size: int, width: int, + layers: int, heads: int, output_dim: int, prompt_depth: int, + prompt_length: int): + super().__init__() + self.output_dim = output_dim + self.conv1 = nn.Conv2d( + in_channels=3, + out_channels=width, + kernel_size=patch_size, + stride=patch_size, + bias=False) + + scale = width**-0.5 + self.class_embedding = nn.Parameter(scale * torch.randn(width)) + self.positional_embedding = nn.Parameter(scale * torch.randn( + (input_resolution // patch_size)**2 + 1, width)) + self.ln_pre = LayerNorm(width) + + self.transformer = Transformer( + width, + layers, + heads, + prompt_depth=prompt_depth, + prompt_length=prompt_length) + + self.ln_post = LayerNorm(width) + self.proj = nn.Parameter(scale * torch.randn(width, output_dim)) + + self.patch_size = patch_size + self.input_resolution = input_resolution + + def forward(self, x: torch.Tensor, dense=False): + """ + Args: + x (torch.Tensor): input features. + dense (bool): whether use reimplemented dense forward + function in the last layer. + """ + x = self.conv1(x) # shape = [*, width, grid, grid] + x = x.reshape(x.shape[0], x.shape[1], + -1) # shape = [*, width, grid ** 2] + x = x.permute(0, 2, 1) # shape = [*, grid ** 2, width] + x = torch.cat([ + self.class_embedding.to(x.dtype) + torch.zeros( + x.shape[0], 1, x.shape[-1], dtype=x.dtype, device=x.device), x + ], + dim=1) # shape = [*, grid ** 2 + 1, width] + + if dense and (x.shape[1] != self.positional_embedding.shape[0]): + x = x + self.resized_pos_embed(self.input_resolution, + x.shape[1]).to(x.dtype) + else: + x = x + self.positional_embedding.to(x.dtype) + + x = self.ln_pre(x) + + x = x.permute(1, 0, 2) # NLD -> LND + x = self.transformer(x, dense) + x = x.permute(1, 0, 2) # LND -> NLD + + if dense: + x = self.ln_post(x[:, :, :]) + else: + x = self.ln_post(x[:, 0, :]) + + if self.proj is not None: + x = x @ self.proj + + return x + + def resized_pos_embed(self, in_res, tgt_res, mode='bicubic'): + """Resize the position embedding.""" + # assert L == (input_resolution // self.patch_size) ** 2 + 1 + L, D = self.positional_embedding.shape + + in_side = in_res // self.patch_size + # tgt_side = tgt_res // self.patch_size + tgt_side = int((tgt_res - 1)**0.5) + + cls_pos = self.positional_embedding[0].unsqueeze(0) # 1 D + pos_embed = self.positional_embedding[1:].reshape( + 1, in_side, in_side, D).permute(0, 3, 1, 2) # L-1 D -> 1 D S S + resized_pos_embed = F.interpolate( + pos_embed, + size=(tgt_side, tgt_side), + mode=mode, + align_corners=False, + ) # 1 D S S -> 1 D S' S' + resized_pos_embed = resized_pos_embed.squeeze(0).reshape( + D, -1).T # L'-1 D + + return torch.cat((cls_pos, resized_pos_embed), dim=0) + + +class CLIP(nn.Module): + """Custom implementation of CLIP model. + + Refer to: https://github.com/openai/CLIP + """ + + def __init__( + self, + embed_dim: int, + # vision + image_resolution: int, + vision_layers: Union[Tuple[int, int, int, int], int], + vision_width: int, + vision_patch_size: int, + # text + context_length: int, + vocab_size: int, + transformer_width: int, + transformer_heads: int, + transformer_layers: int, + # prompt + prompt_depth: int = 0, + prompt_length: int = 0, + ): + super().__init__() + + self.context_length = context_length + + self.image_resolution = image_resolution + + if isinstance(vision_layers, (tuple, list)): + assert prompt_length == 0 and prompt_depth == 0 + vision_heads = vision_width * 32 // 64 + self.visual = ModifiedResNet( + layers=vision_layers, + output_dim=embed_dim, + heads=vision_heads, + input_resolution=image_resolution, + width=vision_width) + else: + vision_heads = vision_width // 64 + self.visual = VisualTransformer( + input_resolution=image_resolution, + patch_size=vision_patch_size, + width=vision_width, + layers=vision_layers, + heads=vision_heads, + output_dim=embed_dim, + prompt_depth=prompt_depth, + prompt_length=prompt_length, + ) + + self.transformer = Transformer( + width=transformer_width, + layers=transformer_layers, + heads=transformer_heads, + attn_mask=self.build_attention_mask()) + + self.vocab_size = vocab_size + self.token_embedding = nn.Embedding(vocab_size, transformer_width) + self.positional_embedding = nn.Parameter( + torch.empty(self.context_length, transformer_width)) + self.ln_final = LayerNorm(transformer_width) + + self.text_projection = nn.Parameter( + torch.empty(transformer_width, embed_dim)) + self.logit_scale = nn.Parameter(torch.ones([])) + + def build_attention_mask(self): + """Create causal attention mask.""" + # lazily create causal attention mask, with full attention between + # the vision tokens pytorch uses additive attention mask; fill with + # -inf + mask = torch.empty(self.context_length, self.context_length) + mask.fill_(float('-inf')) + mask.triu_(1) # zero out the lower diagonal + return mask + + @property + def dtype(self): + """Return the dtype of the model.""" + return self.visual.conv1.weight.dtype + + def encode_image(self, image, masks=None, pool_mask=None, dense=False): + """Image encoding.""" + if pool_mask is not None: + return self.visual( + image.type(self.dtype), mask=pool_mask, dense=dense) + if masks is None: + return self.visual(image.type(self.dtype), dense=dense) + else: + return self.visual(image.type(self.dtype), masks.type(self.dtype)) + + def encode_text(self, text): + """Texts encoding.""" + x = self.token_embedding(text).type( + self.dtype) # [batch_size, n_ctx, d_model] + + x = x + self.positional_embedding.type(self.dtype) + x = x.permute(1, 0, 2) # NLD -> LND + x = self.transformer(x) + x = x.permute(1, 0, 2) # LND -> NLD + x = self.ln_final(x).type(self.dtype) + + # x.shape = [batch_size, n_ctx, transformer.width] + # take features from the eot embedding (eot_token is the highest number + # in each sequence) + x = x[torch.arange(x.shape[0]), + text.argmax(dim=-1)] @ self.text_projection + + return x + + def forward(self, image, text): + """ + Args: + image (torch.Tensor): input images. + text (torch.Tensor): input text. + """ + image_features = self.encode_image(image) + text_features = self.encode_text(text) + # import pdb; pdb.set_trace() + # normalized features + # image_features shape: [1, 1024] + image_features = image_features / image_features.norm( + dim=-1, keepdim=True) + text_features = text_features / text_features.norm( + dim=-1, keepdim=True) + + # cosine similarity as logits + logit_scale = self.logit_scale.exp() + logits_per_iamge = logit_scale * image_features @ text_features.t() + logits_per_text = logit_scale * text_features @ image_features.t() + + # shape = [global_batch_size, global_batch_size] + return logits_per_iamge, logits_per_text + + +def convert_weights(model: nn.Module): + """Convert applicable model parameters to fp16.""" + + def _convert_weights_to_fp16(layer): + if isinstance(layer, (nn.Conv1d, nn.Conv2d, nn.Linear)): + layer.weight.data = layer.weight.data.half() + if layer.bias is not None: + layer.bias.data = layer.bias.data.half() + + if isinstance(layer, nn.MultiheadAttention): + for attr in [ + *[f'{s}_proj_weight' for s in ['in', 'q', 'k', 'v']], + 'in_proj_bias', 'bias_k', 'bias_v' + ]: + tensor = getattr(layer, attr) + if tensor is not None: + tensor.data = tensor.data.half() + + for name in ['text_projection', 'proj']: + if hasattr(layer, name): + attr = getattr(layer, name) + if attr is not None: + attr.data = attr.data.half() + + model.apply(_convert_weights_to_fp16) + + +def build_model(state_dict: dict, prompt_depth=0, prompt_length=0): + """Build a CLIP model from given pretrained weights.""" + vit = 'visual.proj' in state_dict + + if vit: + vision_width = state_dict['visual.conv1.weight'].shape[0] + vision_layers = len([ + k for k in state_dict.keys() + if k.startswith('visual.') and k.endswith('.attn.in_proj_weight') + ]) + vision_patch_size = state_dict['visual.conv1.weight'].shape[-1] + grid_size = round( + (state_dict['visual.positional_embedding'].shape[0] - 1)**0.5) + image_resolution = vision_patch_size * grid_size + else: + counts: list = [ + len({ + k.split('.')[2] + for k in state_dict if k.startswith(f'visual.layer{b}') + }) for b in [1, 2, 3, 4] + ] + vision_layers = tuple(counts) + vision_width = state_dict['visual.layer1.0.conv1.weight'].shape[0] + output_width = round( + (state_dict['visual.attnpool.positional_embedding'].shape[0] - + 1)**0.5) + vision_patch_size = None + assert output_width**2 + 1 == state_dict[ + 'visual.attnpool.positional_embedding'].shape[0] + image_resolution = output_width * 32 + + embed_dim = state_dict['text_projection'].shape[1] + context_length = state_dict['positional_embedding'].shape[0] + vocab_size = state_dict['token_embedding.weight'].shape[0] + transformer_width = state_dict['ln_final.weight'].shape[0] + transformer_heads = transformer_width // 64 + transformer_layers = len({ + k.split('.')[2] + for k in state_dict if k.startswith('transformer.resblocks') + }) + + model = CLIP( + embed_dim, + image_resolution, + vision_layers, + vision_width, + vision_patch_size, + context_length, + vocab_size, + transformer_width, + transformer_heads, + transformer_layers, + prompt_depth=prompt_depth, + prompt_length=prompt_length, + ) + + for key in ['input_resolution', 'context_length', 'vocab_size']: + del state_dict[key] + + convert_weights(model) + model.load_state_dict(state_dict, strict=False) + return model.eval() diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_templates.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_templates.py new file mode 100644 index 0000000..bfc32df --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_templates.py @@ -0,0 +1,204 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +# Source: https://github.com/openai/CLIP. + +IMAGENET_TEMPLATES = [ + 'a bad photo of a {}.', + 'a photo of many {}.', + 'a sculpture of a {}.', + 'a photo of the hard to see {}.', + 'a low resolution photo of the {}.', + 'a rendering of a {}.', + 'graffiti of a {}.', + 'a bad photo of the {}.', + 'a cropped photo of the {}.', + 'a tattoo of a {}.', + 'the embroidered {}.', + 'a photo of a hard to see {}.', + 'a bright photo of a {}.', + 'a photo of a clean {}.', + 'a photo of a dirty {}.', + 'a dark photo of the {}.', + 'a drawing of a {}.', + 'a photo of my {}.', + 'the plastic {}.', + 'a photo of the cool {}.', + 'a close-up photo of a {}.', + 'a black and white photo of the {}.', + 'a painting of the {}.', + 'a painting of a {}.', + 'a pixelated photo of the {}.', + 'a sculpture of the {}.', + 'a bright photo of the {}.', + 'a cropped photo of a {}.', + 'a plastic {}.', + 'a photo of the dirty {}.', + 'a jpeg corrupted photo of a {}.', + 'a blurry photo of the {}.', + 'a photo of the {}.', + 'a good photo of the {}.', + 'a rendering of the {}.', + 'a {} in a video game.', + 'a photo of one {}.', + 'a doodle of a {}.', + 'a close-up photo of the {}.', + 'a photo of a {}.', + 'the origami {}.', + 'the {} in a video game.', + 'a sketch of a {}.', + 'a doodle of the {}.', + 'a origami {}.', + 'a low resolution photo of a {}.', + 'the toy {}.', + 'a rendition of the {}.', + 'a photo of the clean {}.', + 'a photo of a large {}.', + 'a rendition of a {}.', + 'a photo of a nice {}.', + 'a photo of a weird {}.', + 'a blurry photo of a {}.', + 'a cartoon {}.', + 'art of a {}.', + 'a sketch of the {}.', + 'a embroidered {}.', + 'a pixelated photo of a {}.', + 'itap of the {}.', + 'a jpeg corrupted photo of the {}.', + 'a good photo of a {}.', + 'a plushie {}.', + 'a photo of the nice {}.', + 'a photo of the small {}.', + 'a photo of the weird {}.', + 'the cartoon {}.', + 'art of the {}.', + 'a drawing of the {}.', + 'a photo of the large {}.', + 'a black and white photo of a {}.', + 'the plushie {}.', + 'a dark photo of a {}.', + 'itap of a {}.', + 'graffiti of the {}.', + 'a toy {}.', + 'itap of my {}.', + 'a photo of a cool {}.', + 'a photo of a small {}.', + 'a tattoo of the {}.', + # 'A photo of a {} in the scene.', +] + +# v1: 59.0875 +IMAGENET_TEMPLATES_SELECT = [ + 'itap of a {}.', + 'a bad photo of the {}.', + 'a origami {}.', + 'a photo of the large {}.', + 'a {} in a video game.', + 'art of the {}.', + 'a photo of the small {}.', + 'A photo of a {} in the scene', +] + +# v9 +IMAGENET_TEMPLATES_SELECT_CLIP = [ + 'a bad photo of the {}.', + 'a photo of the large {}.', + 'a photo of the small {}.', + 'a cropped photo of a {}.', + 'This is a photo of a {}', + 'This is a photo of a small {}', + 'This is a photo of a medium {}', + 'This is a photo of a large {}', + 'This is a masked photo of a {}', + 'This is a masked photo of a small {}', + 'This is a masked photo of a medium {}', + 'This is a masked photo of a large {}', + 'This is a cropped photo of a {}', + 'This is a cropped photo of a small {}', + 'This is a cropped photo of a medium {}', + 'This is a cropped photo of a large {}', + 'A photo of a {} in the scene', + 'a bad photo of the {} in the scene', + 'a photo of the large {} in the scene', + 'a photo of the small {} in the scene', + 'a cropped photo of a {} in the scene', + 'a photo of a masked {} in the scene', + 'There is a {} in the scene', + 'There is the {} in the scene', + 'This is a {} in the scene', + 'This is the {} in the scene', + 'This is one {} in the scene', + 'There is a masked {} in the scene', + 'There is the masked {} in the scene', + 'This is a masked {} in the scene', + 'This is the masked {} in the scene', + 'This is one masked {} in the scene', +] + +# v10, for comparison +# IMAGENET_TEMPLATES_SELECT_CLIP = [ +# 'a photo of a {}.', +# +# 'This is a photo of a {}', +# 'This is a photo of a small {}', +# 'This is a photo of a medium {}', +# 'This is a photo of a large {}', +# +# 'This is a photo of a {}', +# 'This is a photo of a small {}', +# 'This is a photo of a medium {}', +# 'This is a photo of a large {}', +# +# 'a photo of a {} in the scene', +# 'a photo of a {} in the scene', +# +# 'There is a {} in the scene', +# 'There is the {} in the scene', +# 'This is a {} in the scene', +# 'This is the {} in the scene', +# 'This is one {} in the scene', +# ] + +ViLD_templates = [ + 'There is {article} {category} in the scene.', + 'There is the {category} in the scene.', + 'a photo of {article} {category} in the scene.', + 'a photo of the {category} in the scene.', + 'a photo of one {category} in the scene.', 'itap of {article} {category}.', + 'itap of my {category}.', 'itap of the {category}.', + 'a photo of {article} {category}.', 'a photo of my {category}.', + 'a photo of the {category}.', 'a photo of one {category}.', + 'a photo of many {category}.', 'a good photo of {article} {category}.', + 'a good photo of the {category}.', 'a bad photo of {article} {category}.', + 'a bad photo of the {category}.', 'a photo of a nice {category}.', + 'a photo of the nice {category}.', 'a photo of a cool {category}.', + 'a photo of the cool {category}.', 'a photo of a weird {category}.', + 'a photo of the weird {category}.', 'a photo of a small {category}.', + 'a photo of the small {category}.', 'a photo of a large {category}.', + 'a photo of the large {category}.', 'a photo of a clean {category}.', + 'a photo of the clean {category}.', 'a photo of a dirty {category}.', + 'a photo of the dirty {category}.', + 'a bright photo of {article} {category}.', + 'a bright photo of the {category}.', + 'a dark photo of {article} {category}.', 'a dark photo of the {category}.', + 'a photo of a hard to see {category}.', + 'a photo of the hard to see {category}.', + 'a low resolution photo of {article} {category}.', + 'a low resolution photo of the {category}.', + 'a cropped photo of {article} {category}.', + 'a cropped photo of the {category}.', + 'a close-up photo of {article} {category}.', + 'a close-up photo of the {category}.', + 'a jpeg corrupted photo of {article} {category}.', + 'a jpeg corrupted photo of the {category}.', + 'a blurry photo of {article} {category}.', + 'a blurry photo of the {category}.', + 'a pixelated photo of {article} {category}.', + 'a pixelated photo of the {category}.', + 'a black and white photo of the {category}.', + 'a black and white photo of {article} {category}.', + 'a plastic {category}.', 'the plastic {category}.', 'a toy {category}.', + 'the toy {category}.', 'a plushie {category}.', 'the plushie {category}.', + 'a cartoon {category}.', 'the cartoon {category}.', + 'an embroidered {category}.', 'the embroidered {category}.', + 'a painting of the {category}.', 'a painting of a {category}.' +] diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_wrapper.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_wrapper.py new file mode 100644 index 0000000..f809d2b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/clip_wrapper.py @@ -0,0 +1,275 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# Referred to: https://github.com/KU-CVLAB/CAT-Seg/blob/main/cat_seg/third_party/clip.py # noqa +import hashlib +import os +import urllib +import warnings +from typing import List, Union + +import torch +from PIL import Image +from torchvision.transforms import (CenterCrop, Compose, Normalize, Resize, + ToTensor) +from tqdm import tqdm + +from .clip_model import build_model +from .tokenizer import SimpleTokenizer as _Tokenizer + +__all__ = ['available_models', 'load', 'tokenize'] +_tokenizer = _Tokenizer() + +_MODELS = { + 'RN50': + 'https://openaipublic.azureedge.net/clip/models/afeb0e10f9e5a86da6080e35cf09123aca3b358a0c3e3b6c78a7b63bc04b6762/RN50.pt', # noqa + 'RN101': + 'https://openaipublic.azureedge.net/clip/models/8fa8567bab74a42d41c5915025a8e4538c3bdbe8804a470a72f30b0d94fab599/RN101.pt', # noqa + 'RN50x4': + 'https://openaipublic.azureedge.net/clip/models/7e526bd135e493cef0776de27d5f42653e6b4c8bf9e0f653bb11773263205fdd/RN50x4.pt', # noqa + 'RN50x16': + 'https://openaipublic.azureedge.net/clip/models/52378b407f34354e150460fe41077663dd5b39c54cd0bfd2b27167a4a06ec9aa/RN50x16.pt', # noqa + 'RN50x64': + 'https://openaipublic.azureedge.net/clip/models/be1cfb55d75a9666199fb2206c106743da0f6468c9d327f3e0d0a543a9919d9c/RN50x64.pt', # noqa + 'ViT-B/32': + 'https://openaipublic.azureedge.net/clip/models/40d365715913c9da98579312b702a82c18be219cc2a73407c4526f58eba950af/ViT-B-32.pt', # noqa + 'ViT-B/16': + 'https://openaipublic.azureedge.net/clip/models/5806e77cd80f8b59890b7e101eabd078d9fb84e6937f9e85e4ecb61988df416f/ViT-B-16.pt', # noqa + 'ViT-L/14': + 'https://openaipublic.azureedge.net/clip/models/b8cca3fd41ae0c99ba7e8951adf17d267cdb84cd88be6f7c2e0eca1737a03836/ViT-L-14.pt', # noqa + 'ViT-L/14@336px': + 'https://openaipublic.azureedge.net/clip/models/3035c92b350959924f9f00213499208652fc7ea050643e8b385c2dac08641f02/ViT-L-14-336px.pt', # noqa +} + + +def _download(url: str, root: str = os.path.expanduser('~/.cache/clip')): + """Download clip pretrained weights.""" + os.makedirs(root, exist_ok=True) + filename = os.path.basename(url) + + expected_sha256 = url.split('/')[-2] + download_target = os.path.join(root, filename) + + if os.path.exists(download_target) and not os.path.isfile(download_target): + raise RuntimeError( + f'{download_target} exists and is not a regular file') + + if os.path.isfile(download_target): + if hashlib.sha256(open(download_target, + 'rb').read()).hexdigest() == expected_sha256: + return download_target + else: + warnings.warn( + f'{download_target} exists, but the SHA256 checksum does not\ + match; re-downloading the file') + + with urllib.request.urlopen(url) as source, open(download_target, + 'wb') as output: + with tqdm( + total=int(source.info().get('Content-Length')), + ncols=80) as loop: + while True: + buffer = source.read(8192) + if not buffer: + break + + output.write(buffer) + loop.update(len(buffer)) + + if hashlib.sha256(open(download_target, + 'rb').read()).hexdigest() != expected_sha256: + raise RuntimeError( + 'Model has been downloaded but the SHA256 checksum does not not\ + match') + + return download_target + + +def available_models(): + """Returns a list of available models.""" + return list(_MODELS.keys()) + + +def load(name: str, + device: Union[str, torch.device] = 'cuda' + if torch.cuda.is_available() else 'cpu', + jit=True, + prompt_depth=0, + prompt_length=0): + """Load target clip model.""" + if name not in _MODELS: + raise RuntimeError( + f'Model {name} not found; available models = {available_models()}') + + model_path = _download(_MODELS[name]) + model = torch.jit.load( + model_path, map_location=device if jit else 'cpu').eval() + n_px = model.input_resolution.item() + + transform = Compose([ + Resize(n_px, interpolation=Image.BICUBIC), + CenterCrop(n_px), + lambda image: image.convert('RGB'), + ToTensor(), + Normalize((0.48145466, 0.4578275, 0.40821073), + (0.26862954, 0.26130258, 0.27577711)), + ]) + + if not jit: + model = build_model(model.state_dict(), prompt_depth, + prompt_length).to(device) + return model, transform + + # patch the device names + device_holder = torch.jit.trace( + lambda: torch.ones([]).to(torch.device(device)), example_inputs=[]) + device_node = [ + n for n in device_holder.graph.findAllNodes('prim::Constant') + if 'Device' in repr(n) + ][-1] + + def patch_device(module): + graphs = [module.graph] if hasattr(module, 'graph') else [] + if hasattr(module, 'forward1'): + graphs.append(module.forward1.graph) + + for graph in graphs: + for node in graph.findAllNodes('prim::Constant'): + if 'value' in node.attributeNames() and str( + node['value']).startswith('cuda'): + node.copyAttributes(device_node) + + model.apply(patch_device) + patch_device(model.encode_image) + patch_device(model.encode_text) + + # patch dtype to float32 on CPU + if device == 'cpu': + float_holder = torch.jit.trace( + lambda: torch.ones([]).float(), example_inputs=[]) + float_input = list(float_holder.graph.findNode('aten::to').inputs())[1] + float_node = float_input.node() + + def patch_float(module): + graphs = [module.graph] if hasattr(module, 'graph') else [] + if hasattr(module, 'forward1'): + graphs.append(module.forward1.graph) + + for graph in graphs: + for node in graph.findAllNodes('aten::to'): + inputs = list(node.inputs()) + for i in [1, 2]: + # dtype can be the second or third argument to + # aten::to() + if inputs[i].node()['value'] == 5: + inputs[i].node().copyAttributes(float_node) + + model.apply(patch_float) + patch_float(model.encode_image) + patch_float(model.encode_text) + + model.float() + + return model, transform + + +def load_custom(name: str, + device: Union[str, torch.device] = 'cuda' + if torch.cuda.is_available() else 'cpu', + jit=True, + n_px=224): + """Load a customized clip model.""" + if name not in _MODELS: + raise RuntimeError( + f'Model {name} not found; available models = {available_models()}') + + model_path = _download(_MODELS[name]) + model = torch.jit.load( + model_path, map_location=device if jit else 'cpu').eval() + # n_px = model.input_resolution.item() + + transform = Compose([ + Resize(n_px, interpolation=Image.BICUBIC), + CenterCrop(n_px), + lambda image: image.convert('RGB'), + ToTensor(), + Normalize((0.48145466, 0.4578275, 0.40821073), + (0.26862954, 0.26130258, 0.27577711)), + ]) + + if not jit: + model = build_model(model.state_dict()).to(device) + return model, transform + + # patch the device names + device_holder = torch.jit.trace( + lambda: torch.ones([]).to(torch.device(device)), example_inputs=[]) + device_node = [ + n for n in device_holder.graph.findAllNodes('prim::Constant') + if 'Device' in repr(n) + ][-1] + + def patch_device(module): + graphs = [module.graph] if hasattr(module, 'graph') else [] + if hasattr(module, 'forward1'): + graphs.append(module.forward1.graph) + + for graph in graphs: + for node in graph.findAllNodes('prim::Constant'): + if 'value' in node.attributeNames() and str( + node['value']).startswith('cuda'): + node.copyAttributes(device_node) + + model.apply(patch_device) + patch_device(model.encode_image) + patch_device(model.encode_text) + + # patch dtype to float32 on CPU + if device == 'cpu': + float_holder = torch.jit.trace( + lambda: torch.ones([]).float(), example_inputs=[]) + float_input = list(float_holder.graph.findNode('aten::to').inputs())[1] + float_node = float_input.node() + + def patch_float(module): + graphs = [module.graph] if hasattr(module, 'graph') else [] + if hasattr(module, 'forward1'): + graphs.append(module.forward1.graph) + + for graph in graphs: + for node in graph.findAllNodes('aten::to'): + inputs = list(node.inputs()) + for i in [ + 1, 2 + ]: # dtype can be the second or third argument to + # aten::to() + if inputs[i].node()['value'] == 5: + inputs[i].node().copyAttributes(float_node) + + model.apply(patch_float) + patch_float(model.encode_image) + patch_float(model.encode_text) + + model.float() + + return model, transform + + +def tokenize(texts: Union[str, List[str]], context_length: int = 77): + """Convert texts to tokens.""" + if isinstance(texts, str): + texts = [texts] + + sot_token = _tokenizer.encoder['<|startoftext|>'] + eot_token = _tokenizer.encoder['<|endoftext|>'] + # encode each template text phrase + all_tokens = [[sot_token] + _tokenizer.encode(text) + [eot_token] + for text in texts] + result = torch.zeros(len(all_tokens), context_length, dtype=torch.long) + + for i, tokens in enumerate(all_tokens): + if len(tokens) > context_length: + raise RuntimeError( + f'Input {texts[i]} is too long for context length\ + {context_length}') + result[i, :len(tokens)] = torch.tensor(tokens) + + return result diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/self_attention_block.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/self_attention_block.py new file mode 100644 index 0000000..1c06cbd --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/self_attention_block.py @@ -0,0 +1,79 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch import nn as nn +from torch.nn import functional as F + + +class LinearAttention(nn.Module): + """Multi-Head linear attention proposed in "Transformers are RNNs". + + Source: https://github.com/KU-CVLAB/CAT-Seg/blob/main/cat_seg/modeling/transformer/model.py#L247 # noqa + """ + + def __init__(self, eps=1e-6): + super().__init__() + self.eps = eps + + def forward(self, queries, keys, values): + """ + Args: + queries: [N, L, H, D] + keys: [N, S, H, D] + values: [N, S, H, D] + q_mask: [N, L] + kv_mask: [N, S] + Returns: + queried_values: (N, L, H, D) + """ + Q = F.elu(queries) + 1 + K = F.elu(keys) + 1 + + v_length = values.size(1) + values = values / v_length # prevent fp16 overflow + KV = torch.einsum('nshd,nshv->nhdv', K, values) # (S,D)' @ S,V + Z = 1 / (torch.einsum('nlhd,nhd->nlh', Q, K.sum(dim=1)) + self.eps) + queried_values = torch.einsum('nlhd,nhdv,nlh->nlhv', Q, KV, + Z) * v_length + + return queried_values.contiguous() + + +class FullAttention(nn.Module): + """Multi-head scaled dot-product attention, a.k.a full attention. + + Source: https://github.com/KU-CVLAB/CAT-Seg/blob/main/cat_seg/modeling/transformer/model.py#L276 # noqa + """ + + def __init__(self, use_dropout=False, attention_dropout=0.1): + super().__init__() + self.use_dropout = use_dropout + self.dropout = nn.Dropout(attention_dropout) + + def forward(self, queries, keys, values, q_mask=None, kv_mask=None): + """ + Args: + queries: [N, L, H, D] + keys: [N, S, H, D] + values: [N, S, H, D] + q_mask: [N, L] + kv_mask: [N, S] + Returns: + queried_values: (N, L, H, D) + """ + + # Compute the unnormalized attention and apply the masks + QK = torch.einsum('nlhd,nshd->nlsh', queries, keys) + if kv_mask is not None: + QK.masked_fill_( + ~(q_mask[:, :, None, None] * kv_mask[:, None, :, None]), + float('-inf')) + + # Compute the attention and the weighted average + softmax_temp = 1. / queries.size(3)**.5 # sqrt(D) + A = torch.softmax(softmax_temp * QK, dim=2) + if self.use_dropout: + A = self.dropout(A) + + queried_values = torch.einsum('nlsh,nshd->nlhd', A, values) + + return queried_values.contiguous() diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/tokenizer.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/tokenizer.py new file mode 100644 index 0000000..c84711b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/cat_seg/utils/tokenizer.py @@ -0,0 +1,160 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import gzip +import html +import os +from functools import lru_cache + +import ftfy +import regex as re + + +@lru_cache() +def default_bpe(): + """Return default BPE vocabulary path.""" + return os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'bpe_vocab/bpe_simple_vocab_16e6.txt.gz') + + +@lru_cache() +def bytes_to_unicode(): + """Returns list of utf-8 byte and a corresponding list of unicode strings. + + The reversible bpe codes work on unicode strings. This means you need a + large # of unicode characters in your vocab if you want to avoid UNKs. When + you're at something like a 10B token dataset you end up needing around 5K + for decent coverage. This is a significant percentage of your normal, say, + 32K bpe vocab. To avoid that, we want lookup tables between utf-8 bytes and + unicode strings. And avoids mapping to whitespace/control characters the + bpe code barfs on. + """ + bs = list(range(ord('!'), + ord('~') + 1)) + list(range( + ord('¡'), + ord('¬') + 1)) + list(range(ord('®'), + ord('ÿ') + 1)) + cs = bs[:] + n = 0 + for b in range(2**8): + if b not in bs: + bs.append(b) + cs.append(2**8 + n) + n += 1 + cs = [chr(n) for n in cs] + return dict(zip(bs, cs)) + + +def get_pairs(word): + """Return set of symbol pairs in a word. + + Word is represented as tuple of symbols (symbols being variable-length + strings). + """ + pairs = set() + prev_char = word[0] + for char in word[1:]: + pairs.add((prev_char, char)) + prev_char = char + return pairs + + +def basic_clean(text): + """Clean string.""" + text = ftfy.fix_text(text) + text = html.unescape(html.unescape(text)) + return text.strip() + + +def whitespace_clean(text): + """Clean whitespace in string.""" + text = re.sub(r'\s+', ' ', text) + text = text.strip() + return text + + +class SimpleTokenizer: + """Customized Tokenizer implementation.""" + + def __init__(self, bpe_path: str = default_bpe()): + self.byte_encoder = bytes_to_unicode() + self.byte_decoder = {v: k for k, v in self.byte_encoder.items()} + merges = gzip.open(bpe_path).read().decode('utf-8').split('\n') + merges = merges[1:49152 - 256 - 2 + 1] + merges = [tuple(merge.split()) for merge in merges] + vocab = list(bytes_to_unicode().values()) + vocab = vocab + [v + '' for v in vocab] + for merge in merges: + vocab.append(''.join(merge)) + vocab.extend(['<|startoftext|>', '<|endoftext|>']) + self.encoder = dict(zip(vocab, range(len(vocab)))) + self.decoder = {v: k for k, v in self.encoder.items()} + self.bpe_ranks = dict(zip(merges, range(len(merges)))) + self.cache = { + '<|startoftext|>': '<|startoftext|>', + '<|endoftext|>': '<|endoftext|>' + } + self.pat = re.compile( + r"""<\|startoftext\|>|<\|endoftext\|>|'s|'t|'re|'ve|'m|\ + 'll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+""", re.IGNORECASE) + + def bpe(self, token): + """Refer to bpe vocabulary dictionary.""" + if token in self.cache: + return self.cache[token] + word = tuple(token[:-1]) + (token[-1] + '', ) + pairs = get_pairs(word) + + if not pairs: + return token + '' + + while True: + bigram = min( + pairs, key=lambda pair: self.bpe_ranks.get(pair, float('inf'))) + if bigram not in self.bpe_ranks: + break + first, second = bigram + new_word = [] + i = 0 + while i < len(word): + try: + j = word.index(first, i) + new_word.extend(word[i:j]) + i = j + except ValueError: + new_word.extend(word[i:]) + break + + if word[i] == first and i < len(word) - 1 and word[ + i + 1] == second: + new_word.append(first + second) + i += 2 + else: + new_word.append(word[i]) + i += 1 + new_word = tuple(new_word) + word = new_word + if len(word) == 1: + break + else: + pairs = get_pairs(word) + word = ' '.join(word) + self.cache[token] = word + return word + + def encode(self, text): + """Encode text strings.""" + bpe_tokens = [] + text = whitespace_clean(basic_clean(text)).lower() + for token in re.findall(self.pat, text): + token = ''.join(self.byte_encoder[b] + for b in token.encode('utf-8')) + bpe_tokens.extend(self.encoder[bpe_token] + for bpe_token in self.bpe(token).split(' ')) + return bpe_tokens + + def decode(self, tokens): + """Decoder tokens to strings.""" + text = ''.join([self.decoder[token] for token in tokens]) + text = bytearray([self.byte_decoder[c] for c in text]).decode( + 'utf-8', errors='replace').replace('', ' ') + return text diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/ade20k_384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/ade20k_384x384.py new file mode 100644 index 0000000..488ba3d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/ade20k_384x384.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'ADE20KDataset' +data_root = 'data/ade/ADEChallengeData2016' +crop_size = (384, 384) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', seg_map_path='annotations/training'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/coco-stuff164k_384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/coco-stuff164k_384x384.py new file mode 100644 index 0000000..dd05176 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/coco-stuff164k_384x384.py @@ -0,0 +1,62 @@ +# dataset settings +dataset_type = 'COCOStuffDataset' +data_root = 'data/coco_stuff164k' +crop_size = (384, 384) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/train2017', seg_map_path='annotations/train2017'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/val2017', seg_map_path='annotations/val2017'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/pascal_context_59_384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/pascal_context_59_384x384.py new file mode 100644 index 0000000..250c599 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/datasets/pascal_context_59_384x384.py @@ -0,0 +1,72 @@ +# dataset settings +dataset_type = 'PascalContextDataset59' +data_root = 'data/VOCdevkit/VOC2010/' + +img_scale = (520, 520) +crop_size = (384, 384) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=img_scale, + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClassContext'), + ann_file='ImageSets/SegmentationContext/train.txt', + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='JPEGImages', seg_map_path='SegmentationClassContext'), + ann_file='ImageSets/SegmentationContext/val.txt', + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/default_runtime.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/default_runtime.py new file mode 100644 index 0000000..272b4d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/default_runtime.py @@ -0,0 +1,15 @@ +default_scope = 'mmseg' +env_cfg = dict( + cudnn_benchmark=True, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict(by_epoch=False) +log_level = 'INFO' +load_from = None +resume = False + +tta_model = dict(type='SegTTAModel') diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/schedules/schedule_80k.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/schedules/schedule_80k.py new file mode 100644 index 0000000..0dcd6c4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/_base_/schedules/schedule_80k.py @@ -0,0 +1,24 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=80000, + by_epoch=False) +] +# training schedule for 80k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=8000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=8000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_ade20k-384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_ade20k-384x384.py new file mode 100644 index 0000000..bab43a6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_ade20k-384x384.py @@ -0,0 +1,103 @@ +_base_ = [ + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py', + '../_base_/datasets/ade20k_384x384.py' +] + +custom_imports = dict(imports=['cat_seg']) + +norm_cfg = dict(type='SyncBN', requires_grad=True) +crop_size = (384, 384) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=crop_size, + # due to the clip model, we do normalization in backbone forward() + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +# model_cfg +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='CLIPOVCATSeg', + feature_extractor=dict( + type='ResNet', + depth=101, + # only use the first three layers + num_stages=3, + out_indices=(0, 1, 2), + dilations=(1, 1, 1), + strides=(1, 2, 2), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True, + init_cfg=dict( + type='Pretrained', checkpoint='torchvision://resnet101'), + ), + train_class_json='data/ade150.json', + test_class_json='data/ade150.json', + clip_pretrained='ViT-B/16', + clip_finetune='attention', + ), + neck=dict( + type='CATSegAggregator', + appearance_guidance_dim=1024, + num_layers=2, + pooling_size=(1, 1), + ), + decode_head=dict( + type='CATSegHead', + in_channels=128, + channels=128, + num_classes=150, + embed_dims=128, + decoder_dims=(64, 32), + decoder_guidance_dims=(512, 256), + decoder_guidance_proj_dims=(32, 16), + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + avg_non_ignore=True)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='slide', stride=crop_size, crop_size=crop_size)) + +# dataset settings +train_dataloader = dict( + batch_size=2, + num_workers=4, +) + +# training schedule for 80k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=4000) + +default_hooks = dict( + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), + visualization=dict(type='SegVisualizationHook', draw=True, interval=4000)) + +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0002, betas=(0.9, 0.999), weight_decay=0.0001), + paramwise_cfg=dict( + custom_keys={ + 'backbone.feature_extractor': dict(lr_mult=0.01), + 'backbone.clip_model.visual': dict(lr_mult=0.01) + })) + +# learning policy +param_scheduler = [ + # Use a linear warm-up at [0, 100) iterations + dict(type='LinearLR', start_factor=0.01, by_epoch=False, begin=0, end=500), + # Use a cosine learning rate at [100, 900) iterations + dict( + type='CosineAnnealingLR', + T_max=79500, + by_epoch=False, + begin=500, + end=80000), +] diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_pascal-context-59-384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_pascal-context-59-384x384.py new file mode 100644 index 0000000..8b412cb --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb1-warmcoslr2e-4-adamw-80k_pascal-context-59-384x384.py @@ -0,0 +1,103 @@ +_base_ = [ + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py', + '../_base_/datasets/pascal_context_59_384x384.py' +] + +custom_imports = dict(imports=['cat_seg']) + +norm_cfg = dict(type='SyncBN', requires_grad=True) +crop_size = (384, 384) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=crop_size, + # due to the clip model, we do normalization in backbone forward() + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +# model_cfg +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='CLIPOVCATSeg', + feature_extractor=dict( + type='ResNet', + depth=101, + # only use the first three layers + num_stages=3, + out_indices=(0, 1, 2), + dilations=(1, 1, 1), + strides=(1, 2, 2), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True, + init_cfg=dict( + type='Pretrained', checkpoint='torchvision://resnet101'), + ), + train_class_json='data/pc59.json', + test_class_json='data/pc59.json', + clip_pretrained='ViT-B/16', + clip_finetune='attention', + ), + neck=dict( + type='CATSegAggregator', + appearance_guidance_dim=1024, + num_layers=2, + pooling_size=(1, 1), + ), + decode_head=dict( + type='CATSegHead', + in_channels=128, + channels=128, + num_classes=59, + embed_dims=128, + decoder_dims=(64, 32), + decoder_guidance_dims=(512, 256), + decoder_guidance_proj_dims=(32, 16), + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + avg_non_ignore=True)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='slide', stride=crop_size, crop_size=crop_size)) + +# dataset settings +train_dataloader = dict( + batch_size=2, + num_workers=4, +) + +# training schedule for 80k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=4000) + +default_hooks = dict( + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), + visualization=dict(type='SegVisualizationHook', draw=True, interval=4000)) + +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0002, betas=(0.9, 0.999), weight_decay=0.0001), + paramwise_cfg=dict( + custom_keys={ + 'backbone.feature_extractor': dict(lr_mult=0.01), + 'backbone.clip_model.visual': dict(lr_mult=0.01) + })) + +# learning policy +param_scheduler = [ + # Use a linear warm-up at [0, 100) iterations + dict(type='LinearLR', start_factor=0.01, by_epoch=False, begin=0, end=500), + # Use a cosine learning rate at [100, 900) iterations + dict( + type='CosineAnnealingLR', + T_max=79500, + by_epoch=False, + begin=500, + end=80000), +] diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb2-warmcoslr2e-4-adamw-80k_coco-stuff164k-384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb2-warmcoslr2e-4-adamw-80k_coco-stuff164k-384x384.py new file mode 100644 index 0000000..52bf712 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitb-r101_4xb2-warmcoslr2e-4-adamw-80k_coco-stuff164k-384x384.py @@ -0,0 +1,102 @@ +_base_ = [ + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py', + '../_base_/datasets/coco-stuff164k_384x384.py' +] + +custom_imports = dict(imports=['cat_seg']) + +norm_cfg = dict(type='SyncBN', requires_grad=True) +crop_size = (384, 384) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=crop_size, + # due to the clip model, we do normalization in backbone forward() + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +# model_cfg +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='CLIPOVCATSeg', + feature_extractor=dict( + type='ResNet', + depth=101, + # only use the first three layers + num_stages=3, + out_indices=(0, 1, 2), + dilations=(1, 1, 1), + strides=(1, 2, 2), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True, + init_cfg=dict( + type='Pretrained', checkpoint='torchvision://resnet101'), + ), + train_class_json='data/coco.json', + test_class_json='data/coco.json', + clip_pretrained='ViT-B/16', + clip_finetune='attention', + ), + neck=dict( + type='CATSegAggregator', + appearance_guidance_dim=1024, + num_layers=2, + ), + decode_head=dict( + type='CATSegHead', + in_channels=128, + channels=128, + num_classes=171, + embed_dims=128, + decoder_dims=(64, 32), + decoder_guidance_dims=(512, 256), + decoder_guidance_proj_dims=(32, 16), + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + avg_non_ignore=True)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='slide', stride=crop_size, crop_size=crop_size)) + +# dataset settings +train_dataloader = dict( + batch_size=2, + num_workers=4, +) + +# training schedule for 80k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=4000) + +default_hooks = dict( + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=4000), + visualization=dict(type='SegVisualizationHook', draw=True, interval=4000)) + +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0002, betas=(0.9, 0.999), weight_decay=0.0001), + paramwise_cfg=dict( + custom_keys={ + 'backbone.feature_extractor': dict(lr_mult=0.01), + 'backbone.clip_model.visual': dict(lr_mult=0.01) + })) + +# learning policy +param_scheduler = [ + # Use a linear warm-up at [0, 100) iterations + dict(type='LinearLR', start_factor=0.01, by_epoch=False, begin=0, end=500), + # Use a cosine learning rate at [100, 900) iterations + dict( + type='CosineAnnealingLR', + T_max=79500, + by_epoch=False, + begin=500, + end=80000), +] diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitg-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitg-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py new file mode 100644 index 0000000..345945d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitg-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py @@ -0,0 +1,11 @@ +_base_ = './catseg_vitl-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py' # noqa + +model = dict( + backbone=dict( + type='CLIPOVCATSeg', + clip_pretrained='ViT-G', + custom_clip_weights='~/CLIP-ViT-bigG-14-laion2B-39B-b160k'), + neck=dict( + text_guidance_dim=1280, + appearance_guidance_dim=512, + )) diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vith-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vith-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py new file mode 100644 index 0000000..2f09b8c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vith-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py @@ -0,0 +1,11 @@ +_base_ = './catseg_vitl-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py' # noqa + +model = dict( + backbone=dict( + type='CLIPOVCATSeg', + clip_pretrained='ViT-H', + custom_clip_weights='~/CLIP-ViT-H-14-laion2B-s32B-b79K'), + neck=dict( + text_guidance_dim=1024, + appearance_guidance_dim=512, + )) diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitl-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitl-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py new file mode 100644 index 0000000..bb4d57a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/configs/cat_seg/catseg_vitl-swin-b_4xb1-warmcoslr2e-4_adamw-80k_coco-stuff164k_384x384.py @@ -0,0 +1,72 @@ +_base_ = './catseg_vitb-r101_4xb2-warmcoslr2e-4-adamw-80k_coco-stuff164k-384x384.py' # noqa + +pretrained = 'https://download.openmmlab.com/mmsegmentation/v0.5/pretrain/swin/swin_base_patch4_window12_384_20220317-55b0104a.pth' # noqa +crop_size = (384, 384) +data_preprocessor = dict(size=crop_size) +model = dict( + backbone=dict( + type='CLIPOVCATSeg', + feature_extractor=dict( + _delete_=True, + type='SwinTransformer', + pretrain_img_size=384, + embed_dims=128, + depths=[2, 2, 18], + num_heads=[4, 8, 16], + window_size=12, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0., + attn_drop_rate=0., + drop_path_rate=0.3, + patch_norm=True, + out_indices=(0, 1, 2), + init_cfg=dict(type='Pretrained', checkpoint=pretrained)), + clip_pretrained='ViT-L/14@336px', + ), + neck=dict( + text_guidance_dim=768, + appearance_guidance_dim=512, + ), + decode_head=dict( + embed_dims=128, + decoder_guidance_dims=(256, 128), + )) + +# dataset settings +train_dataloader = dict( + batch_size=1, + num_workers=2, +) + +# training schedule for 80k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=4000) + +default_hooks = dict( + visualization=dict(type='SegVisualizationHook', draw=True, interval=4000)) + +# optimizer +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.0002, betas=(0.9, 0.999), weight_decay=0.0001), + paramwise_cfg=dict( + custom_keys={ + 'backbone.feature_extractor': dict(lr_mult=0.01), + 'backbone.clip_model.visual': dict(lr_mult=0.01) + })) + +# learning policy +param_scheduler = [ + # Use a linear warm-up at [0, 100) iterations + dict(type='LinearLR', start_factor=0.01, by_epoch=False, begin=0, end=500), + # Use a cosine learning rate at [100, 900) iterations + dict( + type='CosineAnnealingLR', + T_max=79500, + by_epoch=False, + begin=500, + end=80000), +] diff --git a/Seg_All_In_One_MMSeg/projects/CAT-Seg/utils/__init__.py b/Seg_All_In_One_MMSeg/projects/CAT-Seg/utils/__init__.py new file mode 100644 index 0000000..02d85f2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/CAT-Seg/utils/__init__.py @@ -0,0 +1,7 @@ +from .clip_templates import (IMAGENET_TEMPLATES, IMAGENET_TEMPLATES_SELECT, + IMAGENET_TEMPLATES_SELECT_CLIP, ViLD_templates) + +__all__ = [ + 'IMAGENET_TEMPLATES', 'IMAGENET_TEMPLATES_SELECT', + 'IMAGENET_TEMPLATES_SELECT_CLIP', 'ViLD_templates' +] diff --git a/Seg_All_In_One_MMSeg/projects/README.md b/Seg_All_In_One_MMSeg/projects/README.md new file mode 100644 index 0000000..5482c47 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/README.md @@ -0,0 +1,19 @@ +# Projects + +The OpenMMLab ecosystem can only grow through the contributions of the community. +Everyone is welcome to post their implementation of any great ideas in this folder! If you wish to start your own project, please go through the [example project](example_project/) for the best practice. For common questions about projects, please read our [faq](faq.md). + +## External Projects + +There are also selected external projects released in the community that use MMSegmentation: + +- [SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation](https://github.com/visual-attention-network/segnext) +- [Vision Transformer Adapter for Dense Predictions](https://github.com/czczup/ViT-Adapter) +- [UniFormer: Unifying Convolution and Self-attention for Visual Recognition](https://github.com/Sense-X/UniFormer) +- [Multi-Scale High-Resolution Vision Transformer for Semantic Segmentation](https://github.com/facebookresearch/HRViT) +- [ViTAE: Vision Transformer Advanced by Exploring Intrinsic Inductive Bias](https://github.com/ViTAE-Transformer/ViTAE-Transformer) +- [DAFormer: Improving Network Architectures and Training Strategies for Domain-Adaptive Semantic Segmentation](https://github.com/lhoyer/DAFormer) +- [MPViT : Multi-Path Vision Transformer for Dense Prediction](https://github.com/youngwanLEE/MPViT) +- [TopFormer: Token Pyramid Transformer for Mobile Semantic Segmentation](https://github.com/hustvl/TopFormer) + +Note: These projects are supported and maintained by their own contributors. The core maintainers of MMSegmentation only ensure the results are reproducible and the code quality meets its claim at the time each project was submitted, but they may not be responsible for future maintenance. diff --git a/Seg_All_In_One_MMSeg/projects/XDecoder/README.md b/Seg_All_In_One_MMSeg/projects/XDecoder/README.md new file mode 100644 index 0000000..3d55575 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/XDecoder/README.md @@ -0,0 +1,17 @@ +# X-Decoder + +> [X-Decoder: Generalized Decoding for Pixel, Image, and Language](https://arxiv.org/pdf/2212.11270.pdf) + + + +## Abstract + +We present X-Decoder, a generalized decoding model that can predict pixel-level segmentation and language tokens seamlessly. X-Decodert takes as input two types of queries: (i) generic non-semantic queries and (ii) semantic queries induced from text inputs, to decode different pixel-level and token-level outputs in the same semantic space. With such a novel design, X-Decoder is the first work that provides a unified way to support all types of image segmentation and a variety of vision-language (VL) tasks. Further, our design enables seamless interactions across tasks at different granularities and brings mutual benefits by learning a common and rich pixel-level visual-semantic understanding space, without any pseudo-labeling. After pretraining on a mixed set of a limited amount of segmentation data and millions of image-text pairs, X-Decoder exhibits strong transferability to a wide range of downstream tasks in both zero-shot and finetuning settings. Notably, it achieves (1) state-of-the-art results on open-vocabulary segmentation and referring segmentation on eight datasets; (2) better or competitive finetuned performance to other generalist and specialist models on segmentation and VL tasks; and (3) flexibility for efficient finetuning and novel task composition (e.g., referring captioning and image editing). + +

+ +
+ +## Usage + +We implement it based on [mmdetection](https://github.com/open-mmlab/mmdetection/), please refer to [mmdetection/projects/XDecoder](https://github.com/open-mmlab/mmdetection/tree/main/projects/XDecoder) for more details. diff --git a/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/README.md b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/README.md new file mode 100644 index 0000000..c774525 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/README.md @@ -0,0 +1,50 @@ +# BDD100K Dataset + +Support **`BDD100K Dataset`** + +## Description + +Author: CastleDream + +This project implements **`BDD100K Dataset`** + +### Dataset preparing + +Preparing `BDD100K Dataset` dataset following [BDD100K Dataset Preparing Guide](https://github.com/open-mmlab/mmsegmentation/tree/main/projects/mapillary_dataset/docs/en/user_guides/2_dataset_prepare.md#bdd100k) + +```none +mmsegmentation/data +└── bdd100k + ├── images + │ └── 10k + │ ├── test [2000 entries exceeds filelimit, not opening dir] + │ ├── train [7000 entries exceeds filelimit, not opening dir] + │ └── val [1000 entries exceeds filelimit, not opening dir] + └── labels + └── sem_seg + ├── colormaps + │ ├── train [7000 entries exceeds filelimit, not opening dir] + │ └── val [1000 entries exceeds filelimit, not opening dir] + ├── masks + │ ├── train [7000 entries exceeds filelimit, not opening dir] + │ └── val [1000 entries exceeds filelimit, not opening dir] + ├── polygons + │ ├── sem_seg_train.json + │ └── sem_seg_val.json + └── rles + ├── sem_seg_train.json + └── sem_seg_val.json +``` + +### Training commands + +```bash +%cd mmsegmentation +!python tools/train.py projects/bdd100k_dataset/configs/pspnet_r50-d8_4xb2-80k_bdd100k-512x1024.py\ +--work-dir your_work_dir +``` + +## Thanks + +- [\[Datasets\] Add Mapillary Vistas Datasets to MMSeg Core Package. #2576](https://github.com/open-mmlab/mmsegmentation/pull/2576/files) +- [\[Feature\] Support CIHP dataset #1493](https://github.com/open-mmlab/mmsegmentation/pull/1493/files) diff --git a/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/configs/_base_/datasets/bdd100k.py b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/configs/_base_/datasets/bdd100k.py new file mode 100644 index 0000000..24cec69 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/configs/_base_/datasets/bdd100k.py @@ -0,0 +1,70 @@ +# dataset settings +dataset_type = 'BDD100KDataset' +data_root = 'data/bdd100k/' + +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/10k/train', + seg_map_path='labels/sem_seg/masks/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/10k/val', + seg_map_path='labels/sem_seg/masks/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/configs/pspnet_r50-d8_4xb2-80k_bdd100k-512x1024.py b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/configs/pspnet_r50-d8_4xb2-80k_bdd100k-512x1024.py new file mode 100644 index 0000000..456d4c7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/configs/pspnet_r50-d8_4xb2-80k_bdd100k-512x1024.py @@ -0,0 +1,11 @@ +_base_ = [ + '../../../configs/_base_/models/pspnet_r50-d8.py', + './_base_/datasets/bdd100k.py', + '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_80k.py' +] +custom_imports = dict( + imports=['projects.bdd100k_dataset.mmseg.datasets.bdd100k']) +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict(data_preprocessor=data_preprocessor) diff --git a/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/docs/en/user_guides/2_dataset_prepare.md b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/docs/en/user_guides/2_dataset_prepare.md new file mode 100644 index 0000000..f2383cf --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/docs/en/user_guides/2_dataset_prepare.md @@ -0,0 +1,40 @@ +## BDD100K + +- You could download BDD100k datasets from [here](https://bdd-data.berkeley.edu/) after registration. + +- You can download images and masks by clicking `10K Images` button and `Segmentation` button. + +- After download, unzip by the following instructions: + + ```bash + unzip ~/bdd100k_images_10k.zip -d ~/mmsegmentation/data/ + unzip ~/bdd100k_sem_seg_labels_trainval.zip -d ~/mmsegmentation/data/ + ``` + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── bdd100k +│ │ ├── images +│ │ │ └── 10k +| │ │ │ ├── test +| │ │ │ ├── train +| │   │   │ └── val +│ │ └── labels +│ │ │ └── sem_seg +| │ │ │ ├── colormaps +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── masks +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── polygons +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +| │   │   │ └── rles +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +``` diff --git a/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/docs/zh_cn/user_guides/2_dataset_prepare.md b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/docs/zh_cn/user_guides/2_dataset_prepare.md new file mode 100644 index 0000000..64fb763 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/docs/zh_cn/user_guides/2_dataset_prepare.md @@ -0,0 +1,42 @@ +## BDD100K + +- 可以从[官方网站](https://bdd-data.berkeley.edu/) 下载 BDD100K数据集(语义分割任务主要是10K数据集),按照官网要求注册并登陆后,数据可以在[这里](https://bdd-data.berkeley.edu/portal.html#download)找到。 + +- 图像数据对应的名称是是`10K Images`, 语义分割标注对应的名称是`Segmentation` + +- 下载后,可以使用以下代码进行解压 + + ```bash + unzip ~/bdd100k_images_10k.zip -d ~/mmsegmentation/data/ + unzip ~/bdd100k_sem_seg_labels_trainval.zip -d ~/mmsegmentation/data/ + ``` + +就可以得到以下文件结构了: + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── bdd100k +│ │ ├── images +│ │ │ └── 10k +| │ │ │ ├── test +| │ │ │ ├── train +| │   │   │ └── val +│ │ └── labels +│ │ │ └── sem_seg +| │ │ │ ├── colormaps +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── masks +| │ │ │ │ ├──train +| │ │ │ │ └──val +| │ │ │ ├── polygons +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +| │   │   │ └── rles +| │ │ │ │ ├──sem_seg_train.json +| │ │ │ │ └──sem_seg_val.json +``` diff --git a/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/mmseg/datasets/bdd100k.py b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/mmseg/datasets/bdd100k.py new file mode 100644 index 0000000..e536de7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/bdd100k_dataset/mmseg/datasets/bdd100k.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. + +from mmseg.datasets.basesegdataset import BaseSegDataset + +# from mmseg.registry import DATASETS +# @DATASETS.register_module() + + +class BDD100KDataset(BaseSegDataset): + METAINFO = dict( + classes=('road', 'sidewalk', 'building', 'wall', 'fence', 'pole', + 'traffic light', 'traffic sign', 'vegetation', 'terrain', + 'sky', 'person', 'rider', 'car', 'truck', 'bus', 'train', + 'motorcycle', 'bicycle'), + palette=[[128, 64, 128], [244, 35, 232], [70, 70, 70], [102, 102, 156], + [190, 153, 153], [153, 153, 153], [250, 170, + 30], [220, 220, 0], + [107, 142, 35], [152, 251, 152], [70, 130, 180], + [220, 20, 60], [255, 0, 0], [0, 0, 142], [0, 0, 70], + [0, 60, 100], [0, 80, 100], [0, 0, 230], [119, 11, 32]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/example_project/README.md b/Seg_All_In_One_MMSeg/projects/example_project/README.md new file mode 100644 index 0000000..e4fd03c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/example_project/README.md @@ -0,0 +1,134 @@ +# Dummy ResNet Wrapper + +> A README.md template for releasing a project. +> +> All the fields in this README are **mandatory** for others to understand what you have achieved in this implementation. +> Please read our [Projects FAQ](../faq.md) if you still feel unclear about the requirements, or raise an [issue](https://github.com/open-mmlab/mmsegmentation/issues) to us! + +## Description + +> Share any information you would like others to know. For example: +> +> Author: @xxx. +> +> This is an implementation of \[XXX\]. + +Author: @xxx. + +This project implements a dummy ResNet wrapper, which literally does nothing new but prints "hello world" during initialization. + +## Usage + +> For a typical model, this section should contain the commands for training and testing. +> You are also suggested to dump your environment specification to env.yml by `conda env export > env.yml`. + +### Prerequisites + +- Python 3.7 +- PyTorch 1.6 or higher +- [MIM](https://github.com/open-mmlab/mim) v0.33 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc2 or higher + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `example_project/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Training commands + +```shell +mim train mmsegmentation configs/fcn_dummy-r50-d8_4xb2-40k_cityscapes-512x1024.py --work-dir work_dirs/dummy_resnet +``` + +To train on multiple GPUs, e.g. 8 GPUs, run the following command: + +```shell +mim train mmsegmentation configs/fcn_dummy-r50-d8_4xb2-40k_cityscapes-512x1024.py --work-dir work_dirs/dummy_resnet --launcher pytorch --gpus 8 +``` + +### Testing commands + +```shell +mim test mmsegmentation configs/fcn_dummy-r50-d8_4xb2-40k_cityscapes-512x1024.py --work-dir work_dirs/dummy_resnet --checkpoint ${CHECKPOINT_PATH} +``` + +> List the results as usually done in other model's README. \[Example\](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/fcn#results-and-models +> You should claim whether this is based on the pre-trained weights, which are converted from the official release; or it's a reproduced result obtained from retraining the model in this project + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ----: | ------------: | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FCN | R-50-D8 | 512x1024 | 40000 | 5.7 | 4.17 | 72.25 | 73.36 | [config](configs/fcn_dummy-r50-d8_4xb2-40k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_40k_cityscapes/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608-efe53f0d.pth) \| [log](https://download.openmmlab.com/mmsegmentation/v0.5/fcn/fcn_r50-d8_512x1024_40k_cityscapes/fcn_r50-d8_512x1024_40k_cityscapes_20200604_192608.log.json) | + +## Citation + +> You may remove this section if not applicable. + +```bibtex +@misc{mmseg2020, + title={{MMSegmentation}: OpenMMLab Semantic Segmentation Toolbox and Benchmark}, + author={MMSegmentation Contributors}, + howpublished = {\url{https://github.com/open-mmlab/mmsegmentation}}, + year={2020} +} +``` + +## Checklist + +Here is a checklist illustrating a usual development workflow of a successful project, and also serves as an overview of this project's progress. + +> The PIC (person in charge) or contributors of this project should check all the items that they believe have been finished, which will further be verified by codebase maintainers via a PR. + +> OpenMMLab's maintainer will review the code to ensure the project's quality. Reaching the first milestone means that this project suffices the minimum requirement of being merged into 'projects/'. But this project is only eligible to become a part of the core package upon attaining the last milestone. + +> Note that keeping this section up-to-date is crucial not only for this project's developers but the entire community, since there might be some other contributors joining this project and deciding their starting point from this list. It also helps maintainers accurately estimate time and effort on further code polishing, if needed. + +> A project does not necessarily have to be finished in a single PR, but it's essential for the project to at least reach the first milestone in its very first PR. + +- [ ] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [ ] Finish the code + +> The code's design shall follow existing interfaces and convention. For example, each model component should be registered into `mmseg.registry.MODELS` and configurable via a config file. + +- [ ] Basic docstrings & proper citation + +> Each major object should contain a docstring, describing its functionality and arguments. If you have adapted the code from other open-source projects, don't forget to cite the source project in docstring and make sure your behavior is not against its license. Typically, we do not accept any code snippet under GPL license. [A Short Guide to Open Source Licenses](https://medium.com/nationwide-technology/a-short-guide-to-open-source-licenses-cf5b1c329edd) + +- [ ] Test-time correctness + +> If you are reproducing the result from a paper, make sure your model's inference-time performance matches that in the original paper. The weights usually could be obtained by simply renaming the keys in the official pre-trained weights. This test could be skipped though, if you are able to prove the training-time correctness and check the second milestone. + +- [ ] A full README + +> As this template does. + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +> If you are reproducing the result from a paper, checking this item means that you should have trained your model from scratch based on the original paper's specification and verified that the final result matches the report within a minor error range. + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + +> Ideally *all* the methods should have [type hints](https://www.pythontutorial.net/python-basics/python-type-hints/) and [docstrings](https://google.github.io/styleguide/pyguide.html#381-docstrings). [Example](https://github.com/open-mmlab/mmsegmentation/blob/main/mmseg/utils/io.py#L9) + +- [ ] Unit tests + +> Unit tests for each module are required. [Example](https://github.com/open-mmlab/mmsegmentation/blob/main/tests/test_utils/test_io.py#L14) + +- [ ] Code polishing + +> Refactor your code according to reviewer's comment. + +- [ ] Metafile.yml + +> It will be parsed by MIM and Inferencer. [Example](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/fcn.yml) + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +> In particular, you may have to refactor this README into a standard one. [Example](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/fcn/README.md) + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/example_project/configs/fcn_dummy-r50-d8_4xb2-40k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/projects/example_project/configs/fcn_dummy-r50-d8_4xb2-40k_cityscapes-512x1024.py new file mode 100644 index 0000000..4301536 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/example_project/configs/fcn_dummy-r50-d8_4xb2-40k_cityscapes-512x1024.py @@ -0,0 +1,8 @@ +_base_ = ['mmseg::fcn/fcn_r50-d8_4xb2-40k_cityscapes-512x1024.py'] + +custom_imports = dict(imports=['dummy']) + +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, backbone=dict(type='DummyResNet')) diff --git a/Seg_All_In_One_MMSeg/projects/example_project/dummy/__init__.py b/Seg_All_In_One_MMSeg/projects/example_project/dummy/__init__.py new file mode 100644 index 0000000..70df789 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/example_project/dummy/__init__.py @@ -0,0 +1,3 @@ +from .dummy_resnet import DummyResNet + +__all__ = ['DummyResNet'] diff --git a/Seg_All_In_One_MMSeg/projects/example_project/dummy/dummy_resnet.py b/Seg_All_In_One_MMSeg/projects/example_project/dummy/dummy_resnet.py new file mode 100644 index 0000000..a510eaf --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/example_project/dummy/dummy_resnet.py @@ -0,0 +1,14 @@ +from mmseg.models.backbones import ResNetV1c +from mmseg.registry import MODELS + + +@MODELS.register_module() +class DummyResNet(ResNetV1c): + """Implements a dummy ResNet wrapper for demonstration purpose. + Args: + **kwargs: All the arguments are passed to the parent class. + """ + + def __init__(self, **kwargs) -> None: + print('Hello world!') + super().__init__(**kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/faq.md b/Seg_All_In_One_MMSeg/projects/faq.md new file mode 100644 index 0000000..74b292b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/faq.md @@ -0,0 +1,19 @@ +Q1: Why set up `projects/` folder? + +Implementing new models and features into OpenMMLab's algorithm libraries could be troublesome due to the rigorous requirements on code quality, which could hinder the fast iteration of SOTA models and might discourage our members from sharing their latest outcomes here. And that's why we have this `projects/` folder now, where some experimental features, frameworks and models are placed, only needed to satisfy the minimum requirement on the code quality, and can be used as standalone libraries. Users are welcome to use them if they [use MMSegmentation from source](https://mmsegmentation.readthedocs.io/en/latest/get_started.html#best-practices). + +Q2: Why should there be a checklist for a project? + +This checkelist is crucial not only for this project's developers but the entire community, since there might be some other contributors joining this project and deciding their starting point from this list. It also helps maintainers accurately estimate time and effort on further code polishing, if needed. + +Q3: What kind of PR will be merged? + +Reaching the first milestone means that this project suffices the minimum requirement of being merged into 'projects/'. That is, the very first PR of a project must have all the terms in the first milestone checked. We do not have any extra requirements on the project's following PRs, so they can be a minor bug fix or update, and do not have to achieve one milestone at once. But keep in mind that this project is only eligible to become a part of the core package upon attaining the last milestone. + +Q4: Compared to other models in the core packages, why do the model implementations in projects have different training/testing commands? + +Projects are organized independently from the core package, and therefore their modules cannot be directly imported by train.py and test.py. Each model implementation in projects should either use `mim` for training/testing as suggested in the example project or provide a custom train.py/test.py. + +Q5: How to debug a project with a debugger? + +Debugger makes our lives easier, but using it becomes a bit tricky if we have to train/test a model via `mim`. The way to circumvent that is that we can take advantage of relative path to import these modules. Assuming that we are developing a project X and the core modules are placed under `projects/X/modules`, then simply adding `custom_imports = dict(imports='projects.X.modules')` to the config allows us to debug from usual entrypoints (e.g. `tools/train.py`) from the root directory of the algorithm library. Just don't forget to remove 'projects.X' before project publishment. diff --git a/Seg_All_In_One_MMSeg/projects/gid_dataset/configs/_base_/datasets/gid.py b/Seg_All_In_One_MMSeg/projects/gid_dataset/configs/_base_/datasets/gid.py new file mode 100644 index 0000000..f721810 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/gid_dataset/configs/_base_/datasets/gid.py @@ -0,0 +1,67 @@ +# dataset settings +dataset_type = 'GID_Dataset' # 注册的类名 +data_root = 'data/gid/' # 数据集根目录 +crop_size = (256, 256) # 图像裁剪大小 +train_pipeline = [ + dict(type='LoadImageFromFile'), # 从文件中加载图像 + dict(type='LoadAnnotations'), # 从文件中加载标注 + dict( + type='RandomResize', # 随机缩放 + scale=(512, 512), # 缩放尺寸 + ratio_range=(0.5, 2.0), # 缩放比例范围 + keep_ratio=True), # 是否保持长宽比 + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), # 随机裁剪 + dict(type='RandomFlip', prob=0.5), # 随机翻转 + dict(type='PhotoMetricDistortion'), # 图像增强 + dict(type='PackSegInputs') # 打包数据 +] +test_pipeline = [ + dict(type='LoadImageFromFile'), # 从文件中加载图像 + dict(type='Resize', scale=(256, 256), keep_ratio=True), # 缩放 + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), # 从文件中加载标注 + dict(type='PackSegInputs') # 打包数据 +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] # 多尺度预测缩放比例 +tta_pipeline = [ # 多尺度测试 + dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( # 训练数据加载器 + batch_size=2, # 训练时的数据批量大小 + num_workers=4, # 数据加载线程数 + persistent_workers=True, # 是否持久化线程 + sampler=dict(type='InfiniteSampler', shuffle=True), # 无限采样器 + dataset=dict( + type=dataset_type, # 数据集类名 + data_root=data_root, # 数据集根目录 + data_prefix=dict( + img_path='img_dir/train', + seg_map_path='ann_dir/train'), # 训练集图像和标注路径 + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, # 验证时的数据批量大小 + num_workers=4, # 数据加载线程数 + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='img_dir/val', seg_map_path='ann_dir/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/gid_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_gid-256x256.py b/Seg_All_In_One_MMSeg/projects/gid_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_gid-256x256.py new file mode 100644 index 0000000..70cb600 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/gid_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_gid-256x256.py @@ -0,0 +1,15 @@ +_base_ = [ + '../../../configs/_base_/models/deeplabv3plus_r50-d8.py', + './_base_/datasets/gid.py', '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_240k.py' +] +custom_imports = dict(imports=['projects.gid_dataset.mmseg.datasets.gid']) + +crop_size = (256, 256) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101), + decode_head=dict(num_classes=6), + auxiliary_head=dict(num_classes=6)) diff --git a/Seg_All_In_One_MMSeg/projects/gid_dataset/mmseg/datasets/gid.py b/Seg_All_In_One_MMSeg/projects/gid_dataset/mmseg/datasets/gid.py new file mode 100644 index 0000000..a9e8c51 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/gid_dataset/mmseg/datasets/gid.py @@ -0,0 +1,55 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.datasets.basesegdataset import BaseSegDataset +from mmseg.registry import DATASETS + + +# 注册数据集类 +@DATASETS.register_module() +class GID_Dataset(BaseSegDataset): + """Gaofen Image Dataset (GID) + + Dataset paper link: + https://www.sciencedirect.com/science/article/pii/S0034425719303414 + https://x-ytong.github.io/project/GID.html + + GID 6 classes: others, built-up, farmland, forest, meadow, water + + In this example, select 15 images from GID dataset as training set, + and select 5 images as validation set. + The selected images are listed as follows: + + GF2_PMS1__L1A0000647767-MSS1 + GF2_PMS1__L1A0001064454-MSS1 + GF2_PMS1__L1A0001348919-MSS1 + GF2_PMS1__L1A0001680851-MSS1 + GF2_PMS1__L1A0001680853-MSS1 + GF2_PMS1__L1A0001680857-MSS1 + GF2_PMS1__L1A0001757429-MSS1 + GF2_PMS2__L1A0000607681-MSS2 + GF2_PMS2__L1A0000635115-MSS2 + GF2_PMS2__L1A0000658637-MSS2 + GF2_PMS2__L1A0001206072-MSS2 + GF2_PMS2__L1A0001471436-MSS2 + GF2_PMS2__L1A0001642620-MSS2 + GF2_PMS2__L1A0001787089-MSS2 + GF2_PMS2__L1A0001838560-MSS2 + + The ``img_suffix`` is fixed to '.tif' and ``seg_map_suffix`` is + fixed to '.tif' for GID. + """ + METAINFO = dict( + classes=('Others', 'Built-up', 'Farmland', 'Forest', 'Meadow', + 'Water'), + palette=[[0, 0, 0], [255, 0, 0], [0, 255, 0], [0, 255, 255], + [255, 255, 0], [0, 0, 255]]) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=None, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/gid_dataset/tools/dataset_converters/gid.py b/Seg_All_In_One_MMSeg/projects/gid_dataset/tools/dataset_converters/gid.py new file mode 100644 index 0000000..d95654a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/gid_dataset/tools/dataset_converters/gid.py @@ -0,0 +1,181 @@ +import argparse +import glob +import math +import os +import os.path as osp + +import mmcv +import numpy as np +from mmengine.utils import ProgressBar, mkdir_or_exist + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert GID dataset to mmsegmentation format') + parser.add_argument('dataset_img_path', help='GID images folder path') + parser.add_argument('dataset_label_path', help='GID labels folder path') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument( + '-o', '--out_dir', help='output path', default='data/gid') + parser.add_argument( + '--clip_size', + type=int, + help='clipped size of image after preparation', + default=256) + parser.add_argument( + '--stride_size', + type=int, + help='stride of clipping original images', + default=256) + args = parser.parse_args() + return args + + +GID_COLORMAP = dict( + Background=(0, 0, 0), # 0-背景-黑色 + Building=(255, 0, 0), # 1-建筑-红色 + Farmland=(0, 255, 0), # 2-农田-绿色 + Forest=(0, 0, 255), # 3-森林-蓝色 + Meadow=(255, 255, 0), # 4-草地-黄色 + Water=(0, 0, 255) # 5-水-蓝色 +) +palette = list(GID_COLORMAP.values()) +classes = list(GID_COLORMAP.keys()) + + +# 用列表来存一个 RGB 和一个类别的对应 +def colormap2label(palette): + colormap2label_list = np.zeros(256**3, dtype=np.longlong) + for i, colormap in enumerate(palette): + colormap2label_list[(colormap[0] * 256 + colormap[1]) * 256 + + colormap[2]] = i + return colormap2label_list + + +# 给定那个列表,和vis_png然后生成masks_png +def label_indices(RGB_label, colormap2label_list): + RGB_label = RGB_label.astype('int32') + idx = (RGB_label[:, :, 0] * 256 + + RGB_label[:, :, 1]) * 256 + RGB_label[:, :, 2] + return colormap2label_list[idx] + + +def RGB2mask(RGB_label, colormap2label_list): + mask_label = label_indices(RGB_label, colormap2label_list) + return mask_label + + +colormap2label_list = colormap2label(palette) + + +def clip_big_image(image_path, clip_save_dir, args, to_label=False): + """Original image of GID dataset is very large, thus pre-processing of them + is adopted. + + Given fixed clip size and stride size to generate + clipped image, the intersection of width and height is determined. + For example, given one 6800 x 7200 original image, the clip size is + 256 and stride size is 256, thus it would generate 29 x 27 = 783 images + whose size are all 256 x 256. + """ + + image = mmcv.imread(image_path, channel_order='rgb') + # image = mmcv.bgr2gray(image) + + h, w, c = image.shape + clip_size = args.clip_size + stride_size = args.stride_size + + num_rows = math.ceil((h - clip_size) / stride_size) if math.ceil( + (h - clip_size) / + stride_size) * stride_size + clip_size >= h else math.ceil( + (h - clip_size) / stride_size) + 1 + num_cols = math.ceil((w - clip_size) / stride_size) if math.ceil( + (w - clip_size) / + stride_size) * stride_size + clip_size >= w else math.ceil( + (w - clip_size) / stride_size) + 1 + + x, y = np.meshgrid(np.arange(num_cols + 1), np.arange(num_rows + 1)) + xmin = x * clip_size + ymin = y * clip_size + + xmin = xmin.ravel() + ymin = ymin.ravel() + xmin_offset = np.where(xmin + clip_size > w, w - xmin - clip_size, + np.zeros_like(xmin)) + ymin_offset = np.where(ymin + clip_size > h, h - ymin - clip_size, + np.zeros_like(ymin)) + boxes = np.stack([ + xmin + xmin_offset, ymin + ymin_offset, + np.minimum(xmin + clip_size, w), + np.minimum(ymin + clip_size, h) + ], + axis=1) + + if to_label: + image = RGB2mask(image, colormap2label_list) + + for count, box in enumerate(boxes): + start_x, start_y, end_x, end_y = box + clipped_image = image[start_y:end_y, + start_x:end_x] if to_label else image[ + start_y:end_y, start_x:end_x, :] + img_name = osp.basename(image_path).replace('.tif', '') + img_name = img_name.replace('_label', '') + if count % 3 == 0: + mmcv.imwrite( + clipped_image.astype(np.uint8), + osp.join( + clip_save_dir.replace('train', 'val'), + f'{img_name}_{start_x}_{start_y}_{end_x}_{end_y}.png')) + else: + mmcv.imwrite( + clipped_image.astype(np.uint8), + osp.join( + clip_save_dir, + f'{img_name}_{start_x}_{start_y}_{end_x}_{end_y}.png')) + count += 1 + + +def main(): + args = parse_args() + """ + According to this paper: https://ieeexplore.ieee.org/document/9343296/ + select 15 images contained in GID, , which cover the whole six + categories, to generate train set and validation set. + + """ + + if args.out_dir is None: + out_dir = osp.join('data', 'gid') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'val')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'val')) + + src_path_list = glob.glob(os.path.join(args.dataset_img_path, '*.tif')) + print(f'Find {len(src_path_list)} pictures') + + prog_bar = ProgressBar(len(src_path_list)) + + dst_img_dir = osp.join(out_dir, 'img_dir', 'train') + dst_label_dir = osp.join(out_dir, 'ann_dir', 'train') + + for i, img_path in enumerate(src_path_list): + label_path = osp.join( + args.dataset_label_path, + osp.basename(img_path.replace('.tif', '_label.tif'))) + + clip_big_image(img_path, dst_img_dir, args, to_label=False) + clip_big_image(label_path, dst_label_dir, args, to_label=True) + prog_bar.update() + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/projects/gid_dataset/tools/dataset_converters/gid_select15imgFromAll.py b/Seg_All_In_One_MMSeg/projects/gid_dataset/tools/dataset_converters/gid_select15imgFromAll.py new file mode 100644 index 0000000..d3eeff2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/gid_dataset/tools/dataset_converters/gid_select15imgFromAll.py @@ -0,0 +1,75 @@ +import argparse +import os +import shutil + +# select 15 images from GID dataset + +img_list = [ + 'GF2_PMS1__L1A0000647767-MSS1.tif', 'GF2_PMS1__L1A0001064454-MSS1.tif', + 'GF2_PMS1__L1A0001348919-MSS1.tif', 'GF2_PMS1__L1A0001680851-MSS1.tif', + 'GF2_PMS1__L1A0001680853-MSS1.tif', 'GF2_PMS1__L1A0001680857-MSS1.tif', + 'GF2_PMS1__L1A0001757429-MSS1.tif', 'GF2_PMS2__L1A0000607681-MSS2.tif', + 'GF2_PMS2__L1A0000635115-MSS2.tif', 'GF2_PMS2__L1A0000658637-MSS2.tif', + 'GF2_PMS2__L1A0001206072-MSS2.tif', 'GF2_PMS2__L1A0001471436-MSS2.tif', + 'GF2_PMS2__L1A0001642620-MSS2.tif', 'GF2_PMS2__L1A0001787089-MSS2.tif', + 'GF2_PMS2__L1A0001838560-MSS2.tif' +] + +labels_list = [ + 'GF2_PMS1__L1A0000647767-MSS1_label.tif', + 'GF2_PMS1__L1A0001064454-MSS1_label.tif', + 'GF2_PMS1__L1A0001348919-MSS1_label.tif', + 'GF2_PMS1__L1A0001680851-MSS1_label.tif', + 'GF2_PMS1__L1A0001680853-MSS1_label.tif', + 'GF2_PMS1__L1A0001680857-MSS1_label.tif', + 'GF2_PMS1__L1A0001757429-MSS1_label.tif', + 'GF2_PMS2__L1A0000607681-MSS2_label.tif', + 'GF2_PMS2__L1A0000635115-MSS2_label.tif', + 'GF2_PMS2__L1A0000658637-MSS2_label.tif', + 'GF2_PMS2__L1A0001206072-MSS2_label.tif', + 'GF2_PMS2__L1A0001471436-MSS2_label.tif', + 'GF2_PMS2__L1A0001642620-MSS2_label.tif', + 'GF2_PMS2__L1A0001787089-MSS2_label.tif', + 'GF2_PMS2__L1A0001838560-MSS2_label.tif' +] + + +def parse_args(): + parser = argparse.ArgumentParser( + description='From 150 images of GID dataset to select 15 images') + parser.add_argument('dataset_img_dir', help='150 GID images folder path') + parser.add_argument('dataset_label_dir', help='150 GID labels folder path') + + parser.add_argument('dest_img_dir', help='15 GID images folder path') + parser.add_argument('dest_label_dir', help='15 GID labels folder path') + + args = parser.parse_args() + + return args + + +def main(): + """This script is used to select 15 images from GID dataset, According to + paper: https://ieeexplore.ieee.org/document/9343296/""" + args = parse_args() + + img_path = args.dataset_img_dir + label_path = args.dataset_label_dir + + dest_img_dir = args.dest_img_dir + dest_label_dir = args.dest_label_dir + + # copy images of 'img_list' to 'desr_dir' + print('Copy images of img_list to desr_dir ing...') + for img in img_list: + shutil.copy(os.path.join(img_path, img), dest_img_dir) + print('Done!') + + print('copy labels of labels_list to desr_dir ing...') + for label in labels_list: + shutil.copy(os.path.join(label_path, label), dest_label_dir) + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/projects/gid_dataset/user_guides/2_dataset_prepare.md b/Seg_All_In_One_MMSeg/projects/gid_dataset/user_guides/2_dataset_prepare.md new file mode 100644 index 0000000..63bd4d4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/gid_dataset/user_guides/2_dataset_prepare.md @@ -0,0 +1,53 @@ +## Gaofen Image Dataset (GID) + +- GID 数据集可在[此处](https://x-ytong.github.io/project/GID.html)进行下载。 +- GID 数据集包含 150 张 6800x7200 的大尺寸图像,标签为 RGB 标签。 +- 根据[文献](https://ieeexplore.ieee.org/document/9343296/),此处选择 15 张图像生成训练集和验证集,该 15 张图像包含了所有六类信息。所选的图像名称如下: + +```None + GF2_PMS1__L1A0000647767-MSS1 + GF2_PMS1__L1A0001064454-MSS1 + GF2_PMS1__L1A0001348919-MSS1 + GF2_PMS1__L1A0001680851-MSS1 + GF2_PMS1__L1A0001680853-MSS1 + GF2_PMS1__L1A0001680857-MSS1 + GF2_PMS1__L1A0001757429-MSS1 + GF2_PMS2__L1A0000607681-MSS2 + GF2_PMS2__L1A0000635115-MSS2 + GF2_PMS2__L1A0000658637-MSS2 + GF2_PMS2__L1A0001206072-MSS2 + GF2_PMS2__L1A0001471436-MSS2 + GF2_PMS2__L1A0001642620-MSS2 + GF2_PMS2__L1A0001787089-MSS2 + GF2_PMS2__L1A0001838560-MSS2 +``` + +这里也提供了一个脚本来方便的筛选出15张图像, + +``` +python projects/gid_dataset/tools/dataset_converters/gid_select15imgFromAll.py {150 张图像的路径} {150 张标签的路径} {15 张图像的路径} {15 张标签的路径} +``` + +在选择出 15 张图像后,执行以下命令进行裁切及标签的转换,需要修改为您所存储 15 张图像及标签的路径。 + +``` +python projects/gid_dataset/tools/dataset_converters/gid.py {15 张图像的路径} {15 张标签的路径} +``` + +完成裁切后的 GID 数据结构如下: + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── gid +│ │ ├── ann_dir +| │ │ │ ├── train +| │ │ │ ├── val +│ │ ├── img_dir +| │ │ │ ├── train +| │ │ │ ├── val + +``` diff --git a/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/README.md b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/README.md new file mode 100644 index 0000000..7ee6e98 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/README.md @@ -0,0 +1,34 @@ +# HSI Drive 2.0 Dataset + +Support **`HSI Drive 2.0 Dataset`** + +## Description + +Author: Jon Gutierrez + +This project implements **`HSI Drive 2.0 Dataset`** + +### Dataset preparing + +Preparing `HSI Drive 2.0 Dataset` dataset following [HSI Drive 2.0 Dataset Preparing Guide](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/en/user_guides/2_dataset_prepare.md#hsi-drive-2.0) + +```none +mmsegmentation/data +└── HSIDrive20 + ├── images + │ |── training [] + │ |── validation [] + │ |── test [] + └── labels + │ |── training [] + │ |── validation [] + │ |── test [] +``` + +### Training commands + +```bash +%cd mmsegmentation +!python tools/train.py projects/hsidrive20_dataset/configs/unet-s5-d16_fcn_4xb4-160k_hsidrive-208x400.py\ +--work-dir your_work_dir +``` diff --git a/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/configs/_base_/datasets/hsi_drive.py b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/configs/_base_/datasets/hsi_drive.py new file mode 100644 index 0000000..3114262 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/configs/_base_/datasets/hsi_drive.py @@ -0,0 +1,50 @@ +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] + +train_dataloader = dict( + batch_size=1, + num_workers=1, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type='HSIDrive20', + data_root='data/HSIDrive20', + data_prefix=dict( + img_path='images/training', seg_map_path='annotations/training'), + pipeline=train_pipeline)) + +val_dataloader = dict( + batch_size=1, + num_workers=1, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='HSIDrive20', + data_root='data/HSIDrive20', + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) + +test_dataloader = dict( + batch_size=1, + num_workers=1, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type='HSIDrive20', + data_root='data/HSIDrive20', + data_prefix=dict( + img_path='images/test', seg_map_path='annotations/test'), + pipeline=test_pipeline)) + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU'], ignore_index=0) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/configs/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/configs/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py new file mode 100644 index 0000000..d5eab91 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/configs/unet-s5-d16_fcn_4xb4-160k_hsidrive-192x384.py @@ -0,0 +1,58 @@ +_base_ = [ + '../../../configs/_base_/models/fcn_unet_s5-d16.py', + './_base_/datasets/hsi_drive.py', + '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_160k.py' +] + +custom_imports = dict( + imports=['projects.hsidrive20_dataset.mmseg.datasets.hsi_drive']) + +crop_size = (192, 384) +data_preprocessor = dict( + type='SegDataPreProcessor', + size=crop_size, + mean=None, + std=None, + bgr_to_rgb=None, + pad_val=0, + seg_pad_val=255) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(in_channels=25), + decode_head=dict( + ignore_index=0, + num_classes=11, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + avg_non_ignore=True)), + auxiliary_head=dict( + ignore_index=0, + num_classes=11, + loss_decode=dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + avg_non_ignore=True)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) + +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='RandomCrop', crop_size=crop_size), + dict(type='PackSegInputs') +] + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='RandomCrop', crop_size=crop_size), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] + +train_dataloader = dict(dataset=dict(pipeline=train_pipeline)) +test_dataloader = dict(dataset=dict(pipeline=test_pipeline)) diff --git a/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/docs/en/user_guides/2_dataset_prepare.md b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/docs/en/user_guides/2_dataset_prepare.md new file mode 100644 index 0000000..1d4ac8c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/docs/en/user_guides/2_dataset_prepare.md @@ -0,0 +1,42 @@ +## HSI Drive 2.0 + +- You could download HSI Drive 2.0 dataset from [here](https://ipaccess.ehu.eus/HSI-Drive/#download) after just sending an email to gded@ehu.eus with the subject "download HSI-Drive". You will receive a password to uncompress the files. + +- After download, unzip by the following instructions: + + ```bash + 7z x -p"password" ./HSI_Drive_v2_0_Phyton.zip + + mv ./HSIDrive20 path_to_mmsegmentation/data + mv ./HSI_Drive_v2_0_release_notes_Python_version.md path_to_mmsegmentation/data + mv ./image_numbering.pdf path_to_mmsegmentation/data + ``` + +- After unzip, you get + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── HSIDrive20 +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── images_MF +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── RGB +│ │ ├── training_filenames.txt +│ │ ├── validation_filenames.txt +│ │ ├── test_filenames.txt +│ ├── HSI_Drive_v2_0_release_notes_Python_version.md +│ ├── image_numbering.pdf +``` diff --git a/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/docs/zh_cn/user_guides/2_dataset_prepare.md b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/docs/zh_cn/user_guides/2_dataset_prepare.md new file mode 100644 index 0000000..dbf704a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/docs/zh_cn/user_guides/2_dataset_prepare.md @@ -0,0 +1,42 @@ +## HSI Drive 2.0 + +- 您可以从以下位置下载 HSI Drive 2.0 数据集 [here](https://ipaccess.ehu.eus/HSI-Drive/#download) 刚刚向 gded@ehu.eus 发送主题为“下载 HSI-Drive”的电子邮件后 您将收到解压缩文件的密码. + +- 下载后,按照以下说明解压: + + ```bash + 7z x -p"password" ./HSI_Drive_v2_0_Phyton.zip + + mv ./HSIDrive20 path_to_mmsegmentation/data + mv ./HSI_Drive_v2_0_release_notes_Python_version.md path_to_mmsegmentation/data + mv ./image_numbering.pdf path_to_mmsegmentation/data + ``` + +- 解压后得到: + +```none +mmsegmentation +├── mmseg +├── tools +├── configs +├── data +│ ├── HSIDrive20 +│ │ ├── images +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── annotations +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── images_MF +│ │ │ ├── training +│ │ │ ├── validation +│ │ │ ├── test +│ │ ├── RGB +│ │ ├── training_filenames.txt +│ │ ├── validation_filenames.txt +│ │ ├── test_filenames.txt +│ ├── HSI_Drive_v2_0_release_notes_Python_version.md +│ ├── image_numbering.pdf +``` diff --git a/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/mmseg/datasets/hsi_drive.py b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/mmseg/datasets/hsi_drive.py new file mode 100644 index 0000000..f8589b0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hsidrive20_dataset/mmseg/datasets/hsi_drive.py @@ -0,0 +1,23 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.datasets import BaseSegDataset + +# from mmseg.registry import DATASETS + +classes_exp = ('unlabelled', 'road', 'road marks', 'vegetation', + 'painted metal', 'sky', 'concrete', 'pedestrian', 'water', + 'unpainted metal', 'glass') +palette_exp = [[0, 0, 0], [77, 77, 77], [255, 255, 255], [0, 255, 0], + [255, 0, 0], [0, 0, 255], [102, 51, 0], [255, 255, 0], + [0, 207, 250], [255, 166, 0], [0, 204, 204]] + + +# @DATASETS.register_module() +class HSIDrive20Dataset(BaseSegDataset): + METAINFO = dict(classes=classes_exp, palette=palette_exp) + + def __init__(self, + img_suffix='.npy', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/hssn/README.md b/Seg_All_In_One_MMSeg/projects/hssn/README.md new file mode 100644 index 0000000..9dcbf37 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/README.md @@ -0,0 +1,91 @@ +# HSSN + +## Description + +Author: AI-Tianlong + +This project implements `Deep Hierarchical Semantic Segmentation` inference on `cityscapes` dataset + +## Usage + +### Prerequisites + +- Python 3.8 +- PyTorch 1.6 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 +- mmcv v2.0.0rc4 +- mmengine >=0.4.0 + +### Dataset preparing + +Preparing `cityscapes` dataset following this [Dataset Preparing Guide](https://github.com/open-mmlab/mmsegmentation/blob/master/docs/en/dataset_prepare.md#prepare-datasets) + +### Testing commands + +Please put [`hieraseg_deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024_20230112_125023-bc59a3d1.pth`](https://download.openmmlab.com/mmsegmentation/v0.5/hieraseg/hieraseg_deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024_20230112_125023-bc59a3d1.pth) to `mmsegmentation/checkpoints` + +#### Multi-GPUs Test + +```bash +# --tta optional, multi-scale test, need mmengine >=0.4.0 +bash tools/dist_test.sh [configs] [model weights] [number of gpu] --tta +``` + +#### Example + +```shell +bash tools/dist_test.sh projects/hssn/configs/hssn/hieraseg_deeplabv3plus_r101-d8_4xb2-80l_cityscapes-512x1024.py checkpoints/hieraseg_deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024_20230112_125023-bc59a3d1.pth 2 --tta +``` + +## Results + +### Cityscapes + +| Method | Backbone | Crop Size | mIoU | mIoU (ms+flip) | config | model | +| :--------: | :------: | :-------: | :---: | :------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------: | +| DeeplabV3+ | R-101-D8 | 512x1024 | 81.61 | 82.71 | [config](https://github.com/open-mmlab/mmsegmentation/tree/main/projects/HieraSeg/configs/hieraseg/hieraseg_deeplabv3plus_r101-d8_4xb2-80l_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/hieraseg/hieraseg_deeplabv3plus_r101-d8_4xb2-80k_cityscapes-512x1024_20230112_125023-bc59a3d1.pth) | + + + +## Citation + +This project is modified from [qhanghu/HSSN_pytorch](https://github.com/qhanghu/HSSN_pytorch) + +```bibtex +@article{li2022deep, + title={Deep Hierarchical Semantic Segmentation}, + author={Li, Liulei and Zhou, Tianfei and Wang, Wenguan and Li, Jianwu and Yang, Yi}, + journal={CVPR}, + year={2022} +} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/datasets/cityscapes.py b/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/datasets/cityscapes.py new file mode 100644 index 0000000..1698e04 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/datasets/cityscapes.py @@ -0,0 +1,67 @@ +# dataset settings +dataset_type = 'CityscapesDataset' +data_root = 'data/cityscapes/' +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=2, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='leftImg8bit/train', seg_map_path='gtFine/train'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='leftImg8bit/val', seg_map_path='gtFine/val'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/default_runtime.py b/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/default_runtime.py new file mode 100644 index 0000000..272b4d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/default_runtime.py @@ -0,0 +1,15 @@ +default_scope = 'mmseg' +env_cfg = dict( + cudnn_benchmark=True, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict(by_epoch=False) +log_level = 'INFO' +load_from = None +resume = False + +tta_model = dict(type='SegTTAModel') diff --git a/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/models/deeplabv3plus_r50-d8_vd_contrast.py b/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/models/deeplabv3plus_r50-d8_vd_contrast.py new file mode 100644 index 0000000..a6af45c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/models/deeplabv3plus_r50-d8_vd_contrast.py @@ -0,0 +1,55 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + backbone=dict( + type='ResNetV1d', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='DepthwiseSeparableASPPContrastHead', + in_channels=2048, + in_index=3, + channels=512, + dilations=(1, 12, 24, 36), + c1_in_channels=256, + c1_channels=48, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + proj='convmlp', + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/schedules/schedule_80k.py b/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/schedules/schedule_80k.py new file mode 100644 index 0000000..0dcd6c4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/configs/_base_/schedules/schedule_80k.py @@ -0,0 +1,24 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=80000, + by_epoch=False) +] +# training schedule for 80k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=8000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=8000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/projects/hssn/configs/hssn/hieraseg_deeplabv3plus_r101-d8_4xb2-80l_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/projects/hssn/configs/hssn/hieraseg_deeplabv3plus_r101-d8_4xb2-80l_cityscapes-512x1024.py new file mode 100644 index 0000000..8f04a2d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/configs/hssn/hieraseg_deeplabv3plus_r101-d8_4xb2-80l_cityscapes-512x1024.py @@ -0,0 +1,21 @@ +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8_vd_contrast.py', + '../_base_/datasets/cityscapes.py', '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] + +custom_imports = dict(imports=[ + 'projects.hssn.decode_head.sep_aspp_contrast_head', + 'projects.hssn.losses.hiera_triplet_loss_cityscape' +]) + +model = dict( + pretrained=None, + backbone=dict(depth=101), + decode_head=dict( + num_classes=26, + loss_decode=dict( + type='HieraTripletLossCityscape', num_classes=19, + loss_weight=1.0)), + auxiliary_head=dict(num_classes=19), + test_cfg=dict(mode='whole', is_hiera=True, hiera_num_classes=7)) diff --git a/Seg_All_In_One_MMSeg/projects/hssn/decode_head/__init__.py b/Seg_All_In_One_MMSeg/projects/hssn/decode_head/__init__.py new file mode 100644 index 0000000..da454ea --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/decode_head/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .sep_aspp_contrast_head import DepthwiseSeparableASPPContrastHead + +__all__ = ['DepthwiseSeparableASPPContrastHead'] diff --git a/Seg_All_In_One_MMSeg/projects/hssn/decode_head/sep_aspp_contrast_head.py b/Seg_All_In_One_MMSeg/projects/hssn/decode_head/sep_aspp_contrast_head.py new file mode 100644 index 0000000..331af30 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/decode_head/sep_aspp_contrast_head.py @@ -0,0 +1,193 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List, Tuple + +import torch +import torch.nn as nn +from mmcv.cnn import build_norm_layer +from torch import Tensor + +from mmseg.models.decode_heads.sep_aspp_head import DepthwiseSeparableASPPHead +from mmseg.models.losses import accuracy +from mmseg.models.utils import resize +from mmseg.registry import MODELS +from mmseg.utils import SampleList + + +class ProjectionHead(nn.Module): + """ProjectionHead, project feature map to specific channels. + + Args: + dim_in (int): Input channels. + norm_cfg (dict): config of norm layer. + proj_dim (int): Output channels. Default: 256. + proj (str): Projection type, 'linear' or 'convmlp'. Default: 'convmlp' + """ + + def __init__(self, + dim_in: int, + norm_cfg: dict, + proj_dim: int = 256, + proj: str = 'convmlp'): + super().__init__() + assert proj in ['convmlp', 'linear'] + if proj == 'linear': + self.proj = nn.Conv2d(dim_in, proj_dim, kernel_size=1) + elif proj == 'convmlp': + self.proj = nn.Sequential( + nn.Conv2d(dim_in, dim_in, kernel_size=1), + build_norm_layer(norm_cfg, dim_in)[1], nn.ReLU(inplace=True), + nn.Conv2d(dim_in, proj_dim, kernel_size=1)) + + def forward(self, x): + return torch.nn.functional.normalize(self.proj(x), p=2, dim=1) + + +@MODELS.register_module() +class DepthwiseSeparableASPPContrastHead(DepthwiseSeparableASPPHead): + """Deep Hierarchical Semantic Segmentation. This head is the implementation + of ``_. + + Based on Encoder-Decoder with Atrous Separable Convolution for + Semantic Image Segmentation. + `DeepLabV3+ `_. + + Args: + proj (str): The type of ProjectionHead, 'linear' or 'convmlp', + default 'convmlp' + """ + + def __init__(self, proj: str = 'convmlp', **kwargs): + super().__init__(**kwargs) + self.proj_head = ProjectionHead( + dim_in=2048, norm_cfg=self.norm_cfg, proj=proj) + self.register_buffer('step', torch.zeros(1)) + + def forward(self, inputs) -> Tuple[Tensor]: + """Forward function.""" + output = super().forward(inputs) + + self.step += 1 + embedding = self.proj_head(inputs[-1]) + + return output, embedding + + def predict_by_feat(self, seg_logits: Tuple[Tensor], + batch_img_metas: List[dict]) -> Tensor: + """Transform a batch of output seg_logits to the input shape. + + Args: + seg_logits (Tensor): The output from decode head forward function. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + + Returns: + Tensor: Outputs segmentation logits map. + """ + # HSSN decode_head output is: (out, embedding): tuple + # only need 'out' here. + if isinstance(seg_logits, tuple): + seg_logit = seg_logits[0] + + if seg_logit.size(1) == 26: # For cityscapes dataset,19 + 7 + hiera_num_classes = 7 + seg_logit[:, 0:2] += seg_logit[:, -7] + seg_logit[:, 2:5] += seg_logit[:, -6] + seg_logit[:, 5:8] += seg_logit[:, -5] + seg_logit[:, 8:10] += seg_logit[:, -4] + seg_logit[:, 10:11] += seg_logit[:, -3] + seg_logit[:, 11:13] += seg_logit[:, -2] + seg_logit[:, 13:19] += seg_logit[:, -1] + + elif seg_logit.size(1) == 12: # For Pascal_person dataset, 7 + 5 + hiera_num_classes = 5 + seg_logit[:, 0:1] = seg_logit[:, 0:1] + \ + seg_logit[:, 7] + seg_logit[:, 10] + seg_logit[:, 1:5] = seg_logit[:, 1:5] + \ + seg_logit[:, 8] + seg_logit[:, 11] + seg_logit[:, 5:7] = seg_logit[:, 5:7] + \ + seg_logit[:, 9] + seg_logit[:, 11] + + elif seg_logit.size(1) == 25: # For LIP dataset, 20 + 5 + hiera_num_classes = 5 + seg_logit[:, 0:1] = seg_logit[:, 0:1] + \ + seg_logit[:, 20] + seg_logit[:, 23] + seg_logit[:, 1:8] = seg_logit[:, 1:8] + \ + seg_logit[:, 21] + seg_logit[:, 24] + seg_logit[:, 10:12] = seg_logit[:, 10:12] + \ + seg_logit[:, 21] + seg_logit[:, 24] + seg_logit[:, 13:16] = seg_logit[:, 13:16] + \ + seg_logit[:, 21] + seg_logit[:, 24] + seg_logit[:, 8:10] = seg_logit[:, 8:10] + \ + seg_logit[:, 22] + seg_logit[:, 24] + seg_logit[:, 12:13] = seg_logit[:, 12:13] + \ + seg_logit[:, 22] + seg_logit[:, 24] + seg_logit[:, 16:20] = seg_logit[:, 16:20] + \ + seg_logit[:, 22] + seg_logit[:, 24] + + # elif seg_logit.size(1) == 144 # For Mapillary dataset, 124+16+4 + # unofficial repository not release mapillary until 2023/2/6 + + if isinstance(batch_img_metas[0]['img_shape'], torch.Size): + # slide inference + size = batch_img_metas[0]['img_shape'] + elif 'pad_shape' in batch_img_metas[0]: + size = batch_img_metas[0]['pad_shape'][:2] + else: + size = batch_img_metas[0]['img_shape'] + seg_logit = seg_logit[:, :-hiera_num_classes] + seg_logit = resize( + input=seg_logit, + size=size, + mode='bilinear', + align_corners=self.align_corners) + + return seg_logit + + def loss_by_feat( + self, + seg_logits: Tuple[Tensor], # (out, embedding) + batch_data_samples: SampleList) -> dict: + """Compute segmentation loss. Will fix in future. + + Args: + seg_logits (Tuple[Tensor]): The output from decode head + forward function. + For this decode_head output are (out, embedding): tuple + batch_data_samples (List[:obj:`SegDataSample`]): The seg + data samples. It usually includes information such + as `metainfo` and `gt_sem_seg`. + + Returns: + dict[str, Tensor]: a dictionary of loss components + """ + seg_logit_before = seg_logits[0] + embedding = seg_logits[1] + seg_label = self._stack_batch_gt(batch_data_samples) + + loss = dict() + seg_logit = resize( + input=seg_logit_before, + size=seg_label.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + if self.sampler is not None: + seg_weight = self.sampler.sample(seg_logit, seg_label) + else: + seg_weight = None + seg_label = seg_label.squeeze(1) + seg_logit_before = resize( + input=seg_logit_before, + scale_factor=0.5, + mode='bilinear', + align_corners=self.align_corners) + + loss['loss_seg'] = self.loss_decode( + self.step, + embedding, + seg_logit_before, + seg_logit, + seg_label, + weight=seg_weight, + ignore_index=self.ignore_index) + loss['acc_seg'] = accuracy(seg_logit, seg_label) + return loss diff --git a/Seg_All_In_One_MMSeg/projects/hssn/losses/__init__.py b/Seg_All_In_One_MMSeg/projects/hssn/losses/__init__.py new file mode 100644 index 0000000..47d2686 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/losses/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .hiera_triplet_loss_cityscape import HieraTripletLossCityscape + +__all__ = ['HieraTripletLossCityscape'] diff --git a/Seg_All_In_One_MMSeg/projects/hssn/losses/hiera_triplet_loss_cityscape.py b/Seg_All_In_One_MMSeg/projects/hssn/losses/hiera_triplet_loss_cityscape.py new file mode 100644 index 0000000..a784f13 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/losses/hiera_triplet_loss_cityscape.py @@ -0,0 +1,218 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmseg.models.builder import LOSSES +from mmseg.models.losses.cross_entropy_loss import CrossEntropyLoss +from .tree_triplet_loss import TreeTripletLoss + +hiera_map = [0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 6, 6, 6, 6, 6] +hiera_index = [[0, 2], [2, 5], [5, 8], [8, 10], [10, 11], [11, 13], [13, 19]] + +hiera = { + 'hiera_high': { + 'flat': [0, 2], + 'construction': [2, 5], + 'object': [5, 8], + 'nature': [8, 10], + 'sky': [10, 11], + 'human': [11, 13], + 'vehicle': [13, 19] + } +} + + +def prepare_targets(targets): + b, h, w = targets.shape + targets_high = torch.ones( + (b, h, w), dtype=targets.dtype, device=targets.device) * 255 + indices_high = [] + for index, high in enumerate(hiera['hiera_high'].keys()): + indices = hiera['hiera_high'][high] + for ii in range(indices[0], indices[1]): + targets_high[targets == ii] = index + indices_high.append(indices) + + return targets, targets_high, indices_high + + +def losses_hiera(predictions, + targets, + targets_top, + num_classes, + indices_high, + eps=1e-8): + """Implementation of hiera loss. + + Args: + predictions (torch.Tensor): seg logits produced by decode head. + targets (torch.Tensor): The learning label of the prediction. + targets_top (torch.Tensor): The hierarchy ground truth of the learning + label. + num_classes (int): Number of categories. + indices_high (List[List[int]]): Hierarchy indices of each hierarchy. + eps (float):Term added to the Logarithm to improve numerical stability. + """ + b, _, h, w = predictions.shape + predictions = torch.sigmoid(predictions.float()) + void_indices = (targets == 255) + targets[void_indices] = 0 + targets = F.one_hot(targets, num_classes=num_classes).permute(0, 3, 1, 2) + void_indices2 = (targets_top == 255) + targets_top[void_indices2] = 0 + targets_top = F.one_hot(targets_top, num_classes=7).permute(0, 3, 1, 2) + + MCMA = predictions[:, :num_classes, :, :] + MCMB = torch.zeros((b, 7, h, w)).to(predictions) + for ii in range(7): + MCMB[:, ii:ii + 1, :, :] = torch.max( + torch.cat([ + predictions[:, indices_high[ii][0]:indices_high[ii][1], :, :], + predictions[:, num_classes + ii:num_classes + ii + 1, :, :] + ], + dim=1), 1, True)[0] + + MCLB = predictions[:, num_classes:num_classes + 7, :, :] + MCLA = predictions[:, :num_classes, :, :].clone() + for ii in range(7): + for jj in range(indices_high[ii][0], indices_high[ii][1]): + MCLA[:, jj:jj + 1, :, :] = torch.min( + torch.cat([ + predictions[:, jj:jj + 1, :, :], MCLB[:, ii:ii + 1, :, :] + ], + dim=1), 1, True)[0] + + valid_indices = (~void_indices).unsqueeze(1) + num_valid = valid_indices.sum() + valid_indices2 = (~void_indices2).unsqueeze(1) + num_valid2 = valid_indices2.sum() + # channel_num*sum()/one_channel_valid already has a weight + loss = ( + (-targets[:, :num_classes, :, :] * torch.log(MCLA + eps) - + (1.0 - targets[:, :num_classes, :, :]) * torch.log(1.0 - MCMA + eps)) + * valid_indices).sum() / num_valid / num_classes + loss += ((-targets_top[:, :, :, :] * torch.log(MCLB + eps) - + (1.0 - targets_top[:, :, :, :]) * torch.log(1.0 - MCMB + eps)) * + valid_indices2).sum() / num_valid2 / 7 + + return 5 * loss + + +def losses_hiera_focal(predictions, + targets, + targets_top, + num_classes, + indices_high, + eps=1e-8, + gamma=2): + """Implementation of hiera loss. + + Args: + predictions (torch.Tensor): seg logits produced by decode head. + targets (torch.Tensor): The learning label of the prediction. + targets_top (torch.Tensor): The hierarchy ground truth of the learning + label. + num_classes (int): Number of categories. + indices_high (List[List[int]]): Hierarchy indices of each hierarchy. + eps (float):Term added to the Logarithm to improve numerical stability. + Defaults: 1e-8. + gamma (int): The exponent value. Defaults: 2. + """ + b, _, h, w = predictions.shape + predictions = torch.sigmoid(predictions.float()) + void_indices = (targets == 255) + targets[void_indices] = 0 + targets = F.one_hot(targets, num_classes=num_classes).permute(0, 3, 1, 2) + void_indices2 = (targets_top == 255) + targets_top[void_indices2] = 0 + targets_top = F.one_hot(targets_top, num_classes=7).permute(0, 3, 1, 2) + + MCMA = predictions[:, :num_classes, :, :] + MCMB = torch.zeros((b, 7, h, w), + dtype=predictions.dtype, + device=predictions.device) + for ii in range(7): + MCMB[:, ii:ii + 1, :, :] = torch.max( + torch.cat([ + predictions[:, indices_high[ii][0]:indices_high[ii][1], :, :], + predictions[:, num_classes + ii:num_classes + ii + 1, :, :] + ], + dim=1), 1, True)[0] + + MCLB = predictions[:, num_classes:num_classes + 7, :, :] + MCLA = predictions[:, :num_classes, :, :].clone() + for ii in range(7): + for jj in range(indices_high[ii][0], indices_high[ii][1]): + MCLA[:, jj:jj + 1, :, :] = torch.min( + torch.cat([ + predictions[:, jj:jj + 1, :, :], MCLB[:, ii:ii + 1, :, :] + ], + dim=1), 1, True)[0] + + valid_indices = (~void_indices).unsqueeze(1) + num_valid = valid_indices.sum() + valid_indices2 = (~void_indices2).unsqueeze(1) + num_valid2 = valid_indices2.sum() + # channel_num*sum()/one_channel_valid already has a weight + loss = ((-targets[:, :num_classes, :, :] * torch.pow( + (1.0 - MCLA), gamma) * torch.log(MCLA + eps) - + (1.0 - targets[:, :num_classes, :, :]) * torch.pow(MCMA, gamma) * + torch.log(1.0 - MCMA + eps)) * + valid_indices).sum() / num_valid / num_classes + loss += ( + (-targets_top[:, :, :, :] * torch.pow( + (1.0 - MCLB), gamma) * torch.log(MCLB + eps) - + (1.0 - targets_top[:, :, :, :]) * torch.pow(MCMB, gamma) * + torch.log(1.0 - MCMB + eps)) * valid_indices2).sum() / num_valid2 / 7 + + return 5 * loss + + +@LOSSES.register_module() +class HieraTripletLossCityscape(nn.Module): + """Modified from https://github.com/qhanghu/HSSN_pytorch/blob/main/mmseg/mo + dels/losses/hiera_triplet_loss_cityscape.py.""" + + def __init__(self, num_classes, use_sigmoid=False, loss_weight=1.0): + super().__init__() + self.num_classes = num_classes + self.loss_weight = loss_weight + self.treetripletloss = TreeTripletLoss(num_classes, hiera_map, + hiera_index) + self.ce = CrossEntropyLoss() + + def forward(self, + step, + embedding, + cls_score_before, + cls_score, + label, + weight=None, + **kwargs): + targets, targets_top, indices_top = prepare_targets(label) + + loss = losses_hiera(cls_score, targets, targets_top, self.num_classes, + indices_top) + ce_loss = self.ce(cls_score[:, :-7], label) + ce_loss2 = self.ce(cls_score[:, -7:], targets_top) + loss = loss + ce_loss + ce_loss2 + + loss_triplet, class_count = self.treetripletloss(embedding, label) + class_counts = [ + torch.ones_like(class_count) + for _ in range(torch.distributed.get_world_size()) + ] + torch.distributed.all_gather(class_counts, class_count, async_op=False) + class_counts = torch.cat(class_counts, dim=0) + + if torch.distributed.get_world_size() == torch.nonzero( + class_counts, as_tuple=False).size(0): + factor = 1 / 4 * (1 + torch.cos( + torch.tensor((step.item() - 80000) / 80000 * + math.pi))) if step.item() < 80000 else 0.5 + loss += factor * loss_triplet + + return loss * self.loss_weight diff --git a/Seg_All_In_One_MMSeg/projects/hssn/losses/tree_triplet_loss.py b/Seg_All_In_One_MMSeg/projects/hssn/losses/tree_triplet_loss.py new file mode 100644 index 0000000..ccc0937 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/hssn/losses/tree_triplet_loss.py @@ -0,0 +1,86 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +import torch.nn.functional as F + +from mmseg.models.builder import LOSSES + + +@LOSSES.register_module() +class TreeTripletLoss(nn.Module): + """TreeTripletLoss. Modified from https://github.com/qhanghu/HSSN_pytorch/b + lob/main/mmseg/models/losses/tree_triplet_loss.py. + + Args: + num_classes (int): Number of categories. + hiera_map (List[int]): Hierarchy map of each category. + hiera_index (List[List[int]]): Hierarchy indices of each hierarchy. + ignore_index (int): Specifies a target value that is ignored and + does not contribute to the input gradients. Defaults: 255. + + Examples: + >>> num_classes = 19 + >>> hiera_map = [ + 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 6, 6, 6, 6, 6] + >>> hiera_index = [ + 0, 2], [2, 5], [5, 8], [8, 10], [10, 11], [11, 13], [13, 19]] + """ + + def __init__(self, num_classes, hiera_map, hiera_index, ignore_index=255): + super().__init__() + + self.ignore_label = ignore_index + self.num_classes = num_classes + self.hiera_map = hiera_map + self.hiera_index = hiera_index + + def forward(self, feats: torch.Tensor, labels=None, max_triplet=200): + labels = labels.unsqueeze(1).float().clone() + labels = torch.nn.functional.interpolate( + labels, (feats.shape[2], feats.shape[3]), mode='nearest') + labels = labels.squeeze(1).long() + assert labels.shape[-1] == feats.shape[-1], '{} {}'.format( + labels.shape, feats.shape) + + labels = labels.view(-1) + feats = feats.permute(0, 2, 3, 1) + feats = feats.contiguous().view(-1, feats.shape[-1]) + + triplet_loss = 0 + exist_classes = torch.unique(labels) + exist_classes = [x for x in exist_classes if x != 255] + class_count = 0 + + for ii in exist_classes: + index_range = self.hiera_index[self.hiera_map[ii]] + index_anchor = labels == ii + index_pos = (labels >= index_range[0]) & ( + labels < index_range[-1]) & (~index_anchor) + index_neg = (labels < index_range[0]) | (labels >= index_range[-1]) + + min_size = min( + torch.sum(index_anchor), torch.sum(index_pos), + torch.sum(index_neg), max_triplet) + + feats_anchor = feats[index_anchor][:min_size] + feats_pos = feats[index_pos][:min_size] + feats_neg = feats[index_neg][:min_size] + + distance = torch.zeros(min_size, 2).to(feats) + distance[:, 0:1] = 1 - (feats_anchor * feats_pos).sum(1, True) + distance[:, 1:2] = 1 - (feats_anchor * feats_neg).sum(1, True) + + # margin always 0.1 + (4-2)/4 since the hierarchy is three level + # TODO: should include label of pos is the same as anchor + margin = 0.6 * torch.ones(min_size).to(feats) + + tl = distance[:, 0] - distance[:, 1] + margin + tl = F.relu(tl) + + if tl.size(0) > 0: + triplet_loss += tl.mean() + class_count += 1 + if class_count == 0: + return None, torch.tensor([0]).to(feats) + triplet_loss /= class_count + return triplet_loss, torch.tensor([class_count]).to(feats) diff --git a/Seg_All_In_One_MMSeg/projects/isnet/README.md b/Seg_All_In_One_MMSeg/projects/isnet/README.md new file mode 100644 index 0000000..0a79ad6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/isnet/README.md @@ -0,0 +1,117 @@ +# ISNet + +[ISNet: Integrate Image-Level and Semantic-Level Context for Semantic Segmentation](https://arxiv.org/pdf/2108.12382.pdf) + +## Description + +This is an implementation of [ISNet](https://arxiv.org/pdf/2108.12382.pdf). +[Official Repo](https://github.com/SegmentationBLWX/sssegmentation) + +## Usage + +### Prerequisites + +- Python 3.7 +- PyTorch 1.6 or higher +- [MIM](https://github.com/open-mmlab/mim) v0.33 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc2 or higher + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `isnet/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Training commands + +```shell +mim train mmsegmentation configs/isnet_r50-d8_8xb2-160k_cityscapes-512x1024.py --work-dir work_dirs/isnet +``` + +To train on multiple GPUs, e.g. 8 GPUs, run the following command: + +```shell +mim train mmsegmentation configs/isnet_r50-d8_8xb2-160k_cityscapes-512x1024.py --work-dir work_dirs/isnet --launcher pytorch --gpus 8 +``` + +### Testing commands + +```shell +mim test mmsegmentation configs/isnet_r50-d8_8xb2-160k_cityscapes-512x1024.py --work-dir work_dirs/isnet --checkpoint ${CHECKPOINT_PATH} +``` + +| Method | Backbone | Crop Size | Lr schd | Mem (GB) | Inf time (fps) | mIoU | mIoU(ms+flip) | config | download | +| ------ | -------- | --------- | ------: | -------- | -------------- | ----: | ------------: | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| ISNet | R-50-D8 | 512x1024 | - | - | - | 79.32 | 80.88 | [config](configs/isnet_r50-d8_8xb2-160k_cityscapes-512x1024.py) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/isnet/isnet_r50-d8_cityscapes-512x1024_20230104-a7a8ccf2.pth) | + +## Citation + +```bibtex +@article{Jin2021ISNetII, + title={ISNet: Integrate Image-Level and Semantic-Level Context for Semantic Segmentation}, + author={Zhenchao Jin and B. Liu and Qi Chu and Nenghai Yu}, + journal={2021 IEEE/CVF International Conference on Computer Vision (ICCV)}, + year={2021}, + pages={7169-7178} +} +``` + +## Checklist + +The progress of ISNet. + + + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + + + - [x] Basic docstrings & proper citation + + + + - [x] Test-time correctness + + + + - [x] A full README + + + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + + + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + + + - [ ] Unit tests + + + + - [ ] Code polishing + + + + - [ ] Metafile.yml + + + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + + + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/isnet/configs/isnet_r50-d8_8xb2-160k_cityscapes-512x1024.py b/Seg_All_In_One_MMSeg/projects/isnet/configs/isnet_r50-d8_8xb2-160k_cityscapes-512x1024.py new file mode 100644 index 0000000..a00d392 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/isnet/configs/isnet_r50-d8_8xb2-160k_cityscapes-512x1024.py @@ -0,0 +1,80 @@ +_base_ = [ + '../../../configs/_base_/datasets/cityscapes.py', + '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_80k.py' +] + +data_root = '../../data/cityscapes/' +train_dataloader = dict(dataset=dict(data_root=data_root)) +val_dataloader = dict(dataset=dict(data_root=data_root)) +test_dataloader = dict(dataset=dict(data_root=data_root)) + +custom_imports = dict(imports=['projects.isnet.decode_heads']) + +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) + +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='ResNetV1c', + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=norm_cfg, + norm_eval=False, + style='pytorch', + contract_dilation=True), + decode_head=dict( + type='ISNetHead', + in_channels=(256, 512, 1024, 2048), + input_transform='multiple_select', + in_index=(0, 1, 2, 3), + channels=512, + dropout_ratio=0.1, + transform_channels=256, + concat_input=True, + with_shortcut=False, + shortcut_in_channels=256, + shortcut_feat_channels=48, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=[ + dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=1.0, + loss_name='loss_o'), + dict( + type='CrossEntropyLoss', + use_sigmoid=False, + loss_weight=0.4, + loss_name='loss_d'), + ]), + auxiliary_head=dict( + type='FCNHead', + in_channels=1024, + in_index=2, + channels=512, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + train_cfg=dict(), + # test_cfg=dict(mode='slide', crop_size=(769, 769), stride=(513, 513)) + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/projects/isnet/decode_heads/__init__.py b/Seg_All_In_One_MMSeg/projects/isnet/decode_heads/__init__.py new file mode 100644 index 0000000..a451629 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/isnet/decode_heads/__init__.py @@ -0,0 +1,3 @@ +from .isnet_head import ISNetHead + +__all__ = ['ISNetHead'] diff --git a/Seg_All_In_One_MMSeg/projects/isnet/decode_heads/isnet_head.py b/Seg_All_In_One_MMSeg/projects/isnet/decode_heads/isnet_head.py new file mode 100644 index 0000000..9c8df54 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/isnet/decode_heads/isnet_head.py @@ -0,0 +1,337 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from torch import Tensor + +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.models.losses import accuracy +from mmseg.models.utils import SelfAttentionBlock, resize +from mmseg.registry import MODELS +from mmseg.utils import SampleList + + +class ImageLevelContext(nn.Module): + """ Image-Level Context Module + Args: + feats_channels (int): Input channels of query/key feature. + transform_channels (int): Output channels of key/query transform. + concat_input (bool): whether to concat input feature. + align_corners (bool): align_corners argument of F.interpolate. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict): Config of activation layers. + """ + + def __init__(self, + feats_channels, + transform_channels, + concat_input=False, + align_corners=False, + conv_cfg=None, + norm_cfg=None, + act_cfg=None): + super().__init__() + self.align_corners = align_corners + self.global_avgpool = nn.AdaptiveAvgPool2d((1, 1)) + self.correlate_net = SelfAttentionBlock( + key_in_channels=feats_channels * 2, + query_in_channels=feats_channels, + channels=transform_channels, + out_channels=feats_channels, + share_key_query=False, + query_downsample=None, + key_downsample=None, + key_query_num_convs=2, + value_out_num_convs=1, + key_query_norm=True, + value_out_norm=True, + matmul_norm=True, + with_out=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + if concat_input: + self.bottleneck = ConvModule( + feats_channels * 2, + feats_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + + '''forward''' + + def forward(self, x): + x_global = self.global_avgpool(x) + x_global = resize( + x_global, + size=x.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + feats_il = self.correlate_net(x, torch.cat([x_global, x], dim=1)) + if hasattr(self, 'bottleneck'): + feats_il = self.bottleneck(torch.cat([x, feats_il], dim=1)) + return feats_il + + +class SemanticLevelContext(nn.Module): + """ Semantic-Level Context Module + Args: + feats_channels (int): Input channels of query/key feature. + transform_channels (int): Output channels of key/query transform. + concat_input (bool): whether to concat input feature. + conv_cfg (dict|None): Config of conv layers. + norm_cfg (dict|None): Config of norm layers. + act_cfg (dict): Config of activation layers. + """ + + def __init__(self, + feats_channels, + transform_channels, + concat_input=False, + conv_cfg=None, + norm_cfg=None, + act_cfg=None): + super().__init__() + self.correlate_net = SelfAttentionBlock( + key_in_channels=feats_channels, + query_in_channels=feats_channels, + channels=transform_channels, + out_channels=feats_channels, + share_key_query=False, + query_downsample=None, + key_downsample=None, + key_query_num_convs=2, + value_out_num_convs=1, + key_query_norm=True, + value_out_norm=True, + matmul_norm=True, + with_out=True, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + if concat_input: + self.bottleneck = ConvModule( + feats_channels * 2, + feats_channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=conv_cfg, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + + '''forward''' + + def forward(self, x, preds, feats_il): + inputs = x + batch_size, num_channels, h, w = x.size() + num_classes = preds.size(1) + feats_sl = torch.zeros(batch_size, h * w, num_channels).type_as(x) + for batch_idx in range(batch_size): + # (C, H, W), (num_classes, H, W) --> (H*W, C), (H*W, num_classes) + feats_iter, preds_iter = x[batch_idx], preds[batch_idx] + feats_iter, preds_iter = feats_iter.reshape( + num_channels, -1), preds_iter.reshape(num_classes, -1) + feats_iter, preds_iter = feats_iter.permute(1, + 0), preds_iter.permute( + 1, 0) + # (H*W, ) + argmax = preds_iter.argmax(1) + for clsid in range(num_classes): + mask = (argmax == clsid) + if mask.sum() == 0: + continue + feats_iter_cls = feats_iter[mask] + preds_iter_cls = preds_iter[:, clsid][mask] + weight = torch.softmax(preds_iter_cls, dim=0) + feats_iter_cls = feats_iter_cls * weight.unsqueeze(-1) + feats_iter_cls = feats_iter_cls.sum(0) + feats_sl[batch_idx][mask] = feats_iter_cls + feats_sl = feats_sl.reshape(batch_size, h, w, num_channels) + feats_sl = feats_sl.permute(0, 3, 1, 2).contiguous() + feats_sl = self.correlate_net(inputs, feats_sl) + if hasattr(self, 'bottleneck'): + feats_sl = self.bottleneck(torch.cat([feats_il, feats_sl], dim=1)) + return feats_sl + + +@MODELS.register_module() +class ISNetHead(BaseDecodeHead): + """ISNet: Integrate Image-Level and Semantic-Level + Context for Semantic Segmentation + + This head is the implementation of `ISNet` + `_. + + Args: + transform_channels (int): Output channels of key/query transform. + concat_input (bool): whether to concat input feature. + with_shortcut (bool): whether to use shortcut connection. + shortcut_in_channels (int): Input channels of shortcut. + shortcut_feat_channels (int): Output channels of shortcut. + dropout_ratio (float): Ratio of dropout. + """ + + def __init__(self, transform_channels, concat_input, with_shortcut, + shortcut_in_channels, shortcut_feat_channels, dropout_ratio, + **kwargs): + super().__init__(**kwargs) + + self.in_channels = self.in_channels[-1] + + self.bottleneck = ConvModule( + self.in_channels, + self.channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.ilc_net = ImageLevelContext( + feats_channels=self.channels, + transform_channels=transform_channels, + concat_input=concat_input, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg, + align_corners=self.align_corners) + self.slc_net = SemanticLevelContext( + feats_channels=self.channels, + transform_channels=transform_channels, + concat_input=concat_input, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + + self.decoder_stage1 = nn.Sequential( + ConvModule( + self.channels, + self.channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + nn.Dropout2d(dropout_ratio), + nn.Conv2d( + self.channels, + self.num_classes, + kernel_size=1, + stride=1, + padding=0, + bias=True), + ) + + if with_shortcut: + self.shortcut = ConvModule( + shortcut_in_channels, + shortcut_feat_channels, + kernel_size=1, + stride=1, + padding=0, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg) + self.decoder_stage2 = nn.Sequential( + ConvModule( + self.channels + shortcut_feat_channels, + self.channels, + kernel_size=3, + stride=1, + padding=1, + conv_cfg=self.conv_cfg, + norm_cfg=self.norm_cfg, + act_cfg=self.act_cfg), + nn.Dropout2d(dropout_ratio), + nn.Conv2d( + self.channels, + self.num_classes, + kernel_size=1, + stride=1, + padding=0, + bias=True), + ) + else: + self.decoder_stage2 = nn.Sequential( + nn.Dropout2d(dropout_ratio), + nn.Conv2d( + self.channels, + self.num_classes, + kernel_size=1, + stride=1, + padding=0, + bias=True), + ) + + self.conv_seg = None + self.dropout = None + + def forward(self, inputs): + x = self._transform_inputs(inputs) + feats = self.bottleneck(x[-1]) + + feats_il = self.ilc_net(feats) + + preds_stage1 = self.decoder_stage1(feats) + preds_stage1 = resize( + preds_stage1, + size=feats.size()[2:], + mode='bilinear', + align_corners=self.align_corners) + + feats_sl = self.slc_net(feats, preds_stage1, feats_il) + + if hasattr(self, 'shortcut'): + shortcut_out = self.shortcut(x[0]) + feats_sl = resize( + feats_sl, + size=shortcut_out.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + feats_sl = torch.cat([feats_sl, shortcut_out], dim=1) + preds_stage2 = self.decoder_stage2(feats_sl) + + return preds_stage1, preds_stage2 + + def loss_by_feat(self, seg_logits: Tensor, + batch_data_samples: SampleList) -> dict: + seg_label = self._stack_batch_gt(batch_data_samples) + loss = dict() + + if self.sampler is not None: + seg_weight = self.sampler.sample(seg_logits[-1], seg_label) + else: + seg_weight = None + seg_label = seg_label.squeeze(1) + + for seg_logit, loss_decode in zip(seg_logits, self.loss_decode): + seg_logit = resize( + input=seg_logit, + size=seg_label.shape[2:], + mode='bilinear', + align_corners=self.align_corners) + loss[loss_decode.name] = loss_decode( + seg_logit, + seg_label, + seg_weight, + ignore_index=self.ignore_index) + + loss['acc_seg'] = accuracy( + seg_logits[-1], seg_label, ignore_index=self.ignore_index) + return loss + + def predict_by_feat(self, seg_logits: Tensor, + batch_img_metas: List[dict]) -> Tensor: + _, seg_logits_stage2 = seg_logits + return super().predict_by_feat(seg_logits_stage2, batch_img_metas) diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/README.md b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/README.md new file mode 100644 index 0000000..44a1e33 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/README.md @@ -0,0 +1,86 @@ +# Mapillary Vistas Dataset + +Support **`Mapillary Vistas Dataset`** + +## Description + +Author: AI-Tianlong + +This project implements **`Mapillary Vistas Dataset`** + +### Dataset preparing + +Preparing `Mapillary Vistas Dataset` dataset following [Mapillary Vistas Dataset Preparing Guide](https://github.com/open-mmlab/mmsegmentation/tree/main/projects/mapillary_dataset/docs/en/user_guides/2_dataset_prepare.md) + +```none + mmsegmentation + ├── mmseg + ├── tools + ├── configs + ├── data + │ ├── mapillary + │ │ ├── training + │ │ │ ├── images + │ │ │ ├── v1.2 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── labels_mask + | │   │   │ └── panoptic + │ │ │ ├── v2.0 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── labels_mask + | │ │ │ ├── panoptic + | │   │   │ └── polygons + │ │ ├── validation + │ │ │ ├── images + │ │ │ ├── v1.2 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── labels_mask + | │   │   │ └── panoptic + │ │ │ ├── v2.0 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── labels_mask + | │ │ │ ├── panoptic + | │   │   │ └── polygons +``` + +### Training commands + +```bash +# Dataset train commands +# at `mmsegmentation` folder +bash tools/dist_train.sh projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v1-512x1024.py 4 +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [ ] Test-time correctness + + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [x] Milestone 3: Good to be a part of our core package! + + - [x] Type hints and docstrings + + - [x] Unit tests + + - [x] Code polishing + + - [x] Metafile.yml + +- [x] Move your modules into the core package following the codebase's file hierarchy structure. + +- [x] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v1.py b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v1.py new file mode 100644 index 0000000..611aa47 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v1.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'MapillaryDataset_v1' +data_root = 'data/mapillary/' +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='training/images', seg_map_path='training/v1.2/labels'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='validation/images', + seg_map_path='validation/v1.2/labels'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v1_65.py b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v1_65.py new file mode 100644 index 0000000..f594f37 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v1_65.py @@ -0,0 +1,37 @@ +# dataset settings +_base_ = './mapillary_v1.py' +metainfo = dict( + classes=('Bird', 'Ground Animal', 'Curb', 'Fence', 'Guard Rail', 'Barrier', + 'Wall', 'Bike Lane', 'Crosswalk - Plain', 'Curb Cut', 'Parking', + 'Pedestrian Area', 'Rail Track', 'Road', 'Service Lane', + 'Sidewalk', 'Bridge', 'Building', 'Tunnel', 'Person', 'Bicyclist', + 'Motorcyclist', 'Other Rider', 'Lane Marking - Crosswalk', + 'Lane Marking - General', 'Mountain', 'Sand', 'Sky', 'Snow', + 'Terrain', 'Vegetation', 'Water', 'Banner', 'Bench', 'Bike Rack', + 'Billboard', 'Catch Basin', 'CCTV Camera', 'Fire Hydrant', + 'Junction Box', 'Mailbox', 'Manhole', 'Phone Booth', 'Pothole', + 'Street Light', 'Pole', 'Traffic Sign Frame', 'Utility Pole', + 'Traffic Light', 'Traffic Sign (Back)', 'Traffic Sign (Front)', + 'Trash Can', 'Bicycle', 'Boat', 'Bus', 'Car', 'Caravan', + 'Motorcycle', 'On Rails', 'Other Vehicle', 'Trailer', 'Truck', + 'Wheeled Slow', 'Car Mount', 'Ego Vehicle'), + palette=[[165, 42, 42], [0, 192, 0], [196, 196, 196], [190, 153, 153], + [180, 165, 180], [90, 120, 150], [102, 102, 156], [128, 64, 255], + [140, 140, 200], [170, 170, 170], [250, 170, 160], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], [244, 35, 232], + [150, 100, 100], [70, 70, 70], [150, 120, 90], [220, 20, 60], + [255, 0, 0], [255, 0, 100], [255, 0, 200], [200, 128, 128], + [255, 255, 255], [64, 170, 64], [230, 160, 50], [70, 130, 180], + [190, 255, 255], [152, 251, 152], [107, 142, 35], [0, 170, 30], + [255, 255, 128], [250, 0, 30], [100, 140, 180], [220, 220, 220], + [220, 128, 128], [222, 40, 40], [100, 170, 30], [40, 40, 40], + [33, 33, 33], [100, 128, 160], [142, 0, 0], [70, 100, 150], + [210, 170, 100], [153, 153, 153], [128, 128, 128], [0, 0, 80], + [250, 170, 30], [192, 192, 192], [220, 220, 0], [140, 140, 20], + [119, 11, 32], [150, 0, 255], [0, 60, 100], [0, 0, 142], + [0, 0, 90], [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 192], [32, 32, 32], [120, 10, 10]]) + +train_dataloader = dict(dataset=dict(metainfo=metainfo)) +val_dataloader = dict(dataset=dict(metainfo=metainfo)) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v2.py b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v2.py new file mode 100644 index 0000000..7cb7a95 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/_base_/datasets/mapillary_v2.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'MapillaryDataset_v2' +data_root = 'data/mapillary/' +crop_size = (512, 1024) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict( + type='RandomResize', + scale=(2048, 1024), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 1024), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', file_client_args=dict(backend='disk')), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=2, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='training/images', seg_map_path='training/v2.0/labels'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='validation/images', + seg_map_path='validation/v2.0/labels'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v1-512x1024.py b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v1-512x1024.py new file mode 100644 index 0000000..b559e0d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v1-512x1024.py @@ -0,0 +1,17 @@ +_base_ = [ + '../../../configs/_base_/models/deeplabv3plus_r50-d8.py', + './_base_/datasets/mapillary_v1.py', + '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_240k.py' +] +custom_imports = dict( + imports=['projects.mapillary_dataset.mmseg.datasets.mapillary']) + +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101), + decode_head=dict(num_classes=66), + auxiliary_head=dict(num_classes=66)) diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v2-512x1024.py b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v2-512x1024.py new file mode 100644 index 0000000..cfe31a2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/deeplabv3plus_r101-d8_4xb2-240k_mapillay_v2-512x1024.py @@ -0,0 +1,16 @@ +_base_ = [ + '../../../configs/_base_/models/deeplabv3plus_r50-d8.py', + './_base_/datasets/mapillary_v2.py', + '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_240k.py' +] +custom_imports = dict( + imports=['projects.mapillary_dataset.mmseg.datasets.mapillary']) +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101), + decode_head=dict(num_classes=124), + auxiliary_head=dict(num_classes=124)) diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/pspnet_r101-d8_4xb2-240k_mapillay_v1-512x1024.py b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/pspnet_r101-d8_4xb2-240k_mapillay_v1-512x1024.py new file mode 100644 index 0000000..1ca2b57 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/pspnet_r101-d8_4xb2-240k_mapillay_v1-512x1024.py @@ -0,0 +1,16 @@ +_base_ = [ + '../../../configs/_base_/models/pspnet_r50-d8.py', + './_base_/datasets/mapillary_v1.py', + '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_240k.py' +] +custom_imports = dict( + imports=['projects.mapillary_dataset.mmseg.datasets.mapillary']) +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101), + decode_head=dict(num_classes=66), + auxiliary_head=dict(num_classes=66)) diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/pspnet_r101-d8_4xb2-240k_mapillay_v2-512x1024.py b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/pspnet_r101-d8_4xb2-240k_mapillay_v2-512x1024.py new file mode 100644 index 0000000..c04746a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/configs/pspnet_r101-d8_4xb2-240k_mapillay_v2-512x1024.py @@ -0,0 +1,16 @@ +_base_ = [ + '../../../configs/_base_/models/pspnet_r50-d8.py', + './_base_/datasets/mapillary_v2.py', + '../../../configs/_base_/default_runtime.py', + '../../../configs/_base_/schedules/schedule_240k.py' +] +custom_imports = dict( + imports=['projects.mapillary_dataset.mmseg.datasets.mapillary']) +crop_size = (512, 1024) +data_preprocessor = dict(size=crop_size) +model = dict( + data_preprocessor=data_preprocessor, + pretrained='open-mmlab://resnet101_v1c', + backbone=dict(depth=101), + decode_head=dict(num_classes=124), + auxiliary_head=dict(num_classes=124)) diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/docs/en/user_guides/2_dataset_prepare.md b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/docs/en/user_guides/2_dataset_prepare.md new file mode 100644 index 0000000..c5cbc0f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/docs/en/user_guides/2_dataset_prepare.md @@ -0,0 +1,255 @@ +## Mapillary Vistas Datasets + +- The dataset could be download [here](https://www.mapillary.com/dataset/vistas) after registration. + +- Mapillary Vistas Dataset use 8-bit with color-palette to store labels. No conversion operation is required. + +- Assumption you have put the dataset zip file in `mmsegmentation/data/mapillary` + +- Please run the following commands to unzip dataset. + + ```bash + cd data/mapillary + unzip An-ZjB1Zm61yAZG0ozTymz8I8NqI4x0MrYrh26dq7kPgfu8vf9ImrdaOAVOFYbJ2pNAgUnVGBmbue9lTgdBOb5BbKXIpFs0fpYWqACbrQDChAA2fdX0zS9PcHu7fY8c-FOvyBVxPNYNFQuM.zip + ``` + +- After unzip, you will get Mapillary Vistas Dataset like this structure. Semantic segmentation mask labels in `labels` folder. + + ```none + mmsegmentation + ├── mmseg + ├── tools + ├── configs + ├── data + │ ├── mapillary + │ │ ├── training + │ │ │ ├── images + │ │ │ ├── v1.2 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │   │   │ └── panoptic + │ │ │ ├── v2.0 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── panoptic + | │   │   │ └── polygons + │ │ ├── validation + │ │ │ ├── images + | │ │ ├── v1.2 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │   │   │ └── panoptic + │ │ │ ├── v2.0 + | │ │ │ ├── instances + | │ │ │ ├── labels + | │ │ │ ├── panoptic + | │   │   │ └── polygons + ``` + +- You could set Datasets version with `MapillaryDataset_v1` and `MapillaryDataset_v2` in your configs. + View the Mapillary Vistas Datasets config file here [V1.2](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/_base_/datasets/mapillary_v1.py) and [V2.0](https://github.com/open-mmlab/mmsegmentation/blob/main/configs/_base_/datasets/mapillary_v2.py) + +- **View datasets labels index and palette** + +- **Mapillary Vistas Datasets labels information** + **v1.2 information** + + ```none + There are 66 labels classes in v1.2 + 0--Bird--[165, 42, 42], + 1--Ground Animal--[0, 192, 0], + 2--Curb--[196, 196, 196], + 3--Fence--[190, 153, 153], + 4--Guard Rail--[180, 165, 180], + 5--Barrier--[90, 120, 150], + 6--Wall--[102, 102, 156], + 7--Bike Lane--[128, 64, 255], + 8--Crosswalk - Plain--[140, 140, 200], + 9--Curb Cut--[170, 170, 170], + 10--Parking--[250, 170, 160], + 11--Pedestrian Area--[96, 96, 96], + 12--Rail Track--[230, 150, 140], + 13--Road--[128, 64, 128], + 14--Service Lane--[110, 110, 110], + 15--Sidewalk--[244, 35, 232], + 16--Bridge--[150, 100, 100], + 17--Building--[70, 70, 70], + 18--Tunnel--[150, 120, 90], + 19--Person--[220, 20, 60], + 20--Bicyclist--[255, 0, 0], + 21--Motorcyclist--[255, 0, 100], + 22--Other Rider--[255, 0, 200], + 23--Lane Marking - Crosswalk--[200, 128, 128], + 24--Lane Marking - General--[255, 255, 255], + 25--Mountain--[64, 170, 64], + 26--Sand--[230, 160, 50], + 27--Sky--[70, 130, 180], + 28--Snow--[190, 255, 255], + 29--Terrain--[152, 251, 152], + 30--Vegetation--[107, 142, 35], + 31--Water--[0, 170, 30], + 32--Banner--[255, 255, 128], + 33--Bench--[250, 0, 30], + 34--Bike Rack--[100, 140, 180], + 35--Billboard--[220, 220, 220], + 36--Catch Basin--[220, 128, 128], + 37--CCTV Camera--[222, 40, 40], + 38--Fire Hydrant--[100, 170, 30], + 39--Junction Box--[40, 40, 40], + 40--Mailbox--[33, 33, 33], + 41--Manhole--[100, 128, 160], + 42--Phone Booth--[142, 0, 0], + 43--Pothole--[70, 100, 150], + 44--Street Light--[210, 170, 100], + 45--Pole--[153, 153, 153], + 46--Traffic Sign Frame--[128, 128, 128], + 47--Utility Pole--[0, 0, 80], + 48--Traffic Light--[250, 170, 30], + 49--Traffic Sign (Back)--[192, 192, 192], + 50--Traffic Sign (Front)--[220, 220, 0], + 51--Trash Can--[140, 140, 20], + 52--Bicycle--[119, 11, 32], + 53--Boat--[150, 0, 255], + 54--Bus--[0, 60, 100], + 55--Car--[0, 0, 142], + 56--Caravan--[0, 0, 90], + 57--Motorcycle--[0, 0, 230], + 58--On Rails--[0, 80, 100], + 59--Other Vehicle--[128, 64, 64], + 60--Trailer--[0, 0, 110], + 61--Truck--[0, 0, 70], + 62--Wheeled Slow--[0, 0, 192], + 63--Car Mount--[32, 32, 32], + 64--Ego Vehicle--[120, 10, 10], + 65--Unlabeled--[0, 0, 0] + ``` + + **v2.0 information** + + ```none + There are 124 labels classes in v2.0 + 0--Bird--[165, 42, 42], + 1--Ground Animal--[0, 192, 0], + 2--Ambiguous Barrier--[250, 170, 31], + 3--Concrete Block--[250, 170, 32], + 4--Curb--[196, 196, 196], + 5--Fence--[190, 153, 153], + 6--Guard Rail--[180, 165, 180], + 7--Barrier--[90, 120, 150], + 8--Road Median--[250, 170, 33], + 9--Road Side--[250, 170, 34], + 10--Lane Separator--[128, 128, 128], + 11--Temporary Barrier--[250, 170, 35], + 12--Wall--[102, 102, 156], + 13--Bike Lane--[128, 64, 255], + 14--Crosswalk - Plain--[140, 140, 200], + 15--Curb Cut--[170, 170, 170], + 16--Driveway--[250, 170, 36], + 17--Parking--[250, 170, 160], + 18--Parking Aisle--[250, 170, 37], + 19--Pedestrian Area--[96, 96, 96], + 20--Rail Track--[230, 150, 140], + 21--Road--[128, 64, 128], + 22--Road Shoulder--[110, 110, 110], + 23--Service Lane--[110, 110, 110], + 24--Sidewalk--[244, 35, 232], + 25--Traffic Island--[128, 196, 128], + 26--Bridge--[150, 100, 100], + 27--Building--[70, 70, 70], + 28--Garage--[150, 150, 150], + 29--Tunnel--[150, 120, 90], + 30--Person--[220, 20, 60], + 31--Person Group--[220, 20, 60], + 32--Bicyclist--[255, 0, 0], + 33--Motorcyclist--[255, 0, 100], + 34--Other Rider--[255, 0, 200], + 35--Lane Marking - Dashed Line--[255, 255, 255], + 36--Lane Marking - Straight Line--[255, 255, 255], + 37--Lane Marking - Zigzag Line--[250, 170, 29], + 38--Lane Marking - Ambiguous--[250, 170, 28], + 39--Lane Marking - Arrow (Left)--[250, 170, 26], + 40--Lane Marking - Arrow (Other)--[250, 170, 25], + 41--Lane Marking - Arrow (Right)--[250, 170, 24], + 42--Lane Marking - Arrow (Split Left or Straight)--[250, 170, 22], + 43--Lane Marking - Arrow (Split Right or Straight)--[250, 170, 21], + 44--Lane Marking - Arrow (Straight)--[250, 170, 20], + 45--Lane Marking - Crosswalk--[255, 255, 255], + 46--Lane Marking - Give Way (Row)--[250, 170, 19], + 47--Lane Marking - Give Way (Single)--[250, 170, 18], + 48--Lane Marking - Hatched (Chevron)--[250, 170, 12], + 49--Lane Marking - Hatched (Diagonal)--[250, 170, 11], + 50--Lane Marking - Other--[255, 255, 255], + 51--Lane Marking - Stop Line--[255, 255, 255], + 52--Lane Marking - Symbol (Bicycle)--[250, 170, 16], + 53--Lane Marking - Symbol (Other)--[250, 170, 15], + 54--Lane Marking - Text--[250, 170, 15], + 55--Lane Marking (only) - Dashed Line--[255, 255, 255], + 56--Lane Marking (only) - Crosswalk--[255, 255, 255], + 57--Lane Marking (only) - Other--[255, 255, 255], + 58--Lane Marking (only) - Test--[255, 255, 255], + 59--Mountain--[64, 170, 64], + 60--Sand--[230, 160, 50], + 61--Sky--[70, 130, 180], + 62--Snow--[190, 255, 255], + 63--Terrain--[152, 251, 152], + 64--Vegetation--[107, 142, 35], + 65--Water--[0, 170, 30], + 66--Banner--[255, 255, 128], + 67--Bench--[250, 0, 30], + 68--Bike Rack--[100, 140, 180], + 69--Catch Basin--[220, 128, 128], + 70--CCTV Camera--[222, 40, 40], + 71--Fire Hydrant--[100, 170, 30], + 72--Junction Box--[40, 40, 40], + 73--Mailbox--[33, 33, 33], + 74--Manhole--[100, 128, 160], + 75--Parking Meter--[20, 20, 255], + 76--Phone Booth--[142, 0, 0], + 77--Pothole--[70, 100, 150], + 78--Signage - Advertisement--[250, 171, 30], + 79--Signage - Ambiguous--[250, 172, 30], + 80--Signage - Back--[250, 173, 30], + 81--Signage - Information--[250, 174, 30], + 82--Signage - Other--[250, 175, 30], + 83--Signage - Store--[250, 176, 30], + 84--Street Light--[210, 170, 100], + 85--Pole--[153, 153, 153], + 86--Pole Group--[153, 153, 153], + 87--Traffic Sign Frame--[128, 128, 128], + 88--Utility Pole--[0, 0, 80], + 89--Traffic Cone--[210, 60, 60], + 90--Traffic Light - General (Single)--[250, 170, 30], + 91--Traffic Light - Pedestrians--[250, 170, 30], + 92--Traffic Light - General (Upright)--[250, 170, 30], + 93--Traffic Light - General (Horizontal)--[250, 170, 30], + 94--Traffic Light - Cyclists--[250, 170, 30], + 95--Traffic Light - Other--[250, 170, 30], + 96--Traffic Sign - Ambiguous--[192, 192, 192], + 97--Traffic Sign (Back)--[192, 192, 192], + 98--Traffic Sign - Direction (Back)--[192, 192, 192], + 99--Traffic Sign - Direction (Front)--[220, 220, 0], + 100--Traffic Sign (Front)--[220, 220, 0], + 101--Traffic Sign - Parking--[0, 0, 196], + 102--Traffic Sign - Temporary (Back)--[192, 192, 192], + 103--Traffic Sign - Temporary (Front)--[220, 220, 0], + 104--Trash Can--[140, 140, 20], + 105--Bicycle--[119, 11, 32], + 106--Boat--[150, 0, 255], + 107--Bus--[0, 60, 100], + 108--Car--[0, 0, 142], + 109--Caravan--[0, 0, 90], + 110--Motorcycle--[0, 0, 230], + 111--On Rails--[0, 80, 100], + 112--Other Vehicle--[128, 64, 64], + 113--Trailer--[0, 0, 110], + 114--Truck--[0, 0, 70], + 115--Vehicle Group--[0, 0, 142], + 116--Wheeled Slow--[0, 0, 192], + 117--Water Valve--[170, 170, 170], + 118--Car Mount--[32, 32, 32], + 119--Dynamic--[111, 74, 0], + 120--Ego Vehicle--[120, 10, 10], + 121--Ground--[81, 0, 81], + 122--Static--[111, 111, 0], + 123--Unlabeled--[0, 0, 0] + ``` diff --git a/Seg_All_In_One_MMSeg/projects/mapillary_dataset/mmseg/datasets/mapillary.py b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/mmseg/datasets/mapillary.py new file mode 100644 index 0000000..f49bd54 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/mapillary_dataset/mmseg/datasets/mapillary.py @@ -0,0 +1,177 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg.datasets.basesegdataset import BaseSegDataset + +# from mmseg.registry import DATASETS + + +# @DATASETS.register_module() +class MapillaryDataset_v1(BaseSegDataset): + """Mapillary Vistas Dataset. + + Dataset paper link: + http://ieeexplore.ieee.org/document/8237796/ + + v1.2 contain 66 object classes. + (37 instance-specific) + + v2.0 contain 124 object classes. + (70 instance-specific, 46 stuff, 8 void or crowd). + + The ``img_suffix`` is fixed to '.jpg' and ``seg_map_suffix`` is + fixed to '.png' for Mapillary Vistas Dataset. + """ + METAINFO = dict( + classes=('Bird', 'Ground Animal', 'Curb', 'Fence', 'Guard Rail', + 'Barrier', 'Wall', 'Bike Lane', 'Crosswalk - Plain', + 'Curb Cut', 'Parking', 'Pedestrian Area', 'Rail Track', + 'Road', 'Service Lane', 'Sidewalk', 'Bridge', 'Building', + 'Tunnel', 'Person', 'Bicyclist', 'Motorcyclist', + 'Other Rider', 'Lane Marking - Crosswalk', + 'Lane Marking - General', 'Mountain', 'Sand', 'Sky', 'Snow', + 'Terrain', 'Vegetation', 'Water', 'Banner', 'Bench', + 'Bike Rack', 'Billboard', 'Catch Basin', 'CCTV Camera', + 'Fire Hydrant', 'Junction Box', 'Mailbox', 'Manhole', + 'Phone Booth', 'Pothole', 'Street Light', 'Pole', + 'Traffic Sign Frame', 'Utility Pole', 'Traffic Light', + 'Traffic Sign (Back)', 'Traffic Sign (Front)', 'Trash Can', + 'Bicycle', 'Boat', 'Bus', 'Car', 'Caravan', 'Motorcycle', + 'On Rails', 'Other Vehicle', 'Trailer', 'Truck', + 'Wheeled Slow', 'Car Mount', 'Ego Vehicle', 'Unlabeled'), + palette=[[165, 42, 42], [0, 192, 0], [196, 196, 196], [190, 153, 153], + [180, 165, 180], [90, 120, 150], [102, 102, 156], + [128, 64, 255], [140, 140, 200], [170, 170, 170], + [250, 170, 160], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], + [244, 35, 232], [150, 100, 100], [70, 70, 70], [150, 120, 90], + [220, 20, 60], [255, 0, 0], [255, 0, 100], [255, 0, 200], + [200, 128, 128], [255, 255, 255], [64, 170, + 64], [230, 160, 50], + [70, 130, 180], [190, 255, 255], [152, 251, 152], + [107, 142, 35], [0, 170, 30], [255, 255, 128], [250, 0, 30], + [100, 140, 180], [220, 220, 220], [220, 128, 128], + [222, 40, 40], [100, 170, 30], [40, 40, 40], [33, 33, 33], + [100, 128, 160], [142, 0, 0], [70, 100, 150], [210, 170, 100], + [153, 153, 153], [128, 128, 128], [0, 0, 80], [250, 170, 30], + [192, 192, 192], [220, 220, 0], [140, 140, 20], [119, 11, 32], + [150, 0, 255], [0, 60, 100], [0, 0, 142], [0, 0, 90], + [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 192], [32, 32, 32], [120, 10, + 10], [0, 0, 0]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) + + +# @DATASETS.register_module() +class MapillaryDataset_v2(BaseSegDataset): + """Mapillary Vistas Dataset. + + Dataset paper link: + http://ieeexplore.ieee.org/document/8237796/ + + v1.2 contain 66 object classes. + (37 instance-specific) + + v2.0 contain 124 object classes. + (70 instance-specific, 46 stuff, 8 void or crowd). + + The ``img_suffix`` is fixed to '.jpg' and ``seg_map_suffix`` is + fixed to '.png' for Mapillary Vistas Dataset. + """ + METAINFO = dict( + classes=( + 'Bird', 'Ground Animal', 'Ambiguous Barrier', 'Concrete Block', + 'Curb', 'Fence', 'Guard Rail', 'Barrier', 'Road Median', + 'Road Side', 'Lane Separator', 'Temporary Barrier', 'Wall', + 'Bike Lane', 'Crosswalk - Plain', 'Curb Cut', 'Driveway', + 'Parking', 'Parking Aisle', 'Pedestrian Area', 'Rail Track', + 'Road', 'Road Shoulder', 'Service Lane', 'Sidewalk', + 'Traffic Island', 'Bridge', 'Building', 'Garage', 'Tunnel', + 'Person', 'Person Group', 'Bicyclist', 'Motorcyclist', + 'Other Rider', 'Lane Marking - Dashed Line', + 'Lane Marking - Straight Line', 'Lane Marking - Zigzag Line', + 'Lane Marking - Ambiguous', 'Lane Marking - Arrow (Left)', + 'Lane Marking - Arrow (Other)', 'Lane Marking - Arrow (Right)', + 'Lane Marking - Arrow (Split Left or Straight)', + 'Lane Marking - Arrow (Split Right or Straight)', + 'Lane Marking - Arrow (Straight)', 'Lane Marking - Crosswalk', + 'Lane Marking - Give Way (Row)', + 'Lane Marking - Give Way (Single)', + 'Lane Marking - Hatched (Chevron)', + 'Lane Marking - Hatched (Diagonal)', 'Lane Marking - Other', + 'Lane Marking - Stop Line', 'Lane Marking - Symbol (Bicycle)', + 'Lane Marking - Symbol (Other)', 'Lane Marking - Text', + 'Lane Marking (only) - Dashed Line', + 'Lane Marking (only) - Crosswalk', 'Lane Marking (only) - Other', + 'Lane Marking (only) - Test', 'Mountain', 'Sand', 'Sky', 'Snow', + 'Terrain', 'Vegetation', 'Water', 'Banner', 'Bench', 'Bike Rack', + 'Catch Basin', 'CCTV Camera', 'Fire Hydrant', 'Junction Box', + 'Mailbox', 'Manhole', 'Parking Meter', 'Phone Booth', 'Pothole', + 'Signage - Advertisement', 'Signage - Ambiguous', 'Signage - Back', + 'Signage - Information', 'Signage - Other', 'Signage - Store', + 'Street Light', 'Pole', 'Pole Group', 'Traffic Sign Frame', + 'Utility Pole', 'Traffic Cone', 'Traffic Light - General (Single)', + 'Traffic Light - Pedestrians', 'Traffic Light - General (Upright)', + 'Traffic Light - General (Horizontal)', 'Traffic Light - Cyclists', + 'Traffic Light - Other', 'Traffic Sign - Ambiguous', + 'Traffic Sign (Back)', 'Traffic Sign - Direction (Back)', + 'Traffic Sign - Direction (Front)', 'Traffic Sign (Front)', + 'Traffic Sign - Parking', 'Traffic Sign - Temporary (Back)', + 'Traffic Sign - Temporary (Front)', 'Trash Can', 'Bicycle', 'Boat', + 'Bus', 'Car', 'Caravan', 'Motorcycle', 'On Rails', 'Other Vehicle', + 'Trailer', 'Truck', 'Vehicle Group', 'Wheeled Slow', 'Water Valve', + 'Car Mount', 'Dynamic', 'Ego Vehicle', 'Ground', 'Static', + 'Unlabeled'), + palette=[[165, 42, 42], [0, 192, 0], [250, 170, 31], [250, 170, 32], + [196, 196, 196], [190, 153, 153], [180, 165, 180], + [90, 120, 150], [250, 170, 33], [250, 170, 34], + [128, 128, 128], [250, 170, 35], [102, 102, 156], + [128, 64, 255], [140, 140, 200], [170, 170, 170], + [250, 170, 36], [250, 170, 160], [250, 170, 37], [96, 96, 96], + [230, 150, 140], [128, 64, 128], [110, 110, 110], + [110, 110, 110], [244, 35, 232], [128, 196, + 128], [150, 100, 100], + [70, 70, 70], [150, 150, 150], [150, 120, 90], [220, 20, 60], + [220, 20, 60], [255, 0, 0], [255, 0, 100], [255, 0, 200], + [255, 255, 255], [255, 255, 255], [250, 170, 29], + [250, 170, 28], [250, 170, 26], [250, 170, + 25], [250, 170, 24], + [250, 170, 22], [250, 170, 21], [250, 170, + 20], [255, 255, 255], + [250, 170, 19], [250, 170, 18], [250, 170, + 12], [250, 170, 11], + [255, 255, 255], [255, 255, 255], [250, 170, 16], + [250, 170, 15], [250, 170, 15], [255, 255, 255], + [255, 255, 255], [255, 255, 255], [255, 255, 255], + [64, 170, 64], [230, 160, 50], + [70, 130, 180], [190, 255, 255], [152, 251, 152], + [107, 142, 35], [0, 170, 30], [255, 255, 128], [250, 0, 30], + [100, 140, 180], [220, 128, 128], [222, 40, + 40], [100, 170, 30], + [40, 40, 40], [33, 33, 33], [100, 128, 160], [20, 20, 255], + [142, 0, 0], [70, 100, 150], [250, 171, 30], [250, 172, 30], + [250, 173, 30], [250, 174, 30], [250, 175, + 30], [250, 176, 30], + [210, 170, 100], [153, 153, 153], [153, 153, 153], + [128, 128, 128], [0, 0, 80], [210, 60, 60], [250, 170, 30], + [250, 170, 30], [250, 170, 30], [250, 170, + 30], [250, 170, 30], + [250, 170, 30], [192, 192, 192], [192, 192, 192], + [192, 192, 192], [220, 220, 0], [220, 220, 0], [0, 0, 196], + [192, 192, 192], [220, 220, 0], [140, 140, 20], [119, 11, 32], + [150, 0, 255], [0, 60, 100], [0, 0, 142], [0, 0, 90], + [0, 0, 230], [0, 80, 100], [128, 64, 64], [0, 0, 110], + [0, 0, 70], [0, 0, 142], [0, 0, 192], [170, 170, 170], + [32, 32, 32], [111, 74, 0], [120, 10, 10], [81, 0, 81], + [111, 111, 0], [0, 0, 0]]) + + def __init__(self, + img_suffix='.jpg', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, seg_map_suffix=seg_map_suffix, **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/README.md new file mode 100644 index 0000000..d3fa64e --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/README.md @@ -0,0 +1,142 @@ +# Brain CT Images with Intracranial Hemorrhage Masks (Cranium) + +## Description + +This project supports **`Brain CT Images with Intracranial Hemorrhage Masks (Cranium)`**, which can be downloaded from [here](https://www.kaggle.com/datasets/vbookshelf/computed-tomography-ct-images). + +### Dataset Overview + +This dataset consists of head CT (Computed Thomography) images in jpg format. There are 2500 brain window images and 2500 bone window images, for 82 patients. There are approximately 30 image slices per patient. 318 images have associated intracranial image masks. Also included are csv files containing hemorrhage diagnosis data and patient data. +This is version 1.0.0 of this dataset. A full description of this dataset as well as updated versions can be found here: +https://physionet.org/content/ct-ich/1.0.0/ + +### Statistic Information + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ----------------------------------------------------------------------------------- | ----------------- | ------------ | -------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------- | +| [Cranium](https://www.kaggle.com/datasets/vbookshelf/computed-tomography-ct-images) | head_and_neck | segmentation | ct | 2 | 2501/-/- | yes/-/- | 2020 | [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 2501 | 99.93 | - | - | - | - | +| hemorrhage | 318 | 0.07 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![cranium](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/ct/cranium/cranium_dataset.png?raw=true) + +## Dataset Citation + +``` +@article{hssayeni2020computed, + title={Computed tomography images for intracranial hemorrhage detection and segmentation}, + author={Hssayeni, Murtadha and Croock, MS and Salman, AD and Al-khafaji, HF and Yahya, ZA and Ghoraani, B}, + journal={Intracranial Hemorrhage Segmentation Using A Deep Convolutional Model. Data}, + volume={5}, + number={1}, + pages={179}, + year={2020} +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 9.3.0 +- scikit-learn(sklearn) v1.2.0 1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `cranium/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://www.kaggle.com/datasets/vbookshelf/computed-tomography-ct-images) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── ct + │ │ │ │ ├── cranium + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 2000 | 99.93 | 501 | 99.92 | - | - | +| hemorrhage | 260 | 0.07 | 260 | 0.08 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/cranium_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/cranium_512x512.py new file mode 100644 index 0000000..d9b4436 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/cranium_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'CraniumDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_cranium-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_cranium-512x512.py new file mode 100644 index 0000000..ac013a2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_cranium-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './cranium_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.cranium_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_cranium-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_cranium-512x512.py new file mode 100644 index 0000000..c71110a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_cranium-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './cranium_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.cranium_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_cranium-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_cranium-512x512.py new file mode 100644 index 0000000..abbdac2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_cranium-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './cranium_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.cranium_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_cranium-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_cranium-512x512.py new file mode 100644 index 0000000..4185952 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_cranium-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './cranium_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.cranium_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/datasets/cranium_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/datasets/cranium_dataset.py new file mode 100644 index 0000000..d65f1cb --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/datasets/cranium_dataset.py @@ -0,0 +1,31 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class CraniumDataset(BaseSegDataset): + """CraniumDataset dataset. + + In segmentation map annotation for CraniumDataset, + 0 stands for background, which is included in 2 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('background', 'hemorrhage')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/tools/prepare_dataset.py new file mode 100644 index 0000000..1aa4e43 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/ct/cranium/tools/prepare_dataset.py @@ -0,0 +1,66 @@ +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.png' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' +tgt_img_dir = os.path.join(root_path, 'images/train/') +tgt_mask_dir = os.path.join(root_path, 'masks/train/') +os.system('mkdir -p ' + tgt_img_dir) +os.system('mkdir -p ' + tgt_mask_dir) + + +def read_single_array_from_pil(path): + return np.asarray(Image.open(path)) + + +def save_png_from_array(arr, save_path, mode=None): + Image.fromarray(arr, mode=mode).save(save_path) + + +def convert_label(img, convert_dict): + arr = np.zeros_like(img, dtype=np.uint8) + for c, i in convert_dict.items(): + arr[img == c] = i + return arr + + +patients_dir = os.path.join( + root_path, 'Cranium/computed-tomography-images-for-' + + 'intracranial-hemorrhage-detection-and-segmentation-1.0.0' + + '/Patients_CT') + +patients = sorted(os.listdir(patients_dir)) +for p in patients: + data_dir = os.path.join(patients_dir, p, 'brain') + file_names = os.listdir(data_dir) + img_w_mask_names = [ + _.replace('_HGE_Seg', '') for _ in file_names if 'Seg' in _ + ] + img_wo_mask_names = [ + _ for _ in file_names if _ not in img_w_mask_names and 'Seg' not in _ + ] + + for file_name in file_names: + path = os.path.join(data_dir, file_name) + img = read_single_array_from_pil(path) + tgt_name = file_name.replace('.jpg', img_suffix) + tgt_name = p + '_' + tgt_name + if 'Seg' in file_name: # is a mask + tgt_name = tgt_name.replace('_HGE_Seg', '') + mask_path = os.path.join(tgt_mask_dir, tgt_name) + mask = convert_label(img, convert_dict={0: 0, 255: 1}) + save_png_from_array(mask, mask_path) + else: + img_path = os.path.join(tgt_img_dir, tgt_name) + pil = Image.fromarray(img).convert('RGB') + pil.save(img_path) + + if file_name in img_wo_mask_names: + mask = np.zeros_like(img, dtype=np.uint8) + mask_path = os.path.join(tgt_mask_dir, tgt_name) + save_png_from_array(mask, mask_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/README.md new file mode 100644 index 0000000..6e44e41 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/README.md @@ -0,0 +1,149 @@ +# ISIC-2016 Task1 + +## Description + +This project support **`ISIC-2016 Task1 `**, and the dataset used in this project can be downloaded from [here](https://challenge.isic-archive.com/data/#2016). + +### Dataset Overview + +The overarching goal of the challenge is to develop image analysis tools to enable the automated diagnosis of melanoma from dermoscopic images. + +This challenge provides training data (~900 images) for participants to engage in all 3 components of lesion image analysis. A separate test dataset (~350 images) will be provided for participants to generate and submit automated results. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ---------------------------------------------------------------- | ----------------- | ------------ | ---------- | ------------ | --------------------- | ---------------------- | ------------ | ---------------------------------------------------------------------- | +| [ISIC-2016 Task1](https://challenge.isic-archive.com/data/#2016) | full body | segmentation | dermoscopy | 2 | 900/-/379- | yes/-/yes | 2016 | [CC-0](https://creativecommons.org/share-your-work/public-domain/cc0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :---------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 900 | 82.08 | - | - | 379 | 81.98 | +| skin lesion | 900 | 17.92 | - | - | 379 | 18.02 | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/dermoscopy/isic2016_task1/isic2016_task1.png) + +### Prerequisites + +- Python 3.8 +- PyTorch 1.10.0 +- pillow(PIL) 9.3.0 +- scikit-learn(sklearn) 1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of PYTHONPATH, which should point to the project's directory so that Python can locate the module files. In isic2016_task1/ root directory, run the following line to add the current directory to PYTHONPATH: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://challenge.isic-archive.com/data/#2016) and decompression data to path 'data/'. +- run script `"python tools/prepare_dataset.py"` to split dataset and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── dermoscopy + │ │ │ │ ├── isic2016_task1 + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── test.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── xxx.png + │ │ │ │ │ │ │ ├── test + │ │ │ │ | │ │ │ ├── yyy.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── yyy.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── xxx.png + │ │ │ │ │ │ │ ├── test + │ │ │ │ | │ │ │ ├── yyy.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── yyy.png +``` + +### Training commands + +```shell +mim train mmseg ./configs/${CONFIG_PATH} +``` + +To train on multiple GPUs, e.g. 8 GPUs, run the following command: + +```shell +mim train mmseg ./configs/${CONFIG_PATH} --launcher pytorch --gpus 8 +``` + +### Testing commands + +```shell +mim test mmseg ./configs/${CONFIG_PATH} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Results + +### ISIC-2016 Task1 + +| Method | Backbone | Crop Size | lr | mIoU | mDice | config | +| :-------------: | :------: | :-------: | :----: | :--: | :---: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| fcn_unet_s5-d16 | unet | 512x512 | 0.01 | - | - | [config](https://github.com/open-mmlab/mmsegmentation/tree/dev-1.x/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2016-task1-512x512.py) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.001 | - | - | [config](https://github.com/open-mmlab/mmsegmentation/tree/dev-1.x/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2016-task1-512x512.py) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.0001 | - | - | [config](https://github.com/open-mmlab/mmsegmentation/tree/dev-1.x/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2016-task1-512x512.py) | + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] Test-time correctness + + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2016-task1-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2016-task1-512x512.py new file mode 100644 index 0000000..5638de4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2016-task1-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './isic2016-task1_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.isic2016-task1_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2016-task1-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2016-task1-512x512.py new file mode 100644 index 0000000..bf17faa --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2016-task1-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './isic2016-task1_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.isic2016-task1_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2016-task1-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2016-task1-512x512.py new file mode 100644 index 0000000..f7bfcf6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2016-task1-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './isic2016-task1_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.isic2016-task1_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/isic2016-task1_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/isic2016-task1_512x512.py new file mode 100644 index 0000000..029f5d4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/configs/isic2016-task1_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'ISIC2017Task1' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='test.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/datasets/isic2016-task1_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/datasets/isic2016-task1_dataset.py new file mode 100644 index 0000000..8f11bdd --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/datasets/isic2016-task1_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class ISIC2017Task1(BaseSegDataset): + """ISIC2017Task1 dataset. + + In segmentation map annotation for ISIC2017Task1, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('normal', 'skin lesion')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/tools/prepare_dataset.py new file mode 100644 index 0000000..ef4dad5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2016_task1/tools/prepare_dataset.py @@ -0,0 +1,120 @@ +import glob +import os +import shutil + +import numpy as np +from PIL import Image + + +def check_maskid(train_imgs): + for i in train_masks: + img = Image.open(i) + print(np.unique(np.array(img))) + + +def reformulate_file(image_list, mask_list): + file_list = [] + for idx, (imgp, + maskp) in enumerate(zip(sorted(image_list), sorted(mask_list))): + item = {'image': imgp, 'label': maskp} + file_list.append(item) + return file_list + + +def check_file_exist(pair_list): + rel_path = os.getcwd() + for idx, sample in enumerate(pair_list): + image_path = sample['image'] + assert os.path.exists(os.path.join(rel_path, image_path)) + if 'label' in sample: + mask_path = sample['label'] + assert os.path.exists(os.path.join(rel_path, mask_path)) + print('all file path ok!') + + +def convert_maskid(mask): + # add mask id conversion + arr_mask = np.array(mask).astype(np.uint8) + arr_mask[arr_mask == 255] = 1 + return Image.fromarray(arr_mask) + + +def process_dataset(file_lists, part_dir_dict): + for ith, part in enumerate(file_lists): + part_dir = part_dir_dict[ith] + for sample in part: + # read image and mask + image_path = sample['image'] + if 'label' in sample: + mask_path = sample['label'] + + basename = os.path.basename(image_path) + targetname = basename.split('.')[0] # from image name + + # check image file + img_save_path = os.path.join(root_path, 'images', part_dir, + targetname + save_img_suffix) + if not os.path.exists(img_save_path): + if not image_path.endswith('.png'): + src = Image.open(image_path) + src.save(img_save_path) + else: + shutil.copy(image_path, img_save_path) + + if mask_path is not None: + mask_save_path = os.path.join(root_path, 'masks', part_dir, + targetname + save_seg_map_suffix) + if not os.path.exists(mask_save_path): + # check mask file + mask = Image.open(mask_path).convert('L') + # convert mask id + mask = convert_maskid(mask) + if not mask_path.endswith('.png'): + mask.save(mask_save_path) + else: + mask.save(mask_save_path) + + # print image num + part_dir_folder = os.path.join(root_path, 'images', part_dir) + print( + f'{part_dir} has {len(os.listdir(part_dir_folder))} images completed!' # noqa + ) + + +if __name__ == '__main__': + + root_path = 'data/' # original file + img_suffix = '.jpg' + seg_map_suffix = '.png' + save_img_suffix = '.png' + save_seg_map_suffix = '.png' + + train_imgs = glob.glob('data/ISBI2016_ISIC_Part1_Training_Data/*' # noqa + + img_suffix) + train_masks = glob.glob( + 'data/ISBI2016_ISIC_Part1_Training_GroundTruth/*' # noqa + + seg_map_suffix) + + test_imgs = glob.glob('data/ISBI2016_ISIC_Part1_Test_Data/*' + img_suffix) + test_masks = glob.glob( + 'data/ISBI2016_ISIC_Part1_Test_GroundTruth/*' # noqa + + seg_map_suffix) + + assert len(train_imgs) == len(train_masks) + assert len(test_imgs) == len(test_masks) + + print(f'training images: {len(train_imgs)}, test images: {len(test_imgs)}') + + os.system('mkdir -p ' + root_path + 'images/train/') + os.system('mkdir -p ' + root_path + 'images/test/') + os.system('mkdir -p ' + root_path + 'masks/train/') + os.system('mkdir -p ' + root_path + 'masks/test/') + + train_pair_list = reformulate_file(train_imgs, train_masks) + test_pair_list = reformulate_file(test_imgs, test_masks) + + check_file_exist(train_pair_list) + check_file_exist(test_pair_list) + + part_dir_dict = {0: 'train/', 1: 'test/'} + process_dataset([train_pair_list, test_pair_list], part_dir_dict) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/README.md new file mode 100644 index 0000000..c7cc270 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/README.md @@ -0,0 +1,158 @@ +# ISIC-2017 Task1 + +## Description + +This project support **`ISIC-2017 Task1 `**, and the dataset used in this project can be downloaded from [here](https://challenge.isic-archive.com/data/#2017). + +### Dataset Overview + +The goal of the challenge is to help participants develop image analysis tools to enable the automated diagnosis of melanoma from dermoscopic images. + +This challenge provides training data (~2000 images) for participants to engage in all 3 components of lesion image analysis. A separate public validation dataset (~150 images) and blind held-out test dataset (~600 images) will be provided for participants to generate and submit automated results. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ---------------------------------------------------------------- | ----------------- | ------------ | ---------- | ------------ | --------------------- | ---------------------- | ------------ | ---------------------------------------------------------------------- | +| [ISIC-2017 Task1](https://challenge.isic-archive.com/data/#2017) | full body | segmentation | dermoscopy | 2 | 2000/150/600 | yes/yes/yes | 2017 | [CC-0](https://creativecommons.org/share-your-work/public-domain/cc0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :---------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| normal | 2000 | 82.86 | 150 | 73.88 | 600 | 70.62 | +| skin lesion | 2000 | 17.14 | 150 | 26.12 | 600 | 29.38 | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/dermoscopy/isic2017_task1/isic2017_task1.png) + +### Prerequisites + +- Python 3.8 +- PyTorch 1.10.0 +- pillow(PIL) 9.3.0 +- scikit-learn(sklearn) 1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of PYTHONPATH, which should point to the project's directory so that Python can locate the module files. In isic2017_task1/ root directory, run the following line to add the current directory to PYTHONPATH: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://challenge.isic-archive.com/data/#2017) and decompression data to path 'data/'. +- run script `"python tools/prepare_dataset.py"` to split dataset and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── dermoscopy + │ │ │ │ ├── isic2017_task1 + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── test.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── xxx.png + │ │ │ │ │ │ │ ├── val + │ │ │ │ | │ │ │ ├── yyy.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── yyy.png + │ │ │ │ │ │ │ ├── test + │ │ │ │ | │ │ │ ├── yyy.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── yyy.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── xxx.png + │ │ │ │ │ │ │ ├── val + │ │ │ │ | │ │ │ ├── yyy.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── yyy.png + │ │ │ │ │ │ │ ├── test + │ │ │ │ | │ │ │ ├── yyy.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │   │   │ └── yyy.png +``` + +### Training commands + +```shell +mim train mmseg ./configs/${CONFIG_PATH} +``` + +To train on multiple GPUs, e.g. 8 GPUs, run the following command: + +```shell +mim train mmseg ./configs/${CONFIG_PATH} --launcher pytorch --gpus 8 +``` + +### Testing commands + +```shell +mim test mmseg ./configs/${CONFIG_PATH} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Results + +### ISIC-2017 Task1 + +| Method | Backbone | Crop Size | lr | mIoU | mDice | config | +| :-------------: | :------: | :-------: | :----: | :--: | :---: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| fcn_unet_s5-d16 | unet | 512x512 | 0.01 | - | - | [config](https://github.com/open-mmlab/mmsegmentation/tree/dev-1.x/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2017-task1-512x512.py) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.001 | - | - | [config](https://github.com/open-mmlab/mmsegmentation/tree/dev-1.x/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2017-task1-512x512.py) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.0001 | - | - | [config](https://github.com/open-mmlab/mmsegmentation/tree/dev-1.x/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2017-task1-512x512.py) | + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [ ] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2017-task1-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2017-task1-512x512.py new file mode 100644 index 0000000..58d0a12 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_isic2017-task1-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './isic2017-task1_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.isic2017-task1_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2017-task1-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2017-task1-512x512.py new file mode 100644 index 0000000..3becacf --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_isic2017-task1-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './isic2017-task1_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.isic2017-task1_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2017-task1-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2017-task1-512x512.py new file mode 100644 index 0000000..654ef4d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_isic2017-task1-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './isic2017-task1_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.isic2017-task1_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/isic2017-task1_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/isic2017-task1_512x512.py new file mode 100644 index 0000000..95997a1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/configs/isic2017-task1_512x512.py @@ -0,0 +1,41 @@ +dataset_type = 'ISIC2017Task1' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/train/', seg_map_path='masks/train/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict(img_path='images/val/', seg_map_path='masks/val/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/datasets/isic2017-task1_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/datasets/isic2017-task1_dataset.py new file mode 100644 index 0000000..8f11bdd --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/datasets/isic2017-task1_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class ISIC2017Task1(BaseSegDataset): + """ISIC2017Task1 dataset. + + In segmentation map annotation for ISIC2017Task1, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('normal', 'skin lesion')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/tools/prepare_dataset.py new file mode 100644 index 0000000..b3643c9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/dermoscopy/isic2017_task1/tools/prepare_dataset.py @@ -0,0 +1,127 @@ +import glob +import os +import shutil + +import numpy as np +from PIL import Image + + +def check_maskid(train_imgs): + for i in train_masks: + img = Image.open(i) + print(np.unique(np.array(img))) + + +def reformulate_file(image_list, mask_list): + file_list = [] + for idx, (imgp, + maskp) in enumerate(zip(sorted(image_list), sorted(mask_list))): + item = {'image': imgp, 'label': maskp} + file_list.append(item) + return file_list + + +def convert_maskid(mask): + # add mask id conversion + arr_mask = np.array(mask).astype(np.uint8) + arr_mask[arr_mask == 255] = 1 + return Image.fromarray(arr_mask) + + +def check_file_exist(pair_list): + rel_path = os.getcwd() + for idx, sample in enumerate(pair_list): + image_path = sample['image'] + assert os.path.exists(os.path.join(rel_path, image_path)) + if 'label' in sample: + mask_path = sample['label'] + assert os.path.exists(os.path.join(rel_path, mask_path)) + print('all file path ok!') + + +def process_dataset(file_lists, part_dir_dict): + for ith, part in enumerate(file_lists): + part_dir = part_dir_dict[ith] + for sample in part: + # read image and mask + image_path = sample['image'] + if 'label' in sample: + mask_path = sample['label'] + + basename = os.path.basename(image_path) + targetname = basename.split('.')[0] # from image name + + # check image file + img_save_path = os.path.join(root_path, 'images', part_dir, + targetname + save_img_suffix) + if not os.path.exists(img_save_path): + if not image_path.endswith('.png'): + src = Image.open(image_path) + src.save(img_save_path) + else: + shutil.copy(image_path, img_save_path) + + if mask_path is not None: + mask_save_path = os.path.join(root_path, 'masks', part_dir, + targetname + save_seg_map_suffix) + if not os.path.exists(mask_save_path): + # check mask file + mask = Image.open(mask_path).convert('L') + # convert mask id + mask = convert_maskid(mask) + if not mask_path.endswith('.png'): + mask.save(mask_save_path) + else: + mask.save(mask_save_path) + + # print image num + part_dir_folder = os.path.join(root_path, 'images', part_dir) + print( + f'{part_dir} has {len(os.listdir(part_dir_folder))} images completed!' # noqa + ) + + +if __name__ == '__main__': + + root_path = 'data/' # original file + img_suffix = '.jpg' + seg_map_suffix = '.png' + save_img_suffix = '.png' + save_seg_map_suffix = '.png' + + train_imgs = glob.glob('data/ISIC-2017_Training_Data/*' + img_suffix) + train_masks = glob.glob('data/ISIC-2017_Training_Part1_GroundTruth/*' + + seg_map_suffix) + + val_imgs = glob.glob('data/ISIC-2017_Validation_Data/*' + img_suffix) + val_masks = glob.glob('data/ISIC-2017_Validation_Part1_GroundTruth/*' + + seg_map_suffix) + + test_imgs = glob.glob('data/ISIC-2017_Test_v2_Data/*' + img_suffix) + test_masks = glob.glob('data/ISIC-2017_Test_v2_Part1_GroundTruth/*' + + seg_map_suffix) + + assert len(train_imgs) == len(train_masks) + assert len(val_imgs) == len(val_masks) + assert len(test_imgs) == len(test_masks) + + os.system('mkdir -p ' + root_path + 'images/train/') + os.system('mkdir -p ' + root_path + 'images/val/') + os.system('mkdir -p ' + root_path + 'images/test/') + os.system('mkdir -p ' + root_path + 'masks/train/') + os.system('mkdir -p ' + root_path + 'masks/val/') + os.system('mkdir -p ' + root_path + 'masks/test/') + + part_dir_dict = {0: 'train/', 1: 'val/', 2: 'test/'} + + train_pair_list = reformulate_file(train_imgs, train_masks) + val_pair_list = reformulate_file(val_imgs, val_masks) + test_pair_list = reformulate_file(test_imgs, test_masks) + + check_file_exist(train_pair_list) + check_file_exist(val_pair_list) + check_file_exist(test_pair_list) + + part_dir_dict = {0: 'train/', 1: 'val/', 2: 'test/'} + process_dataset([train_pair_list, val_pair_list, test_pair_list], + part_dir_dict) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/README.md new file mode 100644 index 0000000..ea597bc --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/README.md @@ -0,0 +1,145 @@ +# Kvasir-Sessile Dataset (Kvasir SEG) + +## Description + +This project supports **`Kvasir-Sessile Dataset (Kvasir SEG) `**, which can be downloaded from [here](https://opendatalab.com/Kvasir-Sessile_dataset). + +## Dataset Overview + +The Kvasir-SEG dataset contains polyp images and their corresponding ground truth from the Kvasir Dataset v2. The resolution of the images contained in Kvasir-SEG varies from 332x487 to 1920x1072 pixels. + + + +### Information Statistics + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------------------------------- | ----------------- | ------------ | --------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------- | +| [Kvarsir-SEG](https://opendatalab.com/Kvasir-Sessile_dataset) | abdomen | segmentation | endoscopy | 2 | 196/-/- | yes/-/- | 2020 | [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 196 | 92.31 | - | - | - | - | +| polyp | 196 | 7.69 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![kvasir-seg](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/endoscopy_images/kvasir_seg/kvasir_seg_dataset.png?raw=true) + +### Dataset Citation + +``` +@inproceedings{jha2020kvasir, + title={Kvasir-seg: A segmented polyp dataset}, + author={Jha, Debesh and Smedsrud, Pia H and Riegler, Michael A and Halvorsen, P{\aa}l and Lange, Thomas de and Johansen, Dag and Johansen, H{\aa}vard D}, + booktitle={International Conference on Multimedia Modeling}, + pages={451--462}, + year={2020}, + organization={Springer} + } +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `kvasir_seg/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://opendatalab.com/Kvasir-Sessile_dataset) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── endoscopy + │ │ │ │ ├── kvasir_seg + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 156 | 92.28 | 40 | 92.41 | - | - | +| polyp | 156 | 7.72 | 40 | 7.59 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg .configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_kvasir-seg-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_kvasir-seg-512x512.py new file mode 100644 index 0000000..145d5a7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_kvasir-seg-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './kvasir-seg_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.kvasir-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_kvasir-seg-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_kvasir-seg-512x512.py new file mode 100644 index 0000000..3ea05c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_kvasir-seg-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './kvasir-seg_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.kvasir-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_kvasir-seg-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_kvasir-seg-512x512.py new file mode 100644 index 0000000..7e064a7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_kvasir-seg-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './kvasir-seg_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.kvasir-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_kvasir-seg-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_kvasir-seg-512x512.py new file mode 100644 index 0000000..0fc1d6e --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_kvasir-seg-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './kvasir-seg_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.kvasir-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/kvasir-seg_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/kvasir-seg_512x512.py new file mode 100644 index 0000000..e8b2467 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/configs/kvasir-seg_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'KvasirSEGDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/datasets/kvasir-seg_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/datasets/kvasir-seg_dataset.py new file mode 100644 index 0000000..9d60132 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/datasets/kvasir-seg_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class KvasirSEGDataset(BaseSegDataset): + """KvasirSEGDataset dataset. + + In segmentation map annotation for KvasirSEGDataset, 0 stands for + background, which is included in 2 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` is + fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False.. + """ + METAINFO = dict(classes=('background', 'polyp')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/tools/prepare_dataset.py new file mode 100644 index 0000000..74c43e9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg/tools/prepare_dataset.py @@ -0,0 +1,87 @@ +import glob +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.jpg' +seg_map_suffix = '.jpg' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' +tgt_img_dir = os.path.join(root_path, 'images/train/') +tgt_mask_dir = os.path.join(root_path, 'masks/train/') +os.system('mkdir -p ' + tgt_img_dir) +os.system('mkdir -p ' + tgt_mask_dir) + + +def filter_suffix_recursive(src_dir, suffix): + # filter out file names and paths in source directory + suffix = '.' + suffix if '.' not in suffix else suffix + file_paths = glob.glob( + os.path.join(src_dir, '**', '*' + suffix), recursive=True) + file_names = [_.split('/')[-1] for _ in file_paths] + return sorted(file_paths), sorted(file_names) + + +def convert_label(img, convert_dict): + arr = np.zeros_like(img, dtype=np.uint8) + for c, i in convert_dict.items(): + arr[img == c] = i + return arr + + +def convert_pics_into_pngs(src_dir, tgt_dir, suffix, convert='RGB'): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_img_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + num = len(src_paths) + img = np.array(Image.open(src_path)) + if len(img.shape) == 2: + pil = Image.fromarray(img).convert(convert) + elif len(img.shape) == 3: + pil = Image.fromarray(img) + else: + raise ValueError('Input image not 2D/3D: ', img.shape) + + pil.save(tgt_path) + print(f'processed {i+1}/{num}.') + + +def convert_label_pics_into_pngs(src_dir, + tgt_dir, + suffix, + convert_dict={ + 0: 0, + 255: 1 + }): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + num = len(src_paths) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_seg_map_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + + img = np.array(Image.open(src_path)) + img = convert_label(img, convert_dict) + Image.fromarray(img).save(tgt_path) + print(f'processed {i+1}/{num}.') + + +if __name__ == '__main__': + + convert_pics_into_pngs( + os.path.join(root_path, 'sessile-main-Kvasir-SEG/images'), + tgt_img_dir, + suffix=img_suffix) + + convert_label_pics_into_pngs( + os.path.join(root_path, 'sessile-main-Kvasir-SEG/masks'), + tgt_mask_dir, + suffix=seg_map_suffix) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/README.md new file mode 100644 index 0000000..80eb00f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/README.md @@ -0,0 +1,145 @@ +# Kvasir-SEG Segmented Polyp Dataset from Aliyun (Kvasir SEG Aliyun) + +## Description + +This project supports **`Kvasir-SEG Segmented Polyp Dataset from Aliyun (Kvasir SEG Aliyun) `**, which can be downloaded from [here](https://tianchi.aliyun.com/dataset/84385). + +### Dataset Overview + +Colorectal cancer is the second most common cancer type among women and third most common among men. Polyps are precursors to colorectal cancer and therefore important to detect and remove at an early stage. Polyps are found in nearly half of the individuals at age 50 that undergo a colonoscopy screening, and their frequency increase with age.Polyps are abnormal tissue growth from the mucous membrane, which is lining the inside of the GI tract, and can sometimes be cancerous. Colonoscopy is the gold standard for detection and assessment of these polyps with subsequent biopsy and removal of the polyps. Early disease detection has a huge impact on survival from colorectal cancer. Increasing the detection of polyps has been shown to decrease risk of colorectal cancer. Thus, automatic detection of more polyps at an early stage can play a crucial role in prevention and survival from colorectal cancer. + +The Kvasir-SEG dataset is based on the previous Kvasir dataset, which is the first multi-class dataset for gastrointestinal (GI) tract disease detection and classification. It contains annotated polyp images and their corresponding masks. The pixels depicting polyp tissue, the ROI, are represented by the foreground (white mask), while the background (in black) does not contain positive pixels. These images were collected and verified by experienced gastroenterologists from Vestre Viken Health Trust in Norway. The classes include anatomical landmarks, pathological findings and endoscopic procedures. + +### Information Statistics + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------------------------ | ----------------- | ------------ | --------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------- | +| [kvasir-seg](https://tianchi.aliyun.com/dataset/84385) | abdomen | segmentation | endoscopy | 2 | 1000/-/- | yes/-/- | 2020 | [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 1000 | 84.72 | - | - | - | - | +| polyp | 1000 | 15.28 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![kvasir_seg_aliyun](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/endoscopy_images/kvasir_seg_aliyun/kvasir_seg_aliyun_dataset.png?raw=true) + +### Dataset Citation + +``` +@inproceedings{jha2020kvasir, + title={Kvasir-seg: A segmented polyp dataset}, + author={Jha, Debesh and Smedsrud, Pia H and Riegler, Michael A and Halvorsen, P{\aa}l and Lange, Thomas de and Johansen, Dag and Johansen, H{\aa}vard D}, + booktitle={International Conference on Multimedia Modeling}, + pages={451--462}, + year={2020}, + organization={Springer} + } +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `kvasir_seg_aliyun/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://tianchi.aliyun.com/dataset/84385) and decompression data to path 'data/.'. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── endoscopy + │ │ │ │ ├── kvasir_seg_aliyun + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 800 | 84.66 | 200 | 84.94 | - | - | +| polyp | 800 | 15.34 | 200 | 15.06 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_kvasir-seg-aliyun-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_kvasir-seg-aliyun-512x512.py new file mode 100644 index 0000000..b59db95 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_kvasir-seg-aliyun-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './kvasir-seg-aliyun_512x512.py', 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.kvasir-seg-aliyun_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_kvasir-seg-aliyun-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_kvasir-seg-aliyun-512x512.py new file mode 100644 index 0000000..6c52668 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_kvasir-seg-aliyun-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './kvasir-seg-aliyun_512x512.py', 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.kvasir-seg-aliyun_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_kvasir-seg-aliyun-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_kvasir-seg-aliyun-512x512.py new file mode 100644 index 0000000..a192a5b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_kvasir-seg-aliyun-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './kvasir-seg-aliyun_512x512.py', 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.kvasir-seg-aliyun_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_kvasir-seg-aliyun-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_kvasir-seg-aliyun-512x512.py new file mode 100644 index 0000000..5325e1f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_kvasir-seg-aliyun-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './kvasir-seg-aliyun_512x512.py', 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.kvasir-seg-aliyun_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/kvasir-seg-aliyun_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/kvasir-seg-aliyun_512x512.py new file mode 100644 index 0000000..5f86880 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/configs/kvasir-seg-aliyun_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'KvasirSEGAliyunDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/datasets/kvasir-seg-aliyun_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/datasets/kvasir-seg-aliyun_dataset.py new file mode 100644 index 0000000..198caf0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/datasets/kvasir-seg-aliyun_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class KvasirSEGAliyunDataset(BaseSegDataset): + """KvasirSEGAliyunDataset dataset. + + In segmentation map annotation for KvasirSEGAliyunDataset, + 0 stands for background,which is included in 2 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False.. + """ + METAINFO = dict(classes=('background', 'polyp')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/tools/prepare_dataset.py new file mode 100644 index 0000000..b230e7f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/endoscopy/kvasir_seg_aliyun/tools/prepare_dataset.py @@ -0,0 +1,86 @@ +import glob +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.jpg' +seg_map_suffix = '.jpg' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' +tgt_img_dir = os.path.join(root_path, 'images/train/') +tgt_mask_dir = os.path.join(root_path, 'masks/train/') +os.system('mkdir -p ' + tgt_img_dir) +os.system('mkdir -p ' + tgt_mask_dir) + + +def filter_suffix_recursive(src_dir, suffix): + # filter out file names and paths in source directory + suffix = '.' + suffix if '.' not in suffix else suffix + file_paths = glob.glob( + os.path.join(src_dir, '**', '*' + suffix), recursive=True) + file_names = [_.split('/')[-1] for _ in file_paths] + return sorted(file_paths), sorted(file_names) + + +def convert_label(img, convert_dict): + arr = np.zeros_like(img, dtype=np.uint8) + for c, i in convert_dict.items(): + arr[img == c] = i + return arr + + +def convert_pics_into_pngs(src_dir, tgt_dir, suffix, convert='RGB'): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_img_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + num = len(src_paths) + img = np.array(Image.open(src_path)) + if len(img.shape) == 2: + pil = Image.fromarray(img).convert(convert) + elif len(img.shape) == 3: + pil = Image.fromarray(img) + else: + raise ValueError('Input image not 2D/3D: ', img.shape) + + pil.save(tgt_path) + print(f'processed {i+1}/{num}.') + + +def convert_label_pics_into_pngs(src_dir, + tgt_dir, + suffix, + convert_dict={ + 0: 0, + 255: 1 + }): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + num = len(src_paths) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_seg_map_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + + img = np.array(Image.open(src_path).convert('L')) + img = convert_label(img, convert_dict) + Image.fromarray(img).save(tgt_path) + print(f'processed {i+1}/{num}.') + + +if __name__ == '__main__': + convert_pics_into_pngs( + os.path.join(root_path, 'Kvasir-SEG/images'), + tgt_img_dir, + suffix=img_suffix) + + convert_label_pics_into_pngs( + os.path.join(root_path, 'Kvasir-SEG/masks'), + tgt_mask_dir, + suffix=seg_map_suffix) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/README.md new file mode 100644 index 0000000..c2c61c4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/README.md @@ -0,0 +1,158 @@ +# Vessel Assessment and Measurement Platform for Images of the REtina + +## Description + +This project support **`Vessel Assessment and Measurement Platform for Images of the REtina`**, and the dataset used in this project can be downloaded from [here](https://vampire.computing.dundee.ac.uk/vesselseg.html). + +### Dataset Overview + +In order to promote evaluation of vessel segmentation on ultra-wide field-of-view (UWFV) fluorescein angriogram (FA) frames, we make public 8 frames from two different sequences, the manually annotated images and the result of our automatic vessel segmentation algorithm. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ---------------------------------------------------------------- | ----------------- | ------------ | ---------------------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------- | +| [Vampire](https://vampire.computing.dundee.ac.uk/vesselseg.html) | vessel | segmentation | fluorescein angriogram | 2 | 8/-/- | yes/-/- | 2017 | [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 8 | 96.75 | - | - | - | - | +| vessel | 8 | 3.25 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/fluorescein_angriogram/vampire/vampire_dataset.png) + +## Dataset Citation + +```bibtex + +@inproceedings{perez2011improving, + title={Improving vessel segmentation in ultra-wide field-of-view retinal fluorescein angiograms}, + author={Perez-Rovira, Adria and Zutis, K and Hubschman, Jean Pierre and Trucco, Emanuele}, + booktitle={2011 Annual International Conference of the IEEE Engineering in Medicine and Biology Society}, + pages={2614--2617}, + year={2011}, + organization={IEEE} +} + +@article{perez2011rerbee, + title={RERBEE: robust efficient registration via bifurcations and elongated elements applied to retinal fluorescein angiogram sequences}, + author={Perez-Rovira, Adria and Cabido, Raul and Trucco, Emanuele and McKenna, Stephen J and Hubschman, Jean Pierre}, + journal={IEEE Transactions on Medical Imaging}, + volume={31}, + number={1}, + pages={140--150}, + year={2011}, + publisher={IEEE} +} + +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `vampire/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://vampire.computing.dundee.ac.uk/vesselseg.html) and decompression data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to split dataset and change folder structure as below. +- run script `python ../../tools/split_seg_dataset.py` to split dataset. For the Bacteria_detection dataset, as there is no test or validation dataset, we sample 20% samples from the whole dataset as the validation dataset and 80% samples for training data and make two filename lists `train.txt` and `val.txt`. As we set the random seed as the hard code, we eliminated the randomness, the dataset split actually can be reproducible. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── fluorescein_angriogram + │ │ │ │ ├── vampire + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 6 | 97.48 | 2 | 94.54 | - | - | +| vessel | 6 | 2.52 | 2 | 5.46 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_PATH} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_PATH} --checkpoint ${CHECKPOINT_PATH} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [ ] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_vampire-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_vampire-512x512.py new file mode 100644 index 0000000..7f5273a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_vampire-512x512.py @@ -0,0 +1,19 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './vampire_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.vampire_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_vampire-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_vampire-512x512.py new file mode 100644 index 0000000..4382229 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_vampire-512x512.py @@ -0,0 +1,19 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './vampire_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.vampire_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + type='EncoderDecoder', + data_preprocessor=dict(size=img_scale), + pretrained=None, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_vampire-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_vampire-512x512.py new file mode 100644 index 0000000..8d93e17 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_vampire-512x512.py @@ -0,0 +1,22 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './vampire_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.vampire_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + decode_head=dict( + num_classes=2, + loss_decode=dict(type='CrossEntropyLoss', use_sigmoid=True), + out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/vampire_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/vampire_512x512.py new file mode 100644 index 0000000..4eda92f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/configs/vampire_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'VampireDataset' +data_root = 'data' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/datasets/__init__.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/datasets/__init__.py new file mode 100644 index 0000000..93f9cbf --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/datasets/__init__.py @@ -0,0 +1,3 @@ +from .vampire_dataset import VampireDataset + +__all__ = ['VampireDataset'] diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/datasets/vampire_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/datasets/vampire_dataset.py new file mode 100644 index 0000000..4d38040 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/datasets/vampire_dataset.py @@ -0,0 +1,28 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class VampireDataset(BaseSegDataset): + """VampireDataset dataset. + + In segmentation map annotation for VampireDataset, 0 stands for background, + which is included in 2 categories. ``reduce_zero_label`` is fixed to + False. The ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is + fixed to '.png'. + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + """ + METAINFO = dict(classes=('background', 'vessel')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/tools/prepare_dataset.py new file mode 100644 index 0000000..2755b5d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fluorescein_angriogram/vampire/tools/prepare_dataset.py @@ -0,0 +1,44 @@ +import os +import shutil + +from PIL import Image + +path = 'data' + +if not os.path.exists(os.path.join(path, 'images', 'train')): + os.system(f'mkdir -p {os.path.join(path, "images", "train")}') + +if not os.path.exists(os.path.join(path, 'masks', 'train')): + os.system(f'mkdir -p {os.path.join(path, "masks", "train")}') + +origin_data_path = os.path.join(path, 'vesselSegmentation') + +imgs_amd14 = os.listdir(os.path.join(origin_data_path, 'AMD14')) +imgs_ger7 = os.listdir(os.path.join(origin_data_path, 'GER7')) + +for img in imgs_amd14: + shutil.copy( + os.path.join(origin_data_path, 'AMD14', img), + os.path.join(path, 'images', 'train', img)) + # copy GT + img_gt = img.replace('.png', '-GT.png') + shutil.copy( + os.path.join(origin_data_path, 'AMD14-GT', f'{img_gt}'), + os.path.join(path, 'masks', 'train', img)) + +for img in imgs_ger7: + shutil.copy( + os.path.join(origin_data_path, 'GER7', img), + os.path.join(path, 'images', 'train', img)) + # copy GT + img_gt = img.replace('.bmp', '-GT.png') + img = img.replace('bmp', 'png') + shutil.copy( + os.path.join(origin_data_path, 'GER7-GT', img_gt), + os.path.join(path, 'masks', 'train', img)) + +imgs = os.listdir(os.path.join(path, 'images', 'train')) +for img in imgs: + if not img.endswith('.png'): + im = Image.open(os.path.join(path, 'images', 'train', img)) + im.save(os.path.join(path, 'images', 'train', img[:-4] + '.png')) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/README.md new file mode 100644 index 0000000..85d8a3e --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/README.md @@ -0,0 +1,155 @@ +# DR HAGIS: Diabetic Retinopathy, Hypertension, Age-related macular degeneration and Glacuoma ImageS + +## Description + +This project supports **`DR HAGIS: Diabetic Retinopathy, Hypertension, Age-related macular degeneration and Glacuoma ImageS`**, which can be downloaded from [here](https://paperswithcode.com/dataset/dr-hagis). + +### Dataset Overview + +The DR HAGIS database has been created to aid the development of vessel extraction algorithms suitable for retinal screening programmes. Researchers are encouraged to test their segmentation algorithms using this database. All thirty-nine fundus images were obtained from a diabetic retinopathy screening programme in the UK. Hence, all images were taken from diabetic patients. + +Besides the fundus images, the manual segmentation of the retinal surface vessels is provided by an expert grader. These manually segmented images can be used as the ground truth to compare and assess the automatic vessel extraction algorithms. Masks of the FOV are provided as well to quantify the accuracy of vessel extraction within the FOV only. The images were acquired in different screening centers, therefore reflecting the range of image resolutions, digital cameras and fundus cameras used in the clinic. The fundus images were captured using a Topcon TRC-NW6s, Topcon TRC-NW8 or a Canon CR DGi fundus camera with a horizontal 45 degree field-of-view (FOV). The images are 4752x3168 pixels, 3456x2304 pixels, 3126x2136 pixels, 2896x1944 pixels or 2816x1880 pixels in size. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------------------------- | ----------------- | ------------ | ------------------ | ------------ | --------------------- | ---------------------- | ------------ | ------- | +| [DR HAGIS](https://paperswithcode.com/dataset/dr-hagis) | head and neck | segmentation | fundus photography | 2 | 40/-/- | yes/-/- | 2017 | - | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 40 | 96.38 | - | - | - | - | +| vessel | 40 | 3.62 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/fundus_photography/dr_hagis/dr_hagis_dataset.png) + +## Usage + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `dr_hagis/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://paperswithcode.com/dataset/dr-hagis) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── fundus_photography + │ │ │ │ ├── dr_hagis + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 32 | 96.21 | 8 | 97.12 | - | - | +| vessel | 32 | 3.79 | 8 | 2.88 | - | - | + +### Training commands + +Train models on a single server with one GPU. + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +Test models on a single server with one GPU. + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Dataset Citation + +If this work is helpful for your research, please consider citing the below paper. + +``` +@article{holm2017dr, + title={DR HAGIS—a fundus image database for the automatic extraction of retinal surface vessels from diabetic patients}, + author={Holm, Sven and Russell, Greg and Nourrit, Vincent and McLoughlin, Niall}, + journal={Journal of Medical Imaging}, + volume={4}, + number={1}, + pages={014503--014503}, + year={2017}, + publisher={Society of Photo-Optical Instrumentation Engineers} +} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [ ] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/dr-hagis_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/dr-hagis_512x512.py new file mode 100644 index 0000000..93b9638 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/dr-hagis_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'DRHAGISDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_dr-hagis-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_dr-hagis-512x512.py new file mode 100644 index 0000000..9d14427 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_dr-hagis-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './dr-hagis_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.dr-hagis_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_dr-hagis-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_dr-hagis-512x512.py new file mode 100644 index 0000000..507ec74 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_dr-hagis-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './dr-hagis_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.dr-hagis_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_dr-hagis-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_dr-hagis-512x512.py new file mode 100644 index 0000000..092ae00 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_dr-hagis-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './dr-hagis_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.dr-hagis_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/datasets/dr-hagis_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/datasets/dr-hagis_dataset.py new file mode 100644 index 0000000..9659f0b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/datasets/dr-hagis_dataset.py @@ -0,0 +1,27 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class DRHAGISDataset(BaseSegDataset): + """DRHAGISDataset dataset. + + In segmentation map annotation for DRHAGISDataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + """ + METAINFO = dict(classes=('background', 'vessel')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=False, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/tools/prepare_dataset.py new file mode 100644 index 0000000..51f4df7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/dr_hagis/tools/prepare_dataset.py @@ -0,0 +1,41 @@ +import glob +import os +import shutil + +import mmengine +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.jpg' +seg_map_suffix = '_manual_orig.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +x_train = glob.glob(os.path.join('data/DRHAGIS/**/*' + img_suffix)) + +mmengine.mkdir_or_exist(root_path + 'images/train/') +mmengine.mkdir_or_exist(root_path + 'masks/train/') + +D3_palette = {0: (0, 0, 0), 1: (1, 1, 1)} +D3_invert_palette = {v: k for k, v in D3_palette.items()} +D2_255_convert_dict = {0: 0, 255: 1} + +part_dir_dict = {0: 'train/', 1: 'val/'} +for ith, part in enumerate([x_train]): + part_dir = part_dir_dict[ith] + for img in part: + basename = os.path.basename(img) + shutil.copy( + img, root_path + 'images/' + part_dir + basename.split('.')[0] + + save_img_suffix) + mask_path = root_path + 'DRHAGIS/Manual_Segmentations/' + basename.split( # noqa + '.')[0] + seg_map_suffix + label = np.array(Image.open(mask_path)) + + save_mask_path = root_path + 'masks/' + part_dir + basename.split( + '.')[0] + save_seg_map_suffix # noqa + mask = np.array(Image.open(mask_path)).astype(np.uint8) + mask[mask == 255] = 1 + mask = Image.fromarray(mask) + mask.save(save_mask_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/README.md new file mode 100644 index 0000000..e834508 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/README.md @@ -0,0 +1,167 @@ +# Glaucoma grAding from Multi-Modality imAges Task3 + +## Description + +This project support **`Glaucoma grAding from Multi-Modality imAges Task3`**, and the dataset used in this project can be downloaded from [here](https://aistudio.baidu.com/aistudio/competition/detail/121/0/datasets). + +### Dataset Overview + +This regular-challenge dataset was provided by Sun Yat-sen Ophthalmic Center, Sun Yat-sen University, Guangzhou, China. The dataset contains 200 fundus color images: 100 pairs in the training set and 100 pairs in the test set. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ----------------------------------------------------------------------------------- | ----------------- | ------------ | --------------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------- | +| [GammaTask3](https://aistudio.baidu.com/aistudio/competition/detail/121/0/datasets) | eye | segmentation | fundus photophy | 3 | 100/-/100 | yes/-/- | 2021 | [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 100 | 99.02 | - | - | - | - | +| optic disc | 100 | 0.67 | - | - | - | - | +| optic cup | 100 | 0.31 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/fundus_photography/gamma3/gamma3_dataset.png) + +## Dataset Citation + +```bibtex +@article{fu2018joint, + title={Joint optic disc and cup segmentation based on multi-label deep network and polar transformation}, + author={Fu, Huazhu and Cheng, Jun and Xu, Yanwu and Wong, Damon Wing Kee and Liu, Jiang and Cao, Xiaochun}, + journal={IEEE transactions on medical imaging}, + volume={37}, + number={7}, + pages={1597--1605}, + year={2018}, + publisher={IEEE} +} + +@article{sevastopolsky2017optic, + title={Optic disc and cup segmentation methods for glaucoma detection with modification of U-Net convolutional neural network}, + author={Sevastopolsky, Artem}, + journal={Pattern Recognition and Image Analysis}, + volume={27}, + pages={618--624}, + year={2017}, + publisher={Springer} +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `gammm3/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://aistudio.baidu.com/aistudio/competition/detail/121/0/datasets) and decompression data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to split dataset and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── fundus_photography + │ │ │ │ ├── gamma3 + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ │ ├── test + │ │ │ │ | │ │ │ ├── yyy.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── yyy.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 80 | 99.01 | 20 | 99.07 | - | - | +| optic disc | 80 | 0.68 | 20 | 0.63 | - | - | +| optic cup | 80 | 0.32 | 20 | 0.31 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_PATH} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_PATH} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [ ] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_gamma3-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_gamma3-512x512.py new file mode 100644 index 0000000..0daac51 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_gamma3-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './gamma3_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.gamma3_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_gamma3-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_gamma3-512x512.py new file mode 100644 index 0000000..8a25cd0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_gamma3-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './gamma3_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.gamma3_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_gamma3-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_gamma3-512x512.py new file mode 100644 index 0000000..ea64843 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_gamma3-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './gamma3_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.gamma3_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/gamma3_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/gamma3_512x512.py new file mode 100644 index 0000000..d23ab55 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/configs/gamma3_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'Gamma3Dataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/datasets/gamma3_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/datasets/gamma3_dataset.py new file mode 100644 index 0000000..56cbdd6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/datasets/gamma3_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class Gamma3Dataset(BaseSegDataset): + """Gamma3Dataset dataset. + + In segmentation map annotation for Gamma3Dataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('background', 'disc', 'cup')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/tools/prepare_dataset.py new file mode 100644 index 0000000..eb820b6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/gamma3/tools/prepare_dataset.py @@ -0,0 +1,107 @@ +import glob +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.jpg' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' +tgt_img_train_dir = os.path.join(root_path, 'images/train/') +tgt_mask_train_dir = os.path.join(root_path, 'masks/train/') +tgt_img_test_dir = os.path.join(root_path, 'images/test/') +os.system('mkdir -p ' + tgt_img_train_dir) +os.system('mkdir -p ' + tgt_mask_train_dir) +os.system('mkdir -p ' + tgt_img_test_dir) + + +def filter_suffix_recursive(src_dir, suffix): + # filter out file names and paths in source directory + suffix = '.' + suffix if '.' not in suffix else suffix + file_paths = glob.glob( + os.path.join(src_dir, '**/*' + suffix), recursive=True) + file_names = [_.split('/')[-1] for _ in file_paths] + return sorted(file_paths), sorted(file_names) + + +def convert_label(img, convert_dict): + arr = np.zeros_like(img, dtype=np.uint8) + for c, i in convert_dict.items(): + arr[img == c] = i + return arr + + +def convert_pics_into_pngs(src_dir, tgt_dir, suffix, convert='RGB'): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_img_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + num = len(src_paths) + img = np.array(Image.open(src_path)) + if len(img.shape) == 2: + pil = Image.fromarray(img).convert(convert) + elif len(img.shape) == 3: + pil = Image.fromarray(img) + else: + raise ValueError('Input image not 2D/3D: ', img.shape) + + pil.save(tgt_path) + print(f'processed {i+1}/{num}.') + + +def convert_label_pics_into_pngs(src_dir, + tgt_dir, + suffix, + convert_dict={ + 0: 2, + 128: 1, + 255: 0 + }): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + num = len(src_paths) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_seg_map_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + + img = np.array(Image.open(src_path)) + img = convert_label(img, convert_dict) + Image.fromarray(img).save(tgt_path) + print(f'processed {i+1}/{num}.') + + +if __name__ == '__main__': + + convert_pics_into_pngs( + os.path.join( + root_path, + 'task3_disc_cup_segmentation/training/fundus color images/'), + tgt_img_train_dir, + suffix=img_suffix) + + convert_pics_into_pngs( + os.path.join( + root_path, + 'task3_disc_cup_segmentation/testing/fundus color images/'), + tgt_img_test_dir, + suffix=img_suffix) + + convert_label_pics_into_pngs( + os.path.join(root_path, + 'task3_disc_cup_segmentation/training/Disc_Cup_Mask/'), + tgt_mask_train_dir, + suffix=seg_map_suffix, + convert_dict={ + 0: 2, + 128: 1, + 255: 0 + }) + # original: [0, 128, 255] for ['optic cup', 'optic disc', 'background'] + # converted: [0, 1, 2] for ['background', 'optic disc', 'optic cup'] diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/README.md new file mode 100644 index 0000000..6f09203 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/README.md @@ -0,0 +1,140 @@ +# ORVS (Online Retinal image for Vessel Segmentation (ORVS)) + +## Description + +This project supports **`ORVS (Online Retinal image for Vessel Segmentation (ORVS))`**, which can be downloaded from [here](https://opendatalab.org.cn/ORVS). + +### Dataset Overview + +The ORVS dataset is a newly established collaboration between the Department of Computer Science and the Department of Vision Science at the University of Calgary. The dataset contains 49 images collected from a clinic in Calgary, Canada, consisting of 42 training images and 7 testing images. All images were obtained using a Zeiss Visucam 200 with a 30-degree field of view (FOV). The image size is 1444×1444 pixels with 24 bits per pixel. The images are stored in JPEG format with low compression, which is common in ophthalmic practice. All images were manually traced by an expert who has been working in the field of retinal image analysis and has been trained to mark all pixels belonging to retinal vessels. The Windows Paint 3D tool was used for manual image annotation. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------------------------ | ----------------- | ------------ | ------------------ | ------------ | --------------------- | ---------------------- | ------------ | ------- | +| [Bactteria detection](https://opendatalab.org.cn/ORVS) | bacteria | segmentation | fundus photography | 2 | 130/-/72 | yes/-/yes | 2020 | - | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 130 | 94.83 | - | - | 72 | 94.25 | +| vessel | 130 | 5.17 | - | - | 72 | 5.75 | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/fundus_photography/orvs/ORVS_dataset.png) + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `orvs/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- Clone this [repository](https://github.com/AbdullahSarhan/ICPRVessels), then move `Vessels-Datasets` to `data/`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── fundus_photography + │ │ │ │ ├── orvs + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── test.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Training commands + +Train models on a single server with one GPU. + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +Test models on a single server with one GPU. + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Dataset Citation + +If this work is helpful for your research, please consider citing the below paper. + +``` +@inproceedings{sarhan2021transfer, + title={Transfer learning through weighted loss function and group normalization for vessel segmentation from retinal images}, + author={Sarhan, Abdullah and Rokne, Jon and Alhajj, Reda and Crichton, Andrew}, + booktitle={2020 25th International Conference on Pattern Recognition (ICPR)}, + pages={9211--9218}, + year={2021}, + organization={IEEE} +} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [ ] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_orvs-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_orvs-512x512.py new file mode 100644 index 0000000..662f837 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_orvs-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './orvs_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.orvs_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_orvs-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_orvs-512x512.py new file mode 100644 index 0000000..c47cdb6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_orvs-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './orvs_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.orvs_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_orvs-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_orvs-512x512.py new file mode 100644 index 0000000..1097aad --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_orvs-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './orvs_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.orvs_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/orvs_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/orvs_512x512.py new file mode 100644 index 0000000..a5594de --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/configs/orvs_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'ORVSDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='test.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/datasets/orvs_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/datasets/orvs_dataset.py new file mode 100644 index 0000000..e915ae4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/datasets/orvs_dataset.py @@ -0,0 +1,27 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class ORVSDataset(BaseSegDataset): + """ORVSDataset dataset. + + In segmentation map annotation for ORVSDataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + """ + METAINFO = dict(classes=('background', 'vessel')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=False, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/tools/prepare_dataset.py new file mode 100644 index 0000000..f902d87 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/orvs/tools/prepare_dataset.py @@ -0,0 +1,55 @@ +import glob +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.jpg' +seg_map_suffix_list = ['.jpg', '.png', '.tif'] +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +x_train = glob.glob( + os.path.join('data/Vessels-Datasets/*/Train/Original/Images/*' + + img_suffix)) +x_test = glob.glob( + os.path.join('data/Vessels-Datasets/*/Test/Original/Images/*' + + img_suffix)) + +os.system('mkdir -p ' + root_path + 'images/train/') +os.system('mkdir -p ' + root_path + 'images/test/') +os.system('mkdir -p ' + root_path + 'masks/train/') +os.system('mkdir -p ' + root_path + 'masks/test/') + +part_dir_dict = {0: 'train/', 1: 'test/'} +for ith, part in enumerate([x_train, x_test]): + part_dir = part_dir_dict[ith] + for img in part: + type_name = img.split('/')[-5] + basename = type_name + '_' + os.path.basename(img) + save_img_path = root_path + 'images/' + part_dir + basename.split( + '.')[0] + save_img_suffix + Image.open(img).save(save_img_path) + + for seg_map_suffix in seg_map_suffix_list: + if os.path.exists('/'.join(img.split('/')[:-1]).replace( + 'Images', 'Labels')): + mask_path = img.replace('Images', 'Labels').replace( + img_suffix, seg_map_suffix) + else: + mask_path = img.replace('Images', 'labels').replace( + img_suffix, seg_map_suffix) + if os.path.exists(mask_path): + break + save_mask_path = root_path + 'masks/' + part_dir + basename.split( + '.')[0] + save_seg_map_suffix + masks = np.array(Image.open(mask_path).convert('L')).astype(np.uint8) + if len(np.unique(masks)) == 2 and 1 in np.unique(masks): + print(np.unique(masks)) + pass + else: + masks[masks < 128] = 0 + masks[masks >= 128] = 1 + masks = Image.fromarray(masks) + masks.save(save_mask_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/README.md new file mode 100644 index 0000000..0aea9b0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/README.md @@ -0,0 +1,135 @@ +# Retinal Images vessel Tree Extraction (RITE) + +## Description + +This project supports **`Retinal Images vessel Tree Extraction (RITE) `**, which can be downloaded from [here](https://opendatalab.com/RITE). + +### Dataset Overview + +The RITE (Retinal Images vessel Tree Extraction) is a database that enables comparative studies on segmentation or classification of arteries and veins on retinal fundus images, which is established based on the public available DRIVE database (Digital Retinal Images for Vessel Extraction). RITE contains 40 sets of images, equally separated into a training subset and a test subset, the same as DRIVE. The two subsets are built from the corresponding two subsets in DRIVE. For each set, there is a fundus photograph, a vessel reference standard. The fundus photograph is inherited from DRIVE. For the training set, the vessel reference standard is a modified version of 1st_manual from DRIVE. For the test set, the vessel reference standard is 2nd_manual from DRIVE. + +### Statistic Information + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------ | ----------------- | ------------ | ------------------ | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------- | +| [Rite](https://opendatalab.com/RITE) | head_and_neck | segmentation | fundus_photography | 2 | 20/-/20 | yes/-/yes | 2013 | [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 20 | 91.61 | - | - | 20 | 91.58 | +| vessel | 20 | 8.39 | - | - | 20 | 8.42 | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![rite](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/fundus_photography/rite/rite_dataset.png?raw=true) + +### Dataset Citation + +``` +@InProceedings{10.1007/978-3-642-40763-5_54, + author={Hu, Qiao and Abr{\`a}moff, Michael D. and Garvin, Mona K.}, + title={Automated Separation of Binary Overlapping Trees in Low-Contrast Color Retinal Images}, + booktitle={Medical Image Computing and Computer-Assisted Intervention -- MICCAI 2013}, + year={2013}, + pages={436--443}, +} + + +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 9.3.0 +- scikit-learn(sklearn) v1.2.0 1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `rite/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://opendatalab.com/RITE) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── fundus_photography + │ │ │ │ ├── rite + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_rite-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_rite-512x512.py new file mode 100644 index 0000000..27dd436 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_rite-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './rite_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.rite_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_rite-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_rite-512x512.py new file mode 100644 index 0000000..48f6f97 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_rite-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './rite_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.rite_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_rite-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_rite-512x512.py new file mode 100644 index 0000000..5f5b24b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_rite-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './rite_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.rite_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_rite-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_rite-512x512.py new file mode 100644 index 0000000..bf66b6f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_rite-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './rite_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.rite_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/rite_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/rite_512x512.py new file mode 100644 index 0000000..02f620c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/configs/rite_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'RITEDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='test.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/datasets/rite_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/datasets/rite_dataset.py new file mode 100644 index 0000000..99f688d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/datasets/rite_dataset.py @@ -0,0 +1,31 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class RITEDataset(BaseSegDataset): + """RITEDataset dataset. + + In segmentation map annotation for RITEDataset, + 0 stands for background, which is included in 2 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('background', 'vessel')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/tools/prepare_dataset.py new file mode 100644 index 0000000..ca7e996 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/fundus_photography/rite/tools/prepare_dataset.py @@ -0,0 +1,98 @@ +import glob +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.tif' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' +src_img_train_dir = os.path.join(root_path, 'AV_groundTruth/training/images/') +src_img_test_dir = os.path.join(root_path, 'AV_groundTruth/test/images/') +src_mask_train_dir = os.path.join(root_path, 'AV_groundTruth/training/vessel/') +src_mask_test_dir = os.path.join(root_path, 'AV_groundTruth/test/vessel/') + +tgt_img_train_dir = os.path.join(root_path, 'images/train/') +tgt_mask_train_dir = os.path.join(root_path, 'masks/train/') +tgt_img_test_dir = os.path.join(root_path, 'images/test/') +tgt_mask_test_dir = os.path.join(root_path, 'masks/test/') +os.system('mkdir -p ' + tgt_img_train_dir) +os.system('mkdir -p ' + tgt_mask_train_dir) +os.system('mkdir -p ' + tgt_img_test_dir) +os.system('mkdir -p ' + tgt_mask_test_dir) + + +def filter_suffix_recursive(src_dir, suffix): + # filter out file names and paths in source directory + suffix = '.' + suffix if '.' not in suffix else suffix + file_paths = glob.glob( + os.path.join(src_dir, '**', '*' + suffix), recursive=True) + file_names = [_.split('/')[-1] for _ in file_paths] + return sorted(file_paths), sorted(file_names) + + +def convert_label(img, convert_dict): + arr = np.zeros_like(img, dtype=np.uint8) + for c, i in convert_dict.items(): + arr[img == c] = i + return arr + + +def convert_pics_into_pngs(src_dir, tgt_dir, suffix, convert='RGB'): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_img_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + num = len(src_paths) + img = np.array(Image.open(src_path)) + if len(img.shape) == 2: + pil = Image.fromarray(img).convert(convert) + elif len(img.shape) == 3: + pil = Image.fromarray(img) + else: + raise ValueError('Input image not 2D/3D: ', img.shape) + + pil.save(tgt_path) + print(f'processed {i+1}/{num}.') + + +def convert_label_pics_into_pngs(src_dir, + tgt_dir, + suffix, + convert_dict={ + 0: 0, + 255: 1 + }): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + num = len(src_paths) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_seg_map_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + + img = np.array(Image.open(src_path)) + img = convert_label(img, convert_dict) + Image.fromarray(img).save(tgt_path) + print(f'processed {i+1}/{num}.') + + +if __name__ == '__main__': + + convert_pics_into_pngs( + src_img_train_dir, tgt_img_train_dir, suffix=img_suffix) + + convert_pics_into_pngs( + src_img_test_dir, tgt_img_test_dir, suffix=img_suffix) + + convert_label_pics_into_pngs( + src_mask_train_dir, tgt_mask_train_dir, suffix=seg_map_suffix) + + convert_label_pics_into_pngs( + src_mask_test_dir, tgt_mask_test_dir, suffix=seg_map_suffix) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/README.md new file mode 100644 index 0000000..97c4a0f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/README.md @@ -0,0 +1,123 @@ +# breastCancerCellSegmentation + +## Description + +This project supports **`breastCancerCellSegmentation`**, which can be downloaded from [here](https://www.heywhale.com/mw/dataset/5e9e9b35ebb37f002c625423). + +### Dataset Overview + +This dataset, with 58 H&E-stained histopathology images was used for breast cancer cell detection and associated real-world data. +Conventional histology uses a combination of hematoxylin and eosin stains, commonly referred to as H&E. These images are stained because most cells are inherently transparent with little or no intrinsic pigment. +Certain special stains selectively bind to specific components and can be used to identify biological structures such as cells. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| -------------------------------------------------------------------------------------------- | ----------------- | ------------ | -------------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------- | +| [breastCancerCellSegmentation](https://www.heywhale.com/mw/dataset/5e9e9b35ebb37f002c625423) | cell | segmentation | histopathology | 2 | 58/-/- | yes/-/- | 2020 | [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 58 | 98.37 | - | - | - | - | +| breastCancerCell | 58 | 1.63 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/histopathology/breastCancerCellSegmentation/breastCancerCellSegmentation_dataset.png) + +## Usage + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow (PIL) v9.3.0 +- scikit-learn (sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `breastCancerCellSegmentation/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- Download dataset from [here](https://www.heywhale.com/mw/dataset/5e9e9b35ebb37f002c625423) and save it to the `data/` directory . +- Decompress data to path `data/`. This will create a new folder named `data/breastCancerCellSegmentation/`, which contains the original image data. +- run script `python tools/prepare_dataset.py` to format data and change folder structure as below. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── histopathology + │ │ │ │ ├── breastCancerCellSegmentation + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── breastCancerCellSegmentation + | │ │ │ │ │ │ ├── train.txt + | │ │ │ │ │ │ ├── val.txt + | │ │ │ │ │ │ ├── images + | │ │ │ │ │ │ | ├── xxx.tif + | │ │ │ │ │ │ ├── masks + | │ │ │ │ │ │ | ├── xxx.TIF + +``` + +### Training commands + +Train models on a single server with one GPU. + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +Test models on a single server with one GPU. + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/breastCancerCellSegmentation_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/breastCancerCellSegmentation_512x512.py new file mode 100644 index 0000000..1cf0fcc --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/breastCancerCellSegmentation_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'breastCancerCellSegmentationDataset' +data_root = 'data/breastCancerCellSegmentation' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile', imdecode_backend='tifffile'), + dict(type='LoadAnnotations', imdecode_backend='tifffile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile', imdecode_backend='tifffile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations', imdecode_backend='tifffile'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images', seg_map_path='masks'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images', seg_map_path='masks'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_breastCancerCellSegmentation-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_breastCancerCellSegmentation-512x512.py new file mode 100644 index 0000000..55d1708 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_breastCancerCellSegmentation-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './breastCancerCellSegmentation_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.breastCancerCellSegmentation_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_breastCancerCellSegmentation-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_breastCancerCellSegmentation-512x512.py new file mode 100644 index 0000000..cf28aad --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_breastCancerCellSegmentation-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './breastCancerCellSegmentation_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.breastCancerCellSegmentation_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_breastCancerCellSegmentation-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_breastCancerCellSegmentation-512x512.py new file mode 100644 index 0000000..29aaff3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_breastCancerCellSegmentation-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './breastCancerCellSegmentation_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.breastCancerCellSegmentation_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/datasets/breastCancerCellSegmentation_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/datasets/breastCancerCellSegmentation_dataset.py new file mode 100644 index 0000000..eeceb63 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/datasets/breastCancerCellSegmentation_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class breastCancerCellSegmentationDataset(BaseSegDataset): + """breastCancerCellSegmentationDataset dataset. + + In segmentation map annotation for breastCancerCellSegmentationDataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('background', 'breastCancerCell')) + + def __init__(self, + img_suffix='_ccd.tif', + seg_map_suffix='.TIF', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/tools/prepare_dataset.py new file mode 100644 index 0000000..09cc689 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breastCancerCellSegmentation/tools/prepare_dataset.py @@ -0,0 +1,36 @@ +import argparse +import glob +import os + +from sklearn.model_selection import train_test_split + + +def save_anno(img_list, file_path, suffix): + # 只保留文件名,不保留后缀 + img_list = [x.split('/')[-1][:-len(suffix)] for x in img_list] + + with open(file_path, 'w') as file_: + for x in list(img_list): + file_.write(x + '\n') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument( + '--data_root', default='data/breastCancerCellSegmentation/') + args = parser.parse_args() + data_root = args.data_root + + # 1. 划分训练集、验证集 + # 1.1 获取所有图片路径 + img_list = glob.glob(os.path.join(data_root, 'images', '*.tif')) + img_list.sort() + mask_list = glob.glob(os.path.join(data_root, 'masks', '*.TIF')) + mask_list.sort() + assert len(img_list) == len(mask_list) + # 1.2 划分训练集、验证集、测试集 + train_img_list, val_img_list, train_mask_list, val_mask_list = train_test_split( # noqa + img_list, mask_list, test_size=0.2, random_state=42) + # 1.3 保存划分结果 + save_anno(train_img_list, os.path.join(data_root, 'train.txt'), '_ccd.tif') + save_anno(val_img_list, os.path.join(data_root, 'val.txt'), '_ccd.tif') diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/README.md new file mode 100644 index 0000000..b6f1ca6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/README.md @@ -0,0 +1,147 @@ +# Breast Cancer Cell Segmentation + +## Description + +This project support **`Breast Cancer Cell Segmentation`**, and the dataset used in this project can be downloaded from [here](https://tianchi.aliyun.com/dataset/dataDetail?dataId=90152). + +### Dataset Overview + +In this dataset, there are 58 H&E stained histopathology images used in breast cancer cell detection with associated ground truth data available. Routine histology uses the stain combination of hematoxylin and eosin, commonly referred to as H&E. These images are stained since most cells are essentially transparent, with little or no intrinsic pigment. Certain special stains, which bind selectively to particular components, are be used to identify biological structures such as cells. In those images, the challenging problem is cell segmentation for subsequent classification in benign and malignant cells. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| --------------------------------------------------------------------------------------------- | ----------------- | ------------ | -------------- | ------------ | --------------------- | ---------------------- | ------------ | ------------------------------------------------------------------------------------------------------ | +| [Breast Cancer Cell Segmentation](https://tianchi.aliyun.com/dataset/dataDetail?dataId=90152) | thorax | segmentation | histopathology | 2 | 58/-/- | yes/-/- | 2021 | [CC-BY-SA-NC 4.0](http://creativecommons.org/licenses/by-sa/4.0/?spm=5176.12282016.0.0.3f5b5291ypBxb2) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :----------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| normal | 58 | 98.37 | - | - | - | - | +| breast cancer cell | 58 | 1.63 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/histopathology/breast_cancer_cell_seg/breast_cancer_cell_seg_dataset.png) + +## Dataset Citation + +``` +@inproceedings{gelasca2008evaluation, + title={Evaluation and benchmark for biological image segmentation}, + author={Gelasca, Elisa Drelie and Byun, Jiyun and Obara, Boguslaw and Manjunath, BS}, + booktitle={2008 15th IEEE international conference on image processing}, + pages={1816--1819}, + year={2008}, + organization={IEEE} +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `breast_cancer_cell_seg/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://tianchi.aliyun.com/dataset/dataDetail?dataId=90152) and decompression data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── histopathology + │ │ │ │ ├── breast_cancer_cell_seg + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :----------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 46 | 98.36 | 12 | 98.41 | - | - | +| erythrocytes | 46 | 1.64 | 12 | 1.59 | - | - | + +### Training commands + +Train models on a single server with one GPU. + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +Test models on a single server with one GPU. + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/breast-cancer-cell-seg_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/breast-cancer-cell-seg_512x512.py new file mode 100644 index 0000000..ead40e4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/breast-cancer-cell-seg_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'BreastCancerCellSegDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_breast-cancer-cell-seg-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_breast-cancer-cell-seg-512x512.py new file mode 100644 index 0000000..691a0ff --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_breast-cancer-cell-seg-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './breast-cancer-cell-seg_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.breast-cancer-cell-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_breast-cancer-cell-seg-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_breast-cancer-cell-seg-512x512.py new file mode 100644 index 0000000..719b767 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_breast-cancer-cell-seg-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './breast-cancer-cell-seg_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.breast-cancer-cell-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_breast-cancer-cell-seg-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_breast-cancer-cell-seg-512x512.py new file mode 100644 index 0000000..9dfe70f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_breast-cancer-cell-seg-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './breast-cancer-cell-seg_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.breast-cancer-cell-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/datasets/breast-cancer-cell-seg_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/datasets/breast-cancer-cell-seg_dataset.py new file mode 100644 index 0000000..6f27029 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/datasets/breast-cancer-cell-seg_dataset.py @@ -0,0 +1,29 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class BreastCancerCellSegDataset(BaseSegDataset): + """BreastCancerCellSegDataset dataset. + + In segmentation map annotation for BreastCancerCellSegDataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('normal', 'breast cancer cell')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=False, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/tools/prepare_dataset.py new file mode 100644 index 0000000..775f2ee --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/breast_cancer_cell_seg/tools/prepare_dataset.py @@ -0,0 +1,47 @@ +import glob +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.tif' +seg_map_suffix = '.TIF' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +x_train = glob.glob( + os.path.join('data/Breast Cancer Cell Segmentation_datasets/Images/*' + + img_suffix)) + +os.system('mkdir -p ' + root_path + 'images/train/') +os.system('mkdir -p ' + root_path + 'masks/train/') + +D2_255_convert_dict = {0: 0, 255: 1} + + +def convert_2d(img, convert_dict=D2_255_convert_dict): + arr_2d = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8) + for c, i in convert_dict.items(): + arr_2d[img == c] = i + return arr_2d + + +part_dir_dict = {0: 'train/'} +for ith, part in enumerate([x_train]): + part_dir = part_dir_dict[ith] + for img in part: + basename = os.path.basename(img) + img_save_path = root_path + 'images/' + part_dir + basename.split( + '.')[0] + save_img_suffix + Image.open(img).save(img_save_path) + mask_path = root_path + 'Breast Cancer Cell Segmentation_datasets/Masks/' + '_'.join( # noqa + basename.split('_')[:-1]) + seg_map_suffix + label = np.array(Image.open(mask_path)) + + save_mask_path = root_path + 'masks/' + part_dir + basename.split( + '.')[0] + save_seg_map_suffix + assert len(label.shape) == 2 and 255 in label and 1 not in label + mask = convert_2d(label) + mask = Image.fromarray(mask.astype(np.uint8)) + mask.save(save_mask_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/README.md new file mode 100644 index 0000000..1f55b44 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/README.md @@ -0,0 +1,207 @@ +# CoNIC: Colon Nuclei Identification and Counting Challenge + +## Description + +This project supports **`CoNIC: Colon Nuclei Identification and Counting Challenge`**, which can be downloaded from [here](https://drive.google.com/drive/folders/1il9jG7uA4-ebQ_lNmXbbF2eOK9uNwheb). + +### Dataset Overview + +Nuclear segmentation, classification and quantification within Haematoxylin & Eosin stained histology images enables the extraction of interpretable cell-based features that can be used in downstream explainable models in computational pathology (CPath). To help drive forward research and innovation for automatic nuclei recognition in CPath, we organise the Colon Nuclei Identification and Counting (CoNIC) Challenge. The challenge requires researchers to develop algorithms that perform segmentation, classification and counting of 6 different types of nuclei within the current largest known publicly available nuclei-level dataset in CPath, containing around half a million labelled nuclei. + +### Task Information + +The CONIC challenge has 2 tasks: + +- Task 1: Nuclear segmentation and classification. + +The first task requires participants to segment nuclei within the tissue, while also classifying each nucleus into one of the following categories: epithelial, lymphocyte, plasma, eosinophil, neutrophil or connective tissue. + +- Task 2: Prediction of cellular composition. + +For the second task, we ask participants to predict how many nuclei of each class are present in each input image. + +The output of Task 1 can be directly used to perform Task 2, but these can be treated as independent tasks. Therefore, if it is preferred, prediction of cellular composition can be treated as a stand alone regression task. + +***NOTE:We only consider `Task 1` in the following sections.*** + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| -------------------------------------------------------- | ----------------- | ------------ | -------------- | ------------ | --------------------- | ---------------------- | ------------ | ------------------------------------------------------------------------------------------------------------ | +| [CoNIC202](https://conic-challenge.grand-challenge.org/) | abdomen | segmentation | histopathology | 7 | 4981/-/- | yes/-/- | 2022 | [Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 4981 | 83.97 | - | - | - | - | +| neutrophil | 1218 | 0.13 | - | - | - | - | +| epithelial | 4256 | 10.31 | - | - | - | - | +| lymphocyte | 4473 | 1.85 | - | - | - | - | +| plasma | 3316 | 0.55 | - | - | - | - | +| eosinophil | 1456 | 0.1 | - | - | - | - | +| connective | 4613 | 3.08 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/histopathology/conic2022_seg/conic2022_seg_dataset.png) + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `conic2022_seg/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://drive.google.com/drive/folders/1il9jG7uA4-ebQ_lNmXbbF2eOK9uNwheb/) and move data to path `'data/CoNIC_Challenge'`. The directory should be like: + ```shell + data/CoNIC_Challenge + ├── README.txt + ├── by-nc-sa.md + ├── counts.csv + ├── images.npy + ├── labels.npy + └── patch_info.csv + ``` +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── histopathology + │ │ │ │ ├── conic2022_seg + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 3984 | 84.06 | 997 | 83.65 | - | - | +| neutrophil | 956 | 0.12 | 262 | 0.13 | - | - | +| epithelial | 3400 | 10.26 | 856 | 10.52 | - | - | +| lymphocyte | 3567 | 1.83 | 906 | 1.96 | - | - | +| plasma | 2645 | 0.55 | 671 | 0.56 | - | - | +| eosinophil | 1154 | 0.1 | 302 | 0.1 | - | - | +| connective | 3680 | 3.08 | 933 | 3.08 | - | - | + +### Training commands + +Train models on a single server with one GPU. + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +Test models on a single server with one GPU. + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Organizers + +- Simon Graham (TIA, PathLAKE) +- Mostafa Jahanifar (TIA, PathLAKE) +- Dang Vu (TIA) +- Giorgos Hadjigeorghiou (TIA, PathLAKE) +- Thomas Leech (TIA, PathLAKE) +- David Snead (UHCW, PathLAKE) +- Shan Raza (TIA, PathLAKE) +- Fayyaz Minhas (TIA, PathLAKE) +- Nasir Rajpoot (TIA, PathLAKE) + +TIA: Tissue Image Analytics Centre, Department of Computer Science, University of Warwick, United Kingdom + +UHCW: Department of Pathology, University Hospitals Coventry and Warwickshire, United Kingdom + +PathLAKE: Pathology Image Data Lake for Analytics Knowledge & Education, , University Hospitals Coventry and Warwickshire, United Kingdom + +## Dataset Citation + +If this work is helpful for your research, please consider citing the below paper. + +``` +@inproceedings{graham2021lizard, + title={Lizard: A large-scale dataset for colonic nuclear instance segmentation and classification}, + author={Graham, Simon and Jahanifar, Mostafa and Azam, Ayesha and Nimir, Mohammed and Tsang, Yee-Wah and Dodd, Katherine and Hero, Emily and Sahota, Harvir and Tank, Atisha and Benes, Ksenija and others}, + booktitle={Proceedings of the IEEE/CVF International Conference on Computer Vision}, + pages={684--693}, + year={2021} +} +@article{graham2021conic, + title={Conic: Colon nuclei identification and counting challenge 2022}, + author={Graham, Simon and Jahanifar, Mostafa and Vu, Quoc Dang and Hadjigeorghiou, Giorgos and Leech, Thomas and Snead, David and Raza, Shan E Ahmed and Minhas, Fayyaz and Rajpoot, Nasir}, + journal={arXiv preprint arXiv:2111.14485}, + year={2021} +} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/conic2022-seg_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/conic2022-seg_512x512.py new file mode 100644 index 0000000..51b4e57 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/conic2022-seg_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'Conic2022SegDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_conic2022-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_conic2022-512x512.py new file mode 100644 index 0000000..3e0248c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_conic2022-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './conic2022-seg_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.conic2022-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=7), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_conic2022-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_conic2022-512x512.py new file mode 100644 index 0000000..fd0e9d8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_conic2022-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './conic2022-seg_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.conic2022-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=7), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_conic2022-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_conic2022-512x512.py new file mode 100644 index 0000000..bb667f1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_conic2022-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './conic2022-seg_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.conic2022-seg_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=7), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/conic2022_seg_dataset.png b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/conic2022_seg_dataset.png new file mode 100644 index 0000000000000000000000000000000000000000..65bb0bbe0a52e3749766a52f4b9e183bfbb4a13f GIT binary patch literal 668126 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
sjIBzpaG%$ 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

j|BbSV2p2 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%0A9(leD=LhbBI$K#Lg2~2KrvE02m$Lv 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 $6&tOe6{#8L}MUJM1`fZ}4>O?r2 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

(+3T2 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

`yS$W(=+znNEOWI>1gfu9rkwh`4<8zU0kqnnDK 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�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 P0NO1*W8H!qPhR~8(W)gJ literal 0 HcmV?d00001 diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/datasets/conic2022-seg_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/datasets/conic2022-seg_dataset.py new file mode 100644 index 0000000..9af0958 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/datasets/conic2022-seg_dataset.py @@ -0,0 +1,29 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class Conic2022SegDataset(BaseSegDataset): + """Conic2022SegDataset dataset. + + In segmentation map annotation for Conic2022SegDataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + """ + METAINFO = dict( + classes=('background', 'neutrophil', 'epithelial', 'lymphocyte', + 'plasma', 'eosinophil', 'connective')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=False, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/tools/prepare_dataset.py new file mode 100644 index 0000000..89cfb4a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/conic2022_seg/tools/prepare_dataset.py @@ -0,0 +1,65 @@ +import glob +import os +import shutil + +import numpy as np +from PIL import Image + +img_save_root = 'data/' +root_path = 'data/' +img_suffix = '.png' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +label_set = set() + + +def save_masks_from_npz(data, save_root, part='masks/'): + global label_set + num = data.shape[0] + for i in range(num): + # np_img = data[i, :, :, :] + np_mask = data[i, :, :, 1] + label_set = set.union(label_set, set(np.unique(np_mask))) + img = Image.fromarray(np_mask) + save_path = os.path.join(save_root, part, str(i) + save_seg_map_suffix) + img.save(save_path) + + +def save_images_from_npz(data, save_root, part='images/'): + num = data.shape[0] + for i in range(num): + np_img = data[i, :, :, :] + img = Image.fromarray(np_img) + save_path = os.path.join(save_root, part, str(i) + save_img_suffix) + img.save(save_path) + + +images_npy = np.load('data/CoNIC_Challenge/images.npy') +labels_npy = np.load('data/CoNIC_Challenge/labels.npy') + +os.system('mkdir -p ' + img_save_root + 'images_ori') +os.system('mkdir -p ' + img_save_root + 'labels') +save_images_from_npz(images_npy, img_save_root, 'images_ori') +save_masks_from_npz(labels_npy, img_save_root, 'labels') +print(label_set) + +x_train = glob.glob(os.path.join('data/images_ori/*' + img_suffix)) + +os.system('mkdir -p ' + root_path + 'images/train/') +os.system('mkdir -p ' + root_path + 'masks/train/') + +part_dir_dict = {0: 'train/', 1: 'val/'} +for ith, part in enumerate([x_train]): + part_dir = part_dir_dict[ith] + for img in part: + basename = os.path.basename(img) + shutil.copy( + img, root_path + 'images/' + part_dir + basename.split('.')[0] + + save_img_suffix) + mask_path = root_path + 'labels/' + basename.split( + '.')[0] + seg_map_suffix + save_mask_path = root_path + 'masks/' + part_dir + basename.split( + '.')[0] + save_seg_map_suffix + shutil.copy(mask_path, save_mask_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/README.md new file mode 100644 index 0000000..ca3d7aa --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/README.md @@ -0,0 +1,147 @@ +# Colorectal Nuclear Segmentation and Phenotypes (CoNSeP) Dataset + +## Description + +This project supports **`Colorectal Nuclear Segmentation and Phenotypes (CoNSeP) Dataset`**, which can be downloaded from [here](https://warwick.ac.uk/fac/cross_fac/tia/data/hovernet/). + +### Dataset Overview + +The CoNSeP (Colon Segmentation and Phenotyping) dataset consists of 41 H&E stained image tiles, each with a size of 1,000×1,000 pixels and a magnification of 40x. These images were extracted from 16 colorectal adenocarcinoma (CRA) whole slide images (WSI), each of which belonged to a separate patient and was scanned using an Omnyx VL120 scanner at the Pathology Department of the University Hospitals Coventry and Warwickshire NHS Trust, UK. This dataset was first used in paper named, "HoVer-Net: Simultaneous Segmentation and Classification of Nuclei in Multi-Tissue Histology Images". + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| -------------------------------------------------------- | ----------------- | ------------ | -------------- | ------------ | --------------------- | ---------------------- | ------------ | ------- | +| [CoNIC202](https://conic-challenge.grand-challenge.org/) | abdomen | segmentation | histopathology | 7 | 4981/-/- | yes/-/- | 2022 | - | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :-----------------------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 27 | 83.61 | 14 | 80.4 | - | - | +| other | 17 | 0.17 | 9 | 0.52 | - | - | +| inflammatory | 25 | 2.66 | 14 | 2.14 | - | - | +| healthy epithelial | 3 | 1.47 | 2 | 1.58 | - | - | +| dysplastic/malignant epithelial | 10 | 7.17 | 8 | 9.16 | - | - | +| fibroblast | 23 | 3.84 | 14 | 4.63 | - | - | +| muscle | 8 | 1.05 | 3 | 1.42 | - | - | +| endothelial | 7 | 0.02 | 4 | 0.15 | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/histopathology/consep/consep_dataset.png) + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `conic2022_seg/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://opendatalab.com/CoNSeP) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── histopathology + │ │ │ │ ├── consep + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Training commands + +Train models on a single server with one GPU. + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +Test models on a single server with one GPU. + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Dataset Citation + +If this work is helpful for your research, please consider citing the below paper. + +``` +@article{graham2019hover, + title={Hover-net: Simultaneous segmentation and classification of nuclei in multi-tissue histology images}, + author={Graham, Simon and Vu, Quoc Dang and Raza, Shan E Ahmed and Azam, Ayesha and Tsang, Yee Wah and Kwak, Jin Tae and Rajpoot, Nasir}, + journal={Medical Image Analysis}, + volume={58}, + pages={101563}, + year={2019}, + publisher={Elsevier} +} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] Test-time correctness + + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/consep_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/consep_512x512.py new file mode 100644 index 0000000..0d9b894 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/consep_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'ConsepDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_consep-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_consep-512x512.py new file mode 100644 index 0000000..cbcf5db --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_consep-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './consep_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.consep_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=8), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_consep-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_consep-512x512.py new file mode 100644 index 0000000..b374566 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_consep-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './consep_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.consep_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=8), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_consep-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_consep-512x512.py new file mode 100644 index 0000000..35bdaa3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_consep-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + './consep_512x512.py', 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.consep_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=8), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/datasets/consep_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/datasets/consep_dataset.py new file mode 100644 index 0000000..ceb2b3a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/datasets/consep_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class ConsepDataset(BaseSegDataset): + """ConsepDataset dataset. + + In segmentation map annotation for ConsepDataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + """ + METAINFO = dict( + classes=('background', 'other', 'inflammatory', 'healthy epithelial', + 'dysplastic/malignant epithelial', 'fibroblast', 'muscle', + 'endothelial')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=False, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/tools/prepare_dataset.py new file mode 100644 index 0000000..83a2e18 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/consep/tools/prepare_dataset.py @@ -0,0 +1,54 @@ +import glob +import os +import shutil + +import numpy as np +from PIL import Image +from scipy.io import loadmat + +root_path = 'data/' +img_suffix = '.png' +seg_map_suffix = '.mat' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +x_train = glob.glob(os.path.join('data/CoNSeP/Train/Images/*' + img_suffix)) +x_test = glob.glob(os.path.join('data/CoNSeP/Test/Images/*' + img_suffix)) + +os.system('mkdir -p ' + root_path + 'images/train/') +os.system('mkdir -p ' + root_path + 'images/val/') +os.system('mkdir -p ' + root_path + 'masks/train/') +os.system('mkdir -p ' + root_path + 'masks/val/') +D2_255_convert_dict = {0: 0, 255: 1} + + +def convert_2d(img, convert_dict=D2_255_convert_dict): + arr_2d = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8) + for c, i in convert_dict.items(): + arr_2d[img == c] = i + return arr_2d + + +part_dir_dict = {0: 'CoNSeP/Train/', 1: 'CoNSeP/Test/'} +save_dir_dict = {0: 'train/', 1: 'val/'} +for ith, part in enumerate([x_train, x_test]): + part_dir = part_dir_dict[ith] + for img in part: + basename = os.path.basename(img) + shutil.copy( + img, root_path + 'images/' + save_dir_dict[ith] + + basename.split('.')[0] + save_img_suffix) + + mask_path = root_path + part_dir + 'Labels/' + basename.split( + '.')[0] + seg_map_suffix + label_ = loadmat(mask_path) + label = label_['inst_map'] + label_type = label_['inst_type'] + label_dict = {i + 1: int(val) for i, val in enumerate(label_type)} + + save_mask_path = root_path + 'masks/' + save_dir_dict[ + ith] + basename.split('.')[0] + save_seg_map_suffix + + res = convert_2d(label, convert_dict=label_dict) + res = Image.fromarray(res.astype(np.uint8)) + res.save(save_mask_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/README.md new file mode 100644 index 0000000..8130d59 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/README.md @@ -0,0 +1,136 @@ +# Foot Ulcer Segmentation Challenge 2021 (FUSC 2021) + +## Description + +This project supports **`Foot Ulcer Segmentation Challenge 2021 (FUSC 2021) `**, which can be downloaded from [here](https://fusc.grand-challenge.org/). + +### Dataset Overview + +This chronic wound dataset was collected over 2 years from October 2019 to April 2021 at the center and contains 1,210 foot ulcer images taken from 889 patients during multiple clinical visits. The raw images were taken by Canon SX 620 HS digital camera and iPad Pro under uncontrolled illumination conditions, +with various backgrounds. The images (shown in Figure 1) are randomly split into 3 subsets: a training set with 810 images, a validation set with 200 images, and a testing set with 200 images. Of course, the annotations of the testing set are kept private. The data collected were de-identified and in accordance with relevant guidelines and regulations and the patient’s informed consent is waived by the institutional review board of the University of Wisconsin-Milwaukee. + +### Information Statistics + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| --------------------------------------------- | ----------------- | ------------ | -------------- | ------------ | --------------------- | ---------------------- | ------------ | ------------------------------------------------------------- | +| [fusc2021](https://fusc.grand-challenge.org/) | lower limb | segmentation | histopathology | 2 | 810/200/200 | yes/yes/no | 2021 | [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 810 | 98.71 | 200 | 98.78 | - | - | +| wound | 791 | 1.29 | 195 | 1.22 | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![fusc2021](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/histopathology/fusc2021/fusc2021_dataset.png?raw=true) + +### Dataset Citation + +``` +@article{s41598-020-78799-w, + title={Fully automatic wound segmentation with deep convolutional neural networks}, + author={Chuanbo Wang and D. M. Anisuzzaman and Victor Williamson and Mrinal Kanti Dhar and Behrouz Rostami and Jeffrey Niezgoda and Sandeep Gopalakrishnan and Zeyun Yu}, + journal={Scientific Reports}, + volume={10}, + number={1}, + pages={21897}, + year={2020} +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `fusc2021/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://fusc.grand-challenge.org/) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── histopathology + │ │ │ │ ├── fusc2021 + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_fusc2021-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_fusc2021-512x512.py new file mode 100644 index 0000000..c3f4275 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_fusc2021-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './fusc2021_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.fusc2021_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_fusc2021-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_fusc2021-512x512.py new file mode 100644 index 0000000..ed87030 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_fusc2021-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './fusc2021_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.fusc2021_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_fusc2021-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_fusc2021-512x512.py new file mode 100644 index 0000000..cbc09ae --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_fusc2021-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './fusc2021_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.fusc2021_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_fusc2021-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_fusc2021-512x512.py new file mode 100644 index 0000000..f1477ee --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_fusc2021-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './fusc2021_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.fusc2021_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fusc2021_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fusc2021_512x512.py new file mode 100644 index 0000000..e650474 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/configs/fusc2021_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'FUSC2021Dataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/datasets/fusc2021_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/datasets/fusc2021_dataset.py new file mode 100644 index 0000000..d331ac8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/datasets/fusc2021_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class FUSC2021Dataset(BaseSegDataset): + """FUSC2021Dataset dataset. + + In segmentation map annotation for FUSC2021Dataset, 0 stands for background + , which is included in 2 categories. ``reduce_zero_label`` + is fixed to False. The ``img_suffix`` is fixed to '.png' and + ``seg_map_suffix`` is fixed to '.png'. + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False.. + """ + METAINFO = dict(classes=('background', 'wound')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/tools/prepare_dataset.py new file mode 100644 index 0000000..8f2de3d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/fusc2021/tools/prepare_dataset.py @@ -0,0 +1,114 @@ +import glob +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +img_suffix = '.png' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' +src_img_train_dir = os.path.join( + root_path, 'wound-segmentation/data/' + + 'Foot Ulcer Segmentation Challenge/train/images') +src_img_val_dir = os.path.join( + root_path, 'wound-segmentation/data/' + + 'Foot Ulcer Segmentation Challenge/validation/images') +src_img_test_dir = os.path.join( + root_path, 'wound-segmentation/data/' + + 'Foot Ulcer Segmentation Challenge/test/images') +src_mask_train_dir = os.path.join( + root_path, 'wound-segmentation/data/' + + 'Foot Ulcer Segmentation Challenge/train/labels') +src_mask_val_dir = os.path.join( + root_path, 'wound-segmentation/data/' + + 'Foot Ulcer Segmentation Challenge/validation/labels') + +tgt_img_train_dir = os.path.join(root_path, 'images/train/') +tgt_mask_train_dir = os.path.join(root_path, 'masks/train/') +tgt_img_val_dir = os.path.join(root_path, 'images/val/') +tgt_mask_val_dir = os.path.join(root_path, 'masks/val/') +tgt_img_test_dir = os.path.join(root_path, 'images/test/') +os.system('mkdir -p ' + tgt_img_train_dir) +os.system('mkdir -p ' + tgt_img_val_dir) +os.system('mkdir -p ' + tgt_img_test_dir) +os.system('mkdir -p ' + tgt_mask_train_dir) +os.system('mkdir -p ' + tgt_mask_val_dir) + + +def filter_suffix_recursive(src_dir, suffix): + # filter out file names and paths in source directory + suffix = '.' + suffix if '.' not in suffix else suffix + file_paths = glob.glob( + os.path.join(src_dir, '**', '*' + suffix), recursive=True) + file_names = [_.split('/')[-1] for _ in file_paths] + return sorted(file_paths), sorted(file_names) + + +def convert_label(img, convert_dict): + arr = np.zeros_like(img, dtype=np.uint8) + for c, i in convert_dict.items(): + arr[img == c] = i + return arr + + +def convert_pics_into_pngs(src_dir, tgt_dir, suffix, convert='RGB'): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_img_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + num = len(src_paths) + img = np.array(Image.open(src_path)) + if len(img.shape) == 2: + pil = Image.fromarray(img).convert(convert) + elif len(img.shape) == 3: + pil = Image.fromarray(img) + else: + raise ValueError('Input image not 2D/3D: ', img.shape) + + pil.save(tgt_path) + print(f'processed {i+1}/{num}.') + + +def convert_label_pics_into_pngs(src_dir, + tgt_dir, + suffix, + convert_dict={ + 0: 0, + 255: 1 + }): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + num = len(src_paths) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, save_seg_map_suffix) + tgt_path = os.path.join(tgt_dir, tgt_name) + + img = np.array(Image.open(src_path).convert('L')) + img = convert_label(img, convert_dict) + Image.fromarray(img).save(tgt_path) + print(f'processed {i+1}/{num}.') + + +if __name__ == '__main__': + + convert_pics_into_pngs( + src_img_train_dir, tgt_img_train_dir, suffix=img_suffix) + + convert_pics_into_pngs(src_img_val_dir, tgt_img_val_dir, suffix=img_suffix) + + convert_pics_into_pngs( + src_img_test_dir, tgt_img_test_dir, suffix=img_suffix) + + convert_label_pics_into_pngs( + src_mask_train_dir, tgt_mask_train_dir, suffix=seg_map_suffix) + + convert_label_pics_into_pngs( + src_mask_val_dir, tgt_mask_val_dir, suffix=seg_map_suffix) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/README.md new file mode 100644 index 0000000..e0cade7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/README.md @@ -0,0 +1,146 @@ +# Pan-Cancer Histology Dataset for Nuclei Instance Segmentation and Classification (PanNuke) + +## Description + +This project supports **`Pan-Cancer Histology Dataset for Nuclei Instance Segmentation and Classification (PanNuke)`**, which can be downloaded from [here](https://academictorrents.com/details/99f2c7b57b95500711e33f2ee4d14c9fd7c7366c). + +### Dataset Overview + +Semi automatically generated nuclei instance segmentation and classification dataset with exhaustive nuclei labels across 19 different tissue types. The dataset consists of 481 visual fields, of which 312 are randomly sampled from more than 20K whole slide images at different magnifications, from multiple data sources. In total the dataset contains 205,343 labeled nuclei, each with an instance segmentation mask. Models trained on pannuke can aid in whole slide image tissue type segmentation, and generalise to new tissues. PanNuke demonstrates one of the first successfully semi-automatically generated datasets. + +### Statistic Information + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ---------------------------------------------------------------------------------------- | ----------------- | ------------ | -------------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------- | +| [Pannuke](https://academictorrents.com/details/99f2c7b57b95500711e33f2ee4d14c9fd7c7366c) | full_body | segmentation | histopathology | 6 | 7901/-/- | yes/-/- | 2019 | [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :-----------------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 7901 | 83.32 | - | - | - | - | +| neoplastic | 4190 | 8.64 | - | - | - | - | +| non-neoplastic epithelial | 4126 | 1.77 | - | - | - | - | +| inflammatory | 6137 | 3.73 | - | - | - | - | +| connective | 232 | 0.07 | - | - | - | - | +| dead | 1528 | 2.47 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![pannuke](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/histopathology/pannuke/pannuke_dataset.png?raw=true) + +### Dataset Citation + +``` +@inproceedings{gamper2019pannuke, + title={PanNuke: an open pan-cancer histology dataset for nuclei instance segmentation and classification}, + author={Gamper, Jevgenij and Koohbanani, Navid Alemi and Benet, Ksenija and Khuram, Ali and Rajpoot, Nasir}, + booktitle={European Congress on Digital Pathology}, + pages={11--19}, + year={2019}, +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 9.3.0 +- scikit-learn(sklearn) v1.2.0 1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `pannuke/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://academictorrents.com/details/99f2c7b57b95500711e33f2ee4d14c9fd7c7366c) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── histopathology + │ │ │ │ ├── pannuke + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :-----------------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 6320 | 83.38 | 1581 | 83.1 | - | - | +| neoplastic | 3339 | 8.55 | 851 | 9.0 | - | - | +| non-neoplastic epithelial | 3293 | 1.77 | 833 | 1.76 | - | - | +| inflammatory | 4914 | 3.72 | 1223 | 3.76 | - | - | +| connective | 170 | 0.06 | 62 | 0.09 | - | - | +| dead | 1235 | 2.51 | 293 | 2.29 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_bactteria-detection-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_bactteria-detection-512x512.py new file mode 100644 index 0000000..92584e9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_bactteria-detection-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './bactteria-detection_512x512.py', 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.bactteria-detection_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_pannuke-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_pannuke-512x512.py new file mode 100644 index 0000000..042a08c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_pannuke-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './pannuke_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.pannuke_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=6), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_pannuke-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_pannuke-512x512.py new file mode 100644 index 0000000..e92514c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_pannuke-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './pannuke_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.pannuke_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=6), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_pannuke-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_pannuke-512x512.py new file mode 100644 index 0000000..a9403c8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_pannuke-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './pannuke_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.pannuke_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=6), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/pannuke_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/pannuke_512x512.py new file mode 100644 index 0000000..316ac1a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/configs/pannuke_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'PanNukeDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/datasets/pannuke_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/datasets/pannuke_dataset.py new file mode 100644 index 0000000..4d3c687 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/datasets/pannuke_dataset.py @@ -0,0 +1,33 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class PanNukeDataset(BaseSegDataset): + """PanNukeDataset dataset. + + In segmentation map annotation for PanNukeDataset, + 0 stands for background, which is included in 6 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict( + classes=('background', 'neoplastic', 'non-neoplastic epithelial', + 'inflammatory', 'connective', 'dead')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/tools/prepare_dataset.py new file mode 100644 index 0000000..7213b18 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pannuke/tools/prepare_dataset.py @@ -0,0 +1,49 @@ +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' + +tgt_img_dir = os.path.join(root_path, 'images/train') +tgt_mask_dir = os.path.join(root_path, 'masks/train') +os.system('mkdir -p ' + tgt_img_dir) +os.system('mkdir -p ' + tgt_mask_dir) + +fold_img_paths = sorted([ + os.path.join(root_path, 'pannuke/Fold 1/images/fold1/images.npy'), + os.path.join(root_path, 'pannuke/Fold 2/images/fold2/images.npy'), + os.path.join(root_path, 'pannuke/Fold 3/images/fold3/images.npy') +]) + +fold_mask_paths = sorted([ + os.path.join(root_path, 'pannuke/Fold 1/masks/fold1/masks.npy'), + os.path.join(root_path, 'pannuke/Fold 2/masks/fold2/masks.npy'), + os.path.join(root_path, 'pannuke/Fold 3/masks/fold3/masks.npy') +]) + +for n, (img_path, + mask_path) in enumerate(zip(fold_img_paths, fold_mask_paths)): + fold_name = str(n + 1) + imgs = np.load(img_path) + masks = np.load(mask_path) + + for i in range(imgs.shape[0]): + img = np.uint8(imgs[i]) + mask_multichannel = np.minimum(np.uint8(masks[i]), 1) + mask = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8) + for j in range(mask_multichannel.shape[-1]): + factor = (j + 1) % mask_multichannel.shape[-1] + # convert [0,1,2,3,4,5] to [1,2,3,4,5,0], + # with the last label being background + mask[mask_multichannel[..., j] == 1] = factor + + file_name = 'fold' + fold_name + '_' + str(i).rjust(4, '0') + '.png' + print('Processing: ', file_name) + tgt_img_path = os.path.join(tgt_img_dir, file_name) + tgt_mask_path = os.path.join(tgt_mask_dir, file_name) + Image.fromarray(img).save(tgt_img_path) + Image.fromarray(mask).save(tgt_mask_path) + + del imgs + del masks diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/README.md new file mode 100644 index 0000000..5a80949 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/README.md @@ -0,0 +1,153 @@ +# PCam (PatchCamelyon) + +## Description + +This project supports **`Patch Camelyon (PCam) `**, which can be downloaded from [here](https://opendatalab.com/PCam). + +### Dataset Overview + +PatchCamelyon is an image classification dataset. It consists of 327680 color images (96 x 96px) extracted from histopathologic scans of lymph node sections. Each image is annotated with a binary label indicating presence of metastatic tissue. PCam provides a new benchmark for machine learning models: bigger than CIFAR10, smaller than ImageNet, trainable on a single GPU. + +### Statistic Information + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------ | ----------------- | ------------ | -------------- | ------------ | --------------------- | ---------------------- | ------------ | ------------------------------------------------------------- | +| [Pcam](https://opendatalab.com/PCam) | throax | segmentation | histopathology | 2 | 327680/-/- | yes/-/- | 2018 | [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :---------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 214849 | 63.77 | - | - | - | - | +| metastatic tissue | 131832 | 36.22 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![pcam](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/histopathology/pcam/pcam_dataset.png?raw=true) + +### Dataset Citation + +``` +@inproceedings{veeling2018rotation, + title={Rotation equivariant CNNs for digital pathology}, + author={Veeling, Bastiaan S and Linmans, Jasper and Winkens, Jim and Cohen, Taco and Welling, Max}, + booktitle={International Conference on Medical image computing and computer-assisted intervention}, + pages={210--218}, + year={2018}, +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 9.3.0 +- scikit-learn(sklearn) v1.2.0 1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `pcam/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://opendatalab.com/PCam) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```shell +mkdir data & cd data +pip install opendatalab +odl get PCam +mv ./PCam/raw/pcamv1 ./ +rm -rf PCam +cd .. +python tools/prepare_dataset.py +python ../../tools/split_seg_dataset.py +``` + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── histopathology + │ │ │ │ ├── pcam + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :---------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 171948 | 63.82 | 42901 | 63.6 | - | - | +| metastatic tissue | 105371 | 36.18 | 26461 | 36.4 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_pcam-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_pcam-512x512.py new file mode 100644 index 0000000..20601f1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_pcam-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './pcam_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.pcam_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_pcam-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_pcam-512x512.py new file mode 100644 index 0000000..c057535 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_pcam-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './pcam_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.pcam_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_pcam-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_pcam-512x512.py new file mode 100644 index 0000000..4c1d5fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_pcam-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './pcam_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.pcam_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_pcam-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_pcam-512x512.py new file mode 100644 index 0000000..25e3734 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_pcam-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './pcam_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.pcam_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/pcam_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/pcam_512x512.py new file mode 100644 index 0000000..04efc23 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/configs/pcam_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'PCamDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/datasets/pcam_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/datasets/pcam_dataset.py new file mode 100644 index 0000000..1c27de5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/datasets/pcam_dataset.py @@ -0,0 +1,31 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class PCamDataset(BaseSegDataset): + """PCamDataset dataset. + + In segmentation map annotation for PCamDataset, + 0 stands for background, which is included in 2 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('background', 'metastatic tissue')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/tools/prepare_dataset.py new file mode 100644 index 0000000..75038e6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/histopathology/pcam/tools/prepare_dataset.py @@ -0,0 +1,49 @@ +import os + +import h5py +import numpy as np +from PIL import Image + +root_path = 'data/' + +tgt_img_train_dir = os.path.join(root_path, 'images/train/') +tgt_mask_train_dir = os.path.join(root_path, 'masks/train/') +tgt_img_val_dir = os.path.join(root_path, 'images/val/') +tgt_img_test_dir = os.path.join(root_path, 'images/test/') + +os.system('mkdir -p ' + tgt_img_train_dir) +os.system('mkdir -p ' + tgt_mask_train_dir) +os.system('mkdir -p ' + tgt_img_val_dir) +os.system('mkdir -p ' + tgt_img_test_dir) + + +def extract_pics_from_h5(h5_path, h5_key, save_dir): + f = h5py.File(h5_path, 'r') + for i, img in enumerate(f[h5_key]): + img = img.astype(np.uint8).squeeze() + img = Image.fromarray(img) + save_image_path = os.path.join(save_dir, str(i).zfill(8) + '.png') + img.save(save_image_path) + + +if __name__ == '__main__': + + extract_pics_from_h5( + 'data/pcamv1/camelyonpatch_level_2_split_train_x.h5', + h5_key='x', + save_dir=tgt_img_train_dir) + + extract_pics_from_h5( + 'data/pcamv1/camelyonpatch_level_2_split_valid_x.h5', + h5_key='x', + save_dir=tgt_img_val_dir) + + extract_pics_from_h5( + 'data/pcamv1/camelyonpatch_level_2_split_test_x.h5', + h5_key='x', + save_dir=tgt_img_test_dir) + + extract_pics_from_h5( + 'data/pcamv1/camelyonpatch_level_2_split_train_mask.h5', + h5_key='mask', + save_dir=tgt_mask_train_dir) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/README.md new file mode 100644 index 0000000..ca95921 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/README.md @@ -0,0 +1,167 @@ +# RAVIR: A Dataset and Methodology for the Semantic Segmentation and Quantitative Analysis of Retinal Arteries and Veins in Infrared Reflectance Imaging + +## Description + +This project support **`RAVIR: A Dataset and Methodology for the Semantic Segmentation and Quantitative Analysis of Retinal Arteries and Veins in Infrared Reflectance Imaging`**, and the dataset used in this project can be downloaded from [here](https://ravir.grand-challenge.org/). + +### Dataset Overview + +The retinal vasculature provides important clues in the diagnosis and monitoring of systemic diseases including hypertension and diabetes. The microvascular system is of primary involvement in such conditions, and the retina is the only anatomical site where the microvasculature can be directly observed. The objective assessment of retinal vessels has long been considered a surrogate biomarker for systemic vascular diseases, and with recent advancements in retinal imaging and computer vision technologies, this topic has become the subject of renewed attention. In this paper, we present a novel dataset, dubbed RAVIR, for the semantic segmentation of Retinal Arteries and Veins in Infrared Reflectance (IR) imaging. It enables the creation of deep learning-based models that distinguish extracted vessel type without extensive post-processing. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------------- | ----------------- | ------------ | ---------------------------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------- | +| [Ravir](https://ravir.grand-challenge.org/) | eye | segmentation | infrared reflectance imaging | 3 | 23/-/19 | yes/-/- | 2022 | [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 23 | 87.22 | - | - | - | - | +| artery | 23 | 5.45 | - | - | - | - | +| vein | 23 | 7.33 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/infrared_reflectance_imaging/ravir/ravir_dataset.png) + +## Dataset Citation + +```bibtex +@article{hatamizadeh2022ravir, + title={RAVIR: A dataset and methodology for the semantic segmentation and quantitative analysis of retinal arteries and veins in infrared reflectance imaging}, + author={Hatamizadeh, Ali and Hosseini, Hamid and Patel, Niraj and Choi, Jinseo and Pole, Cameron C and Hoeferlin, Cory M and Schwartz, Steven D and Terzopoulos, Demetri}, + journal={IEEE Journal of Biomedical and Health Informatics}, + volume={26}, + number={7}, + pages={3272--3283}, + year={2022}, + publisher={IEEE} +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `ravir/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://ravir.grand-challenge.org/) and decompression data to path `'data/ravir/'`. +- run script `"python tools/prepare_dataset.py"` to split dataset and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py --data_root data/ravir"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── infrared_reflectance_imaging + │ │ │ │ ├── ravir + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ │ ├── test + │ │ │ │ | │ │ │ ├── yyy.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── yyy.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 18 | 87.41 | 5 | 86.53 | - | - | +| artery | 18 | 5.44 | 5 | 5.50 | - | - | +| vein | 18 | 7.15 | 5 | 7.97 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_PATH} +``` + +### Testing commands + +To train models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_PATH} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Results + +### Ravir + +| Method | Backbone | Crop Size | lr | config | +| :-------------: | :------: | :-------: | :----: | :------------------------------------------------------------------------: | +| fcn_unet_s5-d16 | unet | 512x512 | 0.01 | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_ravir-512x512.py) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.001 | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_ravir-512x512.py) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.0001 | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_ravir-512x512.py) | + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] Test-time correctness + + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_ravir-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_ravir-512x512.py new file mode 100644 index 0000000..375ad5a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_ravir-512x512.py @@ -0,0 +1,19 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './ravir_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.ravir_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_ravir-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_ravir-512x512.py new file mode 100644 index 0000000..a7ecf6d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_ravir-512x512.py @@ -0,0 +1,19 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './ravir_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.ravir_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + type='EncoderDecoder', + data_preprocessor=dict(size=img_scale), + pretrained=None, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_ravir-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_ravir-512x512.py new file mode 100644 index 0000000..28556df --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_ravir-512x512.py @@ -0,0 +1,19 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './ravir_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.ravir_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + pretrained=None, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/ravir_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/ravir_512x512.py new file mode 100644 index 0000000..cb4c292 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/configs/ravir_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'RAVIRDataset' +data_root = 'data/ravir' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/datasets/__init__.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/datasets/__init__.py new file mode 100644 index 0000000..6f1d051 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/datasets/__init__.py @@ -0,0 +1,3 @@ +from .ravir_dataset import RAVIRDataset + +__all__ = ['RAVIRDataset'] diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/datasets/ravir_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/datasets/ravir_dataset.py new file mode 100644 index 0000000..c9e0a8e --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/datasets/ravir_dataset.py @@ -0,0 +1,28 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class RAVIRDataset(BaseSegDataset): + """RAVIRDataset dataset. + + In segmentation map annotation for RAVIRDataset, 0 stands for background, + which is included in 3 categories. ``reduce_zero_label`` is fixed to + False. The ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is + fixed to '.png'. + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + """ + METAINFO = dict(classes=('background', 'artery', 'vein')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/tools/prepare_dataset.py new file mode 100644 index 0000000..068dcad --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/infrared_reflectance_imaging/ravir/tools/prepare_dataset.py @@ -0,0 +1,33 @@ +import glob +import os + +import numpy as np +from PIL import Image +from tqdm import tqdm + +# map = {255:2, 128:1, 0:0} + +os.makedirs('data/ravir/images/train', exist_ok=True) +os.makedirs('data/ravir/images/test', exist_ok=True) +os.makedirs('data/ravir/masks/train', exist_ok=True) + +os.system( + r'cp data/ravir/RAVIR\ Dataset/train/training_images/* data/ravir/images/train' # noqa +) +os.system( + r'cp data/ravir/RAVIR\ Dataset/train/training_masks/* data/ravir/masks/train' # noqa +) +os.system(r'cp data/ravir/RAVIR\ Dataset/test/* data/ravir/images/test') + +os.system(r'rm -rf data/ravir/RAVIR\ Dataset') + +imgs = glob.glob(os.path.join('data/ravir/masks/train', '*.png')) + +for im_path in tqdm(imgs): + im = Image.open(im_path) + imn = np.array(im) + imn[imn == 255] = 2 + imn[imn == 128] = 1 + imn[imn == 0] = 0 + new_im = Image.fromarray(imn) + new_im.save(im_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/README.md new file mode 100644 index 0000000..1feb433 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/README.md @@ -0,0 +1,153 @@ +# 2-PM Vessel Dataset + +## Description + +This project supports **`2-PM Vessel Dataset`**, which can be downloaded from [here](https://opendatalab.org.cn/2-PM_Vessel_Dataset). + +### Dataset Overview + +An open-source volumetric brain vasculature dataset obtained with two-photon microscopy at Focused Ultrasound Lab, at Sunnybrook Research Institute (affiliated with University of Toronto by Dr. Alison Burgess, Charissa Poon and Marc Santos). + +The dataset contains a total of 12 volumetric stacks consisting images of mouse brain vasculature and tumor vasculature. + +### Information Statistics + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------------------------------ | ----------------- | ------------ | ----------------- | ------------ | --------------------- | ---------------------- | ------------ | ------------------------------------------------------------- | +| [2pm_vessel](https://opendatalab.org.cn/2-PM_Vessel_Dataset) | vessel | segmentation | microscopy_images | 2 | 216/-/- | yes/-/- | 2021 | [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 216 | 85.78 | - | - | - | - | +| vessel | 180 | 14.22 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![2pmv](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/histopathology/2pm_vessel/2pm_vessel_dataset.png?raw=true) + +### Dataset Citation + +``` +@article{teikari2016deep, + title={Deep learning convolutional networks for multiphoton microscopy vasculature segmentation}, + author={Teikari, Petteri and Santos, Marc and Poon, Charissa and Hynynen, Kullervo}, + journal={arXiv preprint arXiv:1606.02382}, + year={2016} +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `2pm_vessel/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://opendatalab.org.cn/2-PM_Vessel_Dataset) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```shell +mkdir data & cd data +pip install opendatalab +odl get 2-PM_Vessel_Dataset +cd .. +python tools/prepare_dataset.py +python ../../tools/split_seg_dataset.py +``` + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── microscopy_images + │ │ │ │ ├── 2pm_vessel + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 172 | 85.88 | 44 | 85.4 | - | - | +| vessel | 142 | 14.12 | 38 | 14.6 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [ ] Milestone 2: Indicates a successful model implementation. + + - [ ] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/2pm-vessel_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/2pm-vessel_512x512.py new file mode 100644 index 0000000..124403f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/2pm-vessel_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'TwoPMVesselDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_2pm-vessel-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_2pm-vessel-512x512.py new file mode 100644 index 0000000..2a429e9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_2pm-vessel-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './2pm-vessel_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.2pm-vessel_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_2pm-vessel-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_2pm-vessel-512x512.py new file mode 100644 index 0000000..10d9bb8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_2pm-vessel-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './2pm-vessel_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.2pm-vessel_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_2pm-vessel-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_2pm-vessel-512x512.py new file mode 100644 index 0000000..65c1579 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_2pm-vessel-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './2pm-vessel_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.2pm-vessel_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_bactteria-detection-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_bactteria-detection-512x512.py new file mode 100644 index 0000000..91ed6ad --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/configs/fcn-unet-s5-d16_unet_1xb16-0.01lr-sigmoid-20k_bactteria-detection-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './2pm-vessel_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.2pm-vessel_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/datasets/2pm-vessel_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/datasets/2pm-vessel_dataset.py new file mode 100644 index 0000000..984b5a1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/datasets/2pm-vessel_dataset.py @@ -0,0 +1,31 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class TwoPMVesselDataset(BaseSegDataset): + """TwoPMVesselDataset dataset. + + In segmentation map annotation for TwoPMVesselDataset, + 0 stands for background, which is included in 2 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('background', 'vessel')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/tools/prepare_dataset.py new file mode 100644 index 0000000..1b46af2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/2pm_vessel/tools/prepare_dataset.py @@ -0,0 +1,46 @@ +import os + +import tifffile as tiff +from PIL import Image + +root_path = 'data/' + +image_dir = os.path.join(root_path, + '2-PM_Vessel_Dataset/raw/vesselNN_dataset/denoised') +label_dir = os.path.join(root_path, + '2-PM_Vessel_Dataset/raw/vesselNN_dataset/labels') +tgt_img_train_dir = os.path.join(root_path, 'images/train/') +tgt_mask_train_dir = os.path.join(root_path, 'masks/train/') +os.system('mkdir -p ' + tgt_img_train_dir) +os.system('mkdir -p ' + tgt_mask_train_dir) + + +def filter_suffix(src_dir, suffix): + suffix = '.' + suffix if '.' not in suffix else suffix + file_names = [_ for _ in os.listdir(src_dir) if _.endswith(suffix)] + file_paths = [os.path.join(src_dir, _) for _ in file_names] + return sorted(file_paths), sorted(file_names) + + +if __name__ == '__main__': + + image_path_list, _ = filter_suffix(image_dir, suffix='tif') + label_path_list, _ = filter_suffix(label_dir, suffix='.tif') + + for img_path, label_path in zip(image_path_list, label_path_list): + labels = tiff.imread(label_path) + images = tiff.imread(img_path) + assert labels.ndim == 3 + assert images.shape == labels.shape + name = img_path.split('/')[-1].replace('.tif', '') + # a single .tif file contains multiple slices + # as long as it is read by tifffile package. + for i in range(labels.shape[0]): + slice_name = name + '_' + str(i).rjust(3, '0') + '.png' + image = images[i] + label = labels[i] // 255 + + save_path_label = os.path.join(tgt_mask_train_dir, slice_name) + Image.fromarray(label).save(save_path_label) + save_path_image = os.path.join(tgt_img_train_dir, slice_name) + Image.fromarray(image).convert('RGB').save(save_path_image) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/README.md new file mode 100644 index 0000000..1cedda7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/README.md @@ -0,0 +1,160 @@ +# Bactteria detection with darkfield microscopy + +## Description + +This project supports **`Bactteria detection with darkfield microscopy`**, which can be downloaded from [here](https://tianchi.aliyun.com/dataset/94411). + +### Dataset Overview + +Spirochaeta is a genus of bacteria classified within the phylum Spirochaetes. Included in this dataset are 366 darkfield microscopy images and manually annotated masks which can be used for classification and segmentation purposes. Detecting bacteria in blood could have a huge significance for research in both the medical and computer science field. + +It was gathered and annotated by students (hand-on experience) +It has more than just one targeted class (blood cell and bacteria were annotated) +It is highly imbalanced, so naive loss functions would work less properly + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| --------------------------------------------------------------- | ----------------- | ------------ | ---------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------- | +| [Bactteria detection](https://tianchi.aliyun.com/dataset/94411) | bacteria | segmentation | microscopy | 3 | 366/-/- | yes/-/- | 2017 | [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :----------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 366 | 85.9 | - | - | - | - | +| erythrocytes | 345 | 13.03 | - | - | - | - | +| spirochaete | 288 | 1.07 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/microscopy_images/bactteria_detection/bactteria_detection_dataset.png) + +## Usage + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow (PIL) v9.3.0 +- scikit-learn (sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `bactteria_detection/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- Download dataset from [here](https://tianchi.aliyun.com/dataset/94411) and save it to the `data/` directory . +- Decompress data to path `data/`. This will create a new folder named `data/Bacteria_detection_with_darkfield_microscopy_datasets/`, which contains the original image data. +- run script `python tools/prepare_dataset.py` to format data and change folder structure as below. +- run script `python ../../tools/split_seg_dataset.py` to split dataset. For the Bacteria_detection dataset, as there is no test or validation dataset, we sample 20% samples from the whole dataset as the validation dataset and 80% samples for training data and make two filename lists `train.txt` and `val.txt`. As we set the random seed as the hard code, we eliminated the randomness, the dataset split actually can be reproducible. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── microscopy_images + │ │ │ │ ├── bactteria_detection + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── Bacteria_detection_with_darkfield_microscopy_datasets + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :----------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 292 | 85.66 | 74 | 86.7 | - | - | +| erythrocytes | 274 | 13.25 | 71 | 12.29 | - | - | +| spirochaete | 231 | 1.09 | 57 | 1.01 | - | - | + +### Training commands + +Train models on a single server with one GPU. + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +Test models on a single server with one GPU. + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Results + +### Bactteria detection with darkfield microscopy + +***Note: The following experimental results are based on the data randomly partitioned according to the above method described in the dataset preparing section.*** + +| Method | Backbone | Crop Size | lr | mIoU | mDice | config | download | +| :-------------: | :------: | :-------: | :----: | :---: | :---: | :--------------------------------------------------------------------------------------: | :----------------------: | +| fcn_unet_s5-d16 | unet | 512x512 | 0.01 | 76.48 | 84.68 | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_bactteria-detection-512x512.py) | [model](<>) \| [log](<>) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.001 | 61.06 | 63.69 | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_bactteria-detection-512x512.py) | [model](<>) \| [log](<>) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.0001 | 58.87 | 62.42 | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_bactteria-detection-512x512.py) | [model](<>) \| [log](<>) | + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] Test-time correctness + + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/bactteria-detection_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/bactteria-detection_512x512.py new file mode 100644 index 0000000..e3eab4e --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/bactteria-detection_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'BactteriaDetectionDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_bactteria-detection-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_bactteria-detection-512x512.py new file mode 100644 index 0000000..ede58d7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_bactteria-detection-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './bactteria-detection_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.bactteria-detection_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_bactteria-detection-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_bactteria-detection-512x512.py new file mode 100644 index 0000000..bde3fa1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_bactteria-detection-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './bactteria-detection_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.bactteria-detection_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_bactteria-detection-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_bactteria-detection-512x512.py new file mode 100644 index 0000000..08e204f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_bactteria-detection-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './bactteria-detection_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.bactteria-detection_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=3), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/datasets/bactteria-detection_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/datasets/bactteria-detection_dataset.py new file mode 100644 index 0000000..c95097b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/datasets/bactteria-detection_dataset.py @@ -0,0 +1,27 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class BactteriaDetectionDataset(BaseSegDataset): + """BactteriaDetectionDataset dataset. + + In segmentation map annotation for BactteriaDetectionDataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + """ + METAINFO = dict(classes=('background', 'erythrocytes', 'spirochaete')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=False, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/tools/prepare_dataset.py new file mode 100644 index 0000000..8dcc719 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/microscopy_images/bactteria_detection/tools/prepare_dataset.py @@ -0,0 +1,33 @@ +import glob +import os +import shutil + +from PIL import Image + +root_path = 'data/' +img_suffix = '.png' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +x_train = glob.glob( + 'data/Bacteria_detection_with_darkfield_microscopy_datasets/images/*' + + img_suffix) # noqa + +os.system('mkdir -p ' + root_path + 'images/train/') +os.system('mkdir -p ' + root_path + 'masks/train/') + +part_dir_dict = {0: 'train/'} +for ith, part in enumerate([x_train]): + part_dir = part_dir_dict[ith] + for img in part: + basename = os.path.basename(img) + img_save_path = os.path.join(root_path, 'images', part_dir, + basename.split('.')[0] + save_img_suffix) + shutil.copy(img, img_save_path) + mask_path = 'data/Bacteria_detection_with_darkfield_microscopy_datasets/masks/' + basename # noqa + mask = Image.open(mask_path).convert('L') + mask_save_path = os.path.join( + root_path, 'masks', part_dir, + basename.split('.')[0] + save_seg_map_suffix) + mask.save(mask_save_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/tools/split_seg_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/tools/split_seg_dataset.py new file mode 100644 index 0000000..9ab2e92 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/tools/split_seg_dataset.py @@ -0,0 +1,42 @@ +import argparse +import glob +import os + +from sklearn.model_selection import train_test_split + + +def save_anno(img_list, file_path, remove_suffix=True): + if remove_suffix: + img_list = [ + '/'.join(img_path.split('/')[-2:]) for img_path in img_list + ] + img_list = [ + '.'.join(img_path.split('.')[:-1]) for img_path in img_list + ] + with open(file_path, 'w') as file_: + for x in list(img_list): + file_.write(x + '\n') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--data_root', default='data/') + args = parser.parse_args() + data_root = args.data_root + if os.path.exists(os.path.join(data_root, 'masks/val')): + x_val = sorted(glob.glob(data_root + '/images/val/*.png')) + save_anno(x_val, data_root + '/val.txt') + if os.path.exists(os.path.join(data_root, 'masks/test')): + x_test = sorted(glob.glob(data_root + '/images/test/*.png')) + save_anno(x_test, data_root + '/test.txt') + if not os.path.exists(os.path.join( + data_root, 'masks/val')) and not os.path.exists( + os.path.join(data_root, 'masks/test')): + all_imgs = sorted(glob.glob(data_root + '/images/train/*.png')) + x_train, x_val = train_test_split( + all_imgs, test_size=0.2, random_state=0) + save_anno(x_train, data_root + '/train.txt') + save_anno(x_val, data_root + '/val.txt') + else: + x_train = sorted(glob.glob(data_root + '/images/train/*.png')) + save_anno(x_train, data_root + '/train.txt') diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/README.md new file mode 100644 index 0000000..a1cd27b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/README.md @@ -0,0 +1,147 @@ +# Chest Image Dataset for Pneumothorax Segmentation + +## Description + +This project supports **`Chest Image Dataset for Pneumothorax Segmentation`**, which can be downloaded from [here](https://tianchi.aliyun.com/dataset/83075). + +### Dataset Overview + +Pneumothorax can be caused by a blunt chest injury, damage from underlying lung disease, or most horrifying—it may occur for no obvious reason at all. On some occasions, a collapsed lung can be a life-threatening event. +Pneumothorax is usually diagnosed by a radiologist on a chest x-ray, and can sometimes be very difficult to confirm. An accurate AI algorithm to detect pneumothorax would be useful in a lot of clinical scenarios. AI could be used to triage chest radiographs for priority interpretation, or to provide a more confident diagnosis for non-radiologists. + +The dataset is provided by the Society for Imaging Informatics in Medicine(SIIM), American College of Radiology (ACR),Society of Thoracic Radiology (STR) and MD.ai. You can develop a model to classify (and if present, segment) pneumothorax from a set of chest radiographic images. If successful, you could aid in the early recognition of pneumothoraces and save lives. + +### Original Statistic Information + +| Dataset name | Anatomical region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| --------------------------------------------------------------------- | ----------------- | ------------ | -------- | ------------ | --------------------- | ---------------------- | ------------ | ------------------------------------------------------------------ | +| [pneumothorax segmentation](https://tianchi.aliyun.com/dataset/83075) | thorax | segmentation | x_ray | 2 | 12089/-/3205 | yes/-/no | - | [CC-BY-SA-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :---------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| normal | 12089 | 99.75 | - | - | - | - | +| pneumothorax area | 2669 | 0.25 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![bac](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/x_ray/chest_image_pneum/chest_image_pneum_dataset.png) + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `chest_image_pneum/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://tianchi.aliyun.com/dataset/83075) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set can't be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── x_ray + │ │ │ │ ├── chest_image_pneum + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── test.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :---------------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| normal | 9637 | 99.75 | 2410 | 99.74 | - | - | +| pneumothorax area | 2137 | 0.25 | 532 | 0.26 | - | - | + +### Training commands + +Train models on a single server with one GPU. + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +Test models on a single server with one GPU. + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Results + +### Bactteria detection with darkfield microscopy + +| Method | Backbone | Crop Size | lr | mIoU | mDice | config | download | +| :-------------: | :------: | :-------: | :----: | :--: | :---: | :------------------------------------------------------------------------------------: | :----------------------: | +| fcn_unet_s5-d16 | unet | 512x512 | 0.01 | - | - | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-image-pneum-512x512.py) | [model](<>) \| [log](<>) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.001 | - | - | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-image-pneum-512x512.py) | [model](<>) \| [log](<>) | +| fcn_unet_s5-d16 | unet | 512x512 | 0.0001 | - | - | [config](./configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-image-pneum-512x512.py) | [model](<>) \| [log](<>) | + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + + - [x] Basic docstrings & proper citation + + - [x] Test-time correctness + + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + + - [ ] Unit tests + + - [ ] Code polishing + + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/chest-image-pneum_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/chest-image-pneum_512x512.py new file mode 100644 index 0000000..411229b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/chest-image-pneum_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'ChestImagePneumDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-image-pneum-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-image-pneum-512x512.py new file mode 100644 index 0000000..0f26459 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-image-pneum-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './chest-image-pneum_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.chest-image-pneum_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-image-pneum-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-image-pneum-512x512.py new file mode 100644 index 0000000..37b9188 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-image-pneum-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './chest-image-pneum_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.chest-image-pneum_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-image-pneum-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-image-pneum-512x512.py new file mode 100644 index 0000000..379e818 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-image-pneum-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + './chest-image-pneum_512x512.py', + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.chest-image-pneum_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/datasets/chest-image-pneum_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/datasets/chest-image-pneum_dataset.py new file mode 100644 index 0000000..aeee60a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/datasets/chest-image-pneum_dataset.py @@ -0,0 +1,27 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class ChestImagePneumDataset(BaseSegDataset): + """ChestImagePneumDataset dataset. + + In segmentation map annotation for ChestImagePneumDataset, + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + """ + METAINFO = dict(classes=('normal', 'pneumothorax area')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=False, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/tools/prepare_dataset.py new file mode 100644 index 0000000..47eddc9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_image_pneum/tools/prepare_dataset.py @@ -0,0 +1,73 @@ +import os + +import numpy as np +import pandas as pd +import pydicom +from PIL import Image + +root_path = 'data/' +img_suffix = '.dcm' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +x_train = [] +for fpath, dirname, fnames in os.walk('data/chestimage_train_datasets'): + for fname in fnames: + if fname.endswith('.dcm'): + x_train.append(os.path.join(fpath, fname)) +x_test = [] +for fpath, dirname, fnames in os.walk('data/chestimage_test_datasets/'): + for fname in fnames: + if fname.endswith('.dcm'): + x_test.append(os.path.join(fpath, fname)) + +os.system('mkdir -p ' + root_path + 'images/train/') +os.system('mkdir -p ' + root_path + 'images/test/') +os.system('mkdir -p ' + root_path + 'masks/train/') + + +def rle_decode(rle, width, height): + mask = np.zeros(width * height, dtype=np.uint8) + array = np.asarray([int(x) for x in rle.split()]) + starts = array[0::2] + lengths = array[1::2] + + current_position = 0 + for index, start in enumerate(starts): + current_position += start + mask[current_position:current_position + lengths[index]] = 1 + current_position += lengths[index] + + return mask.reshape(width, height, order='F') + + +part_dir_dict = {0: 'train/', 1: 'test/'} +dict_from_csv = pd.read_csv( + root_path + 'chestimage_train-rle_datasets.csv', sep=',', + index_col=0).to_dict()[' EncodedPixels'] + +for ith, part in enumerate([x_train, x_test]): + part_dir = part_dir_dict[ith] + for img in part: + basename = os.path.basename(img) + img_id = '.'.join(basename.split('.')[:-1]) + if ith == 0 and (img_id not in dict_from_csv.keys()): + continue + image = pydicom.read_file(img).pixel_array + save_img_path = root_path + 'images/' + part_dir + '.'.join( + basename.split('.')[:-1]) + save_img_suffix + print(save_img_path) + img_h, img_w = image.shape[:2] + image = Image.fromarray(image) + image.save(save_img_path) + if ith == 1: + continue + if dict_from_csv[img_id] == '-1': + mask = np.zeros((img_h, img_w), dtype=np.uint8) + else: + mask = rle_decode(dict_from_csv[img_id], img_h, img_w) + save_mask_path = root_path + 'masks/' + part_dir + '.'.join( + basename.split('.')[:-1]) + save_seg_map_suffix + mask = Image.fromarray(mask) + mask.save(save_mask_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/README.md new file mode 100644 index 0000000..7cb099c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/README.md @@ -0,0 +1,119 @@ +# Chest X-ray Images with Pneumothorax Masks + +## Description + +This project support **`Chest X-ray Images with Pneumothorax Masks `**, and the dataset used in this project can be downloaded from [here](https://www.kaggle.com/datasets/vbookshelf/pneumothorax-chest-xray-images-and-masks). + +### Dataset Overview + +A pneumothorax (noo-moe-THOR-aks) is a collapsed lung. A pneumothorax occurs when air leaks into the space between your lung and chest wall. This air pushes on the outside of your lung and makes it collapse. Pneumothorax can be a complete lung collapse or a collapse of only a portion of the lung. + +A pneumothorax can be caused by a blunt or penetrating chest injury, certain medical procedures, or damage from underlying lung disease. Or it may occur for no obvious reason. Symptoms usually include sudden chest pain and shortness of breath. On some occasions, a collapsed lung can be a life-threatening event. + +Treatment for a pneumothorax usually involves inserting a needle or chest tube between the ribs to remove the excess air. However, a small pneumothorax may heal on its own. + +### Statistic Information + +| Dataset Name | Anatomical Region | Task type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release date | License | +| --------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------ | -------- | ------------ | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------- | +| [Chest-x-ray-images-with-pneumothorax-masks](https://www.kaggle.com/datasets/vbookshelf/pneumothorax-chest-xray-images-and-masks) | throax | segmentation | x_ray | 2 | 10675/-/1372 | yes/-/yes | 2020 | [CC-BY-NC 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :----------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 10675 | 99.7 | - | - | 1372 | 99.71 | +| pneumothroax | 2379 | 0.3 | - | - | 290 | 0.29 | + +### Visualization + +![chest_x_ray_images_with_pneumothorax_masks](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/x_ray/chest_x_ray_images_with_pneumothorax_masks/chest_x_ray_images_with_pneumothorax_masks_dataset.png?raw=true) + +### Prerequisites + +- Python 3.8 +- PyTorch 1.10.0 +- pillow(PIL) 9.3.0 +- scikit-learn(sklearn) 1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of PYTHONPATH, which should point to the project's directory so that Python can locate the module files. In chest_x_ray_images_with_pneumothorax_masks/ root directory, run the following line to add the current directory to PYTHONPATH: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset preparing + +- download dataset from [here](https://www.kaggle.com/datasets/vbookshelf/pneumothorax-chest-xray-images-and-masks) and decompression data to path 'data/'. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── x_ray + │ │ │ │ ├── chest_x_ray_images_with_pneumothorax_masks + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Training commands + +```shell +mim train mmseg ./configs/${CONFIG_PATH} +``` + +To train on multiple GPUs, e.g. 8 GPUs, run the following command: + +```shell +mim train mmseg ./configs/${CONFIG_PATH} --launcher pytorch --gpus 8 +``` + +### Testing commands + +```shell +mim test mmseg ./configs/${CONFIG_PATH} --checkpoint ${CHECKPOINT_PATH} +``` + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [x] Test-time correctness + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/chest-x-ray-images-with-pneumothorax-masks_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/chest-x-ray-images-with-pneumothorax-masks_512x512.py new file mode 100644 index 0000000..96676de --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/chest-x-ray-images-with-pneumothorax-masks_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'ChestPenumoMaskDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py new file mode 100644 index 0000000..76c214d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py @@ -0,0 +1,20 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './chest-x-ray-images-with-pneumothorax-masks_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict( + imports='datasets.chest-x-ray-images-with-pneumothorax-masks_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py new file mode 100644 index 0000000..066996d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py @@ -0,0 +1,19 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './chest-x-ray-images-with-pneumothorax-masks_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict( + imports='datasets.chest-x-ray-images-with-pneumothorax-masks_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py new file mode 100644 index 0000000..a7065b8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py @@ -0,0 +1,19 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './chest-x-ray-images-with-pneumothorax-masks_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict( + imports='datasets.chest-x-ray-images-with-pneumothorax-masks_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py new file mode 100644 index 0000000..e5682ee --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_chest-x-ray-images-with-pneumothorax-masks-512x512.py @@ -0,0 +1,19 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', + './chest-x-ray-images-with-pneumothorax-masks_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict( + imports='datasets.chest-x-ray-images-with-pneumothorax-masks_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/datasets/chest-x-ray-images-with-pneumothorax-masks_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/datasets/chest-x-ray-images-with-pneumothorax-masks_dataset.py new file mode 100644 index 0000000..d32f597 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/datasets/chest-x-ray-images-with-pneumothorax-masks_dataset.py @@ -0,0 +1,31 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class ChestPenumoMaskDataset(BaseSegDataset): + """ChestPenumoMaskDataset dataset. + + In segmentation map annotation for ChestPenumoMaskDataset, + 0 stands for background, which is included in 2 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('background', 'penumothroax')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/tools/prepare_dataset.py new file mode 100644 index 0000000..c7de1f1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/chest_x_ray_images_with_pneumothorax_masks/tools/prepare_dataset.py @@ -0,0 +1,36 @@ +import glob +import os +import shutil + +from PIL import Image +from sklearn.model_selection import train_test_split + +root_path = 'data/' +img_suffix = '.png' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +all_imgs = glob.glob('data/siim-acr-pneumothorax/png_images/*' + img_suffix) +x_train, x_test = train_test_split(all_imgs, test_size=0.2, random_state=0) + +print(len(x_train), len(x_test)) +os.system('mkdir -p ' + root_path + 'images/train/') +os.system('mkdir -p ' + root_path + 'images/val/') +os.system('mkdir -p ' + root_path + 'masks/train/') +os.system('mkdir -p ' + root_path + 'masks/val/') + +part_dir_dict = {0: 'train/', 1: 'val/'} +for ith, part in enumerate([x_train, x_test]): + part_dir = part_dir_dict[ith] + for img in part: + basename = os.path.basename(img) + img_save_path = os.path.join(root_path, 'images', part_dir, + basename.split('.')[0] + save_img_suffix) + shutil.copy(img, img_save_path) + mask_path = 'data/siim-acr-pneumothorax/png_masks/' + basename + mask = Image.open(mask_path).convert('L') + mask_save_path = os.path.join( + root_path, 'masks', part_dir, + basename.split('.')[0] + save_seg_map_suffix) + mask.save(mask_save_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/README.md new file mode 100644 index 0000000..8469219 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/README.md @@ -0,0 +1,158 @@ +# Covid-19 CT Chest X-ray Dataset + +## Description + +This project supports **`Covid-19 CT Chest X-ray Dataset`**, which can be downloaded from [here](https://github.com/ieee8023/covid-chestxray-dataset). + +### Dataset Overview + +In the context of a COVID-19 pandemic, we want to improve prognostic predictions to triage and manage patient care. Data is the first step to developing any diagnostic/prognostic tool. While there exist large public datasets of more typical chest X-rays from the NIH \[Wang 2017\], Spain \[Bustos 2019\], Stanford \[Irvin 2019\], MIT \[Johnson 2019\] and Indiana University \[Demner-Fushman 2016\], there is no collection of COVID-19 chest X-rays or CT scans designed to be used for computational analysis. + +The 2019 novel coronavirus (COVID-19) presents several unique features [Fang, 2020](https://pubs.rsna.org/doi/10.1148/radiol.2020200432) and [Ai 2020](https://pubs.rsna.org/doi/10.1148/radiol.2020200642). While the diagnosis is confirmed using polymerase chain reaction (PCR), infected patients with pneumonia may present on chest X-ray and computed tomography (CT) images with a pattern that is only moderately characteristic for the human eye [Ng, 2020](https://pubs.rsna.org/doi/10.1148/ryct.2020200034). In late January, a Chinese team published a paper detailing the clinical and paraclinical features of COVID-19. They reported that patients present abnormalities in chest CT images with most having bilateral involvement [Huang 2020](). Bilateral multiple lobular and subsegmental areas of consolidation constitute the typical findings in chest CT images of intensive care unit (ICU) patients on admission [Huang 2020](). In comparison, non-ICU patients show bilateral ground-glass opacity and subsegmental areas of consolidation in their chest CT images [Huang 2020](). In these patients, later chest CT images display bilateral ground-glass opacity with resolved consolidation [Huang 2020](). + +### Statistic Information + +| Dataset Name | Anatomical Region | Task Type | Modality | Nnum. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release date | License | +| ---------------------------------------------------------------------- | ----------------- | ------------ | -------- | ------------- | --------------------- | ---------------------- | ------------ | --------------------------------------------------------------------- | +| [Covid-19-ct-cxr](https://github.com/ieee8023/covid-chestxray-dataset) | thorax | segmentation | x_ray | 2 | 205/-/714 | yes/-/no | 2021 | [CC-BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 205 | 72.84 | - | - | - | - | +| lung | 205 | 27.16 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![cov19ctcxr](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/x_ray/covid_19_ct_cxr/covid_19_ct_cxr_dataset.png?raw=true) + +### Dataset Citation + +``` +@article{cohen2020covidProspective, + title={{COVID-19} Image Data Collection: Prospective Predictions Are the Future}, + author={Joseph Paul Cohen and Paul Morrison and Lan Dao and Karsten Roth and Tim Q Duong and Marzyeh Ghassemi}, + journal={arXiv 2006.11988}, + year={2020} +} + +@article{cohen2020covid, + title={COVID-19 image data collection}, + author={Joseph Paul Cohen and Paul Morrison and Lan Dao}, + journal={arXiv 2003.11597}, + year={2020} +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 9.3.0 +- scikit-learn(sklearn) v1.2.0 1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `covid_19_ct_cxr/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://github.com/ieee8023/covid-chestxray-dataset) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```shell +mkdir data && cd data +git clone git@github.com:ieee8023/covid-chestxray-dataset.git +cd .. +python tools/prepare_dataset.py +python ../../tools/split_seg_dataset.py +``` + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── x_ray + │ │ │ │ ├── covid_19_ct_cxr + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 164 | 72.88 | 41 | 72.69 | - | - | +| lung | 164 | 27.12 | 41 | 27.31 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [x] Test-time correctness + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/covid-19-ct-cxr_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/covid-19-ct-cxr_512x512.py new file mode 100644 index 0000000..5242d06 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/covid-19-ct-cxr_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'Covid19CXRDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='val.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_covid-19-ct-cxr-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_covid-19-ct-cxr-512x512.py new file mode 100644 index 0000000..59a7bed --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet-{use-sigmoid}_1xb16-0.01-20k_covid-19-ct-cxr-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './covid-19-ct-cxr_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.covid-19-ct-cxr_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_covid-19-ct-cxr-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_covid-19-ct-cxr-512x512.py new file mode 100644 index 0000000..83b8527 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_covid-19-ct-cxr-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './covid-19-ct-cxr_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.covid-19-ct-cxr_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_covid-19-ct-cxr-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_covid-19-ct-cxr-512x512.py new file mode 100644 index 0000000..10cfcbd --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_covid-19-ct-cxr-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './covid-19-ct-cxr_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.covid-19-ct-cxr_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_covid-19-ct-cxr-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_covid-19-ct-cxr-512x512.py new file mode 100644 index 0000000..aaccc8f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_covid-19-ct-cxr-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './covid-19-ct-cxr_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.covid-19-ct-cxr_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/datasets/covid-19-ct-cxr_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/datasets/covid-19-ct-cxr_dataset.py new file mode 100644 index 0000000..68a1bb3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/datasets/covid-19-ct-cxr_dataset.py @@ -0,0 +1,31 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class Covid19CXRDataset(BaseSegDataset): + """Covid19CXRDataset dataset. + + In segmentation map annotation for Covid19CXRDataset, + 0 stands for background, which is included in 2 categories. + ``reduce_zero_label`` is fixed to False. The ``img_suffix`` + is fixed to '.png' and ``seg_map_suffix`` is fixed to '.png'. + + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False. + """ + METAINFO = dict(classes=('background', 'lung')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/tools/prepare_dataset.py new file mode 100644 index 0000000..72f6435 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/covid_19_ct_cxr/tools/prepare_dataset.py @@ -0,0 +1,52 @@ +import os + +import numpy as np +from PIL import Image + +root_path = 'data/' +src_img_dir = os.path.join(root_path, 'covid-chestxray-dataset', 'images') +src_mask_dir = os.path.join(root_path, 'covid-chestxray-dataset', + 'annotations/lungVAE-masks') +tgt_img_train_dir = os.path.join(root_path, 'images/train/') +tgt_mask_train_dir = os.path.join(root_path, 'masks/train/') +tgt_img_test_dir = os.path.join(root_path, 'images/test/') +os.system('mkdir -p ' + tgt_img_train_dir) +os.system('mkdir -p ' + tgt_mask_train_dir) +os.system('mkdir -p ' + tgt_img_test_dir) + + +def convert_label(img, convert_dict): + arr = np.zeros_like(img, dtype=np.uint8) + for c, i in convert_dict.items(): + arr[img == c] = i + return arr + + +if __name__ == '__main__': + + all_img_names = os.listdir(src_img_dir) + all_mask_names = os.listdir(src_mask_dir) + + for img_name in all_img_names: + base_name = img_name.replace('.png', '') + base_name = base_name.replace('.jpg', '') + base_name = base_name.replace('.jpeg', '') + mask_name_orig = base_name + '_mask.png' + if mask_name_orig in all_mask_names: + mask_name = base_name + '.png' + src_img_path = os.path.join(src_img_dir, img_name) + src_mask_path = os.path.join(src_mask_dir, mask_name_orig) + tgt_img_path = os.path.join(tgt_img_train_dir, img_name) + tgt_mask_path = os.path.join(tgt_mask_train_dir, mask_name) + + img = Image.open(src_img_path).convert('RGB') + img.save(tgt_img_path) + mask = np.array(Image.open(src_mask_path)) + mask = convert_label(mask, {0: 0, 255: 1}) + mask = Image.fromarray(mask) + mask.save(tgt_mask_path) + else: + src_img_path = os.path.join(src_img_dir, img_name) + tgt_img_path = os.path.join(tgt_img_test_dir, img_name) + img = Image.open(src_img_path).convert('RGB') + img.save(tgt_img_path) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/README.md b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/README.md new file mode 100644 index 0000000..0621205 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/README.md @@ -0,0 +1,144 @@ +# Chest Radiograph Anatomical Structure Segmentation (CRASS) + +## Description + +This project supports **`Chest Radiograph Anatomical Structure Segmentation (CRASS) `**, which can be downloaded from [here](https://crass.grand-challenge.org/). + +### Dataset Overview + +A set of consecutively obtained posterior-anterior chest radiograph were selected from a database containing images acquired at two sites in sub Saharan Africa with a high tuberculosis incidence. All subjects were 15 years or older. Images from digital chest radiography units were used (Delft Imaging Systems, The Netherlands) of varying resolutions, with a typical resolution of 1800--2000 pixels, the pixel size was 250 lm isotropic. From the total set of images, 225 were considered to be normal by an expert radiologist, while 333 of the images contained abnormalities. Of the abnormal images, 220 contained abnormalities in the upper area of the lung where the clavicle is located. The data was divided into a training and a test set. The training set consisted of 299 images, the test set of 249 images. +The current data is still incomplete and to be added later. + +### Information Statistics + +| Dataset Name | Anatomical Region | Task Type | Modality | Num. Classes | Train/Val/Test Images | Train/Val/Test Labeled | Release Date | License | +| ------------------------------------------- | ----------------- | ------------ | -------- | ------------ | --------------------- | ---------------------- | ------------ | ------------------------------------------------------------- | +| [crass](https://crass.grand-challenge.org/) | pulmonary | segmentation | x_ray | 2 | 299/-/234 | yes/-/no | 2021 | [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) | + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 299 | 98.38 | - | - | - | - | +| clavicles | 299 | 1.62 | - | - | - | - | + +Note: + +- `Pct` means percentage of pixels in this category in all pixels. + +### Visualization + +![crass](https://raw.githubusercontent.com/uni-medical/medical-datasets-visualization/main/2d/semantic_seg/x_ray/crass/crass_dataset.png?raw=true) + +### Dataset Citation + +``` +@article{HOGEWEG20121490, + title={Clavicle segmentation in chest radiographs}, + journal={Medical Image Analysis}, + volume={16}, + number={8}, + pages={1490-1502}, + year={2012} +} +``` + +### Prerequisites + +- Python v3.8 +- PyTorch v1.10.0 +- pillow(PIL) v9.3.0 +- scikit-learn(sklearn) v1.2.0 +- [MIM](https://github.com/open-mmlab/mim) v0.3.4 +- [MMCV](https://github.com/open-mmlab/mmcv) v2.0.0rc4 +- [MMEngine](https://github.com/open-mmlab/mmengine) v0.2.0 or higher +- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation) v1.0.0rc5 + +All the commands below rely on the correct configuration of `PYTHONPATH`, which should point to the project's directory so that Python can locate the module files. In `crass/` root directory, run the following line to add the current directory to `PYTHONPATH`: + +```shell +export PYTHONPATH=`pwd`:$PYTHONPATH +``` + +### Dataset Preparing + +- download dataset from [here](https://crass.grand-challenge.org/) and decompress data to path `'data/'`. +- run script `"python tools/prepare_dataset.py"` to format data and change folder structure as below. +- run script `"python ../../tools/split_seg_dataset.py"` to split dataset and generate `train.txt`, `val.txt` and `test.txt`. If the label of official validation set and test set cannot be obtained, we generate `train.txt` and `val.txt` from the training set randomly. + +```none + mmsegmentation + ├── mmseg + ├── projects + │ ├── medical + │ │ ├── 2d_image + │ │ │ ├── x_ray + │ │ │ │ ├── crass + │ │ │ │ │ ├── configs + │ │ │ │ │ ├── datasets + │ │ │ │ │ ├── tools + │ │ │ │ │ ├── data + │ │ │ │ │ │ ├── train.txt + │ │ │ │ │ │ ├── val.txt + │ │ │ │ │ │ ├── images + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png + │ │ │ │ │ │ ├── masks + │ │ │ │ │ │ │ ├── train + │ │ │ │ | │ │ │ ├── xxx.png + │ │ │ │ | │ │ │ ├── ... + │ │ │ │ | │ │ │ └── xxx.png +``` + +### Divided Dataset Information + +***Note: The table information below is divided by ourselves.*** + +| Class Name | Num. Train | Pct. Train | Num. Val | Pct. Val | Num. Test | Pct. Test | +| :--------: | :--------: | :--------: | :------: | :------: | :-------: | :-------: | +| background | 227 | 98.38 | 57 | 98.39 | - | - | +| clavicles | 227 | 1.62 | 57 | 1.61 | - | - | + +### Training commands + +To train models on a single server with one GPU. (default) + +```shell +mim train mmseg ./configs/${CONFIG_FILE} +``` + +### Testing commands + +To test models on a single server with one GPU. (default) + +```shell +mim test mmseg ./configs/${CONFIG_FILE} --checkpoint ${CHECKPOINT_PATH} +``` + + + +## Checklist + +- [x] Milestone 1: PR-ready, and acceptable to be one of the `projects/`. + + - [x] Finish the code + - [x] Basic docstrings & proper citation + - [ ] Test-time correctness + - [x] A full README + +- [x] Milestone 2: Indicates a successful model implementation. + + - [x] Training-time correctness + +- [ ] Milestone 3: Good to be a part of our core package! + + - [ ] Type hints and docstrings + - [ ] Unit tests + - [ ] Code polishing + - [ ] Metafile.yml + +- [ ] Move your modules into the core package following the codebase's file hierarchy structure. + +- [ ] Refactor your modules into the core package following the codebase's file hierarchy structure. diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/crass_512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/crass_512x512.py new file mode 100644 index 0000000..1425f50 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/crass_512x512.py @@ -0,0 +1,42 @@ +dataset_type = 'CRASSDataset' +data_root = 'data/' +img_scale = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=img_scale, keep_ratio=False), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') +] +train_dataloader = dict( + batch_size=16, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='train.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='tval.txt', + data_prefix=dict(img_path='images/', seg_map_path='masks/'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) +test_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mDice']) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_crass-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_crass-512x512.py new file mode 100644 index 0000000..b52bc78 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.0001-20k_crass-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './crass_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.crass_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.0001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_crass-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_crass-512x512.py new file mode 100644 index 0000000..45242c6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.001-20k_crass-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './crass_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.crass_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.001) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_crass-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_crass-512x512.py new file mode 100644 index 0000000..bcf9d0a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-0.01-20k_crass-512x512.py @@ -0,0 +1,17 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './crass_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.crass_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict(num_classes=2), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-lr0.01-sigmoid-20k_crass-512x512.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-lr0.01-sigmoid-20k_crass-512x512.py new file mode 100644 index 0000000..0dde736 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/configs/fcn-unet-s5-d16_unet_1xb16-lr0.01-sigmoid-20k_crass-512x512.py @@ -0,0 +1,18 @@ +_base_ = [ + 'mmseg::_base_/models/fcn_unet_s5-d16.py', './crass_512x512.py', + 'mmseg::_base_/default_runtime.py', + 'mmseg::_base_/schedules/schedule_20k.py' +] +custom_imports = dict(imports='datasets.crass_dataset') +img_scale = (512, 512) +data_preprocessor = dict(size=img_scale) +optimizer = dict(lr=0.01) +optim_wrapper = dict(optimizer=optimizer) +model = dict( + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=2, loss_decode=dict(use_sigmoid=True), out_channels=1), + auxiliary_head=None, + test_cfg=dict(mode='whole', _delete_=True)) +vis_backends = None +visualizer = dict(vis_backends=vis_backends) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/datasets/crass_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/datasets/crass_dataset.py new file mode 100644 index 0000000..f6b5c52 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/datasets/crass_dataset.py @@ -0,0 +1,30 @@ +from mmseg.datasets import BaseSegDataset +from mmseg.registry import DATASETS + + +@DATASETS.register_module() +class CRASSDataset(BaseSegDataset): + """CRASSDataset dataset. + + In segmentation map annotation for CRASSDataset, 0 stands for background, + which is included in 2 categories. ``reduce_zero_label`` is fixed to + False. The ``img_suffix`` is fixed to '.png' and ``seg_map_suffix`` is + fixed to '.png'. + Args: + img_suffix (str): Suffix of images. Default: '.png' + seg_map_suffix (str): Suffix of segmentation maps. Default: '.png' + reduce_zero_label (bool): Whether to mark label zero as ignored. + Default to False.. + """ + METAINFO = dict(classes=('background', 'clavicles')) + + def __init__(self, + img_suffix='.png', + seg_map_suffix='.png', + reduce_zero_label=False, + **kwargs) -> None: + super().__init__( + img_suffix=img_suffix, + seg_map_suffix=seg_map_suffix, + reduce_zero_label=reduce_zero_label, + **kwargs) diff --git a/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/tools/prepare_dataset.py b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/tools/prepare_dataset.py new file mode 100644 index 0000000..bbd5d88 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/medical/2d_image/x_ray/crass/tools/prepare_dataset.py @@ -0,0 +1,84 @@ +import glob +import os + +import cv2 +import SimpleITK as sitk +from PIL import Image + +root_path = 'data/' +img_suffix = '.tif' +seg_map_suffix = '.png' +save_img_suffix = '.png' +save_seg_map_suffix = '.png' + +src_img_train_dir = os.path.join(root_path, 'CRASS/data_train') +src_mask_train_dir = os.path.join(root_path, 'CRASS/mask_mhd') +src_img_test_dir = os.path.join(root_path, 'CRASS/data_test') + +tgt_img_train_dir = os.path.join(root_path, 'images/train/') +tgt_mask_train_dir = os.path.join(root_path, 'masks/train/') +tgt_img_test_dir = os.path.join(root_path, 'images/test/') +os.system('mkdir -p ' + tgt_img_train_dir) +os.system('mkdir -p ' + tgt_mask_train_dir) +os.system('mkdir -p ' + tgt_img_test_dir) + + +def filter_suffix_recursive(src_dir, suffix): + suffix = '.' + suffix if '.' not in suffix else suffix + file_paths = glob( + os.path.join(src_dir, '**', '*' + suffix), recursive=True) + file_names = [_.split('/')[-1] for _ in file_paths] + return sorted(file_paths), sorted(file_names) + + +def read_single_array_from_med(path): + return sitk.GetArrayFromImage(sitk.ReadImage(path)).squeeze() + + +def convert_meds_into_pngs(src_dir, + tgt_dir, + suffix='.dcm', + norm_min=0, + norm_max=255, + convert='RGB'): + if not os.path.exists(tgt_dir): + os.makedirs(tgt_dir) + + src_paths, src_names = filter_suffix_recursive(src_dir, suffix=suffix) + num = len(src_paths) + for i, (src_name, src_path) in enumerate(zip(src_names, src_paths)): + tgt_name = src_name.replace(suffix, '.png') + tgt_path = os.path.join(tgt_dir, tgt_name) + + img = read_single_array_from_med(src_path) + if norm_min is not None and norm_max is not None: + img = cv2.normalize(img, None, norm_min, norm_max, cv2.NORM_MINMAX, + cv2.CV_8U) + pil = Image.fromarray(img).convert(convert) + pil.save(tgt_path) + print(f'processed {i+1}/{num}.') + + +convert_meds_into_pngs( + src_img_train_dir, + tgt_img_train_dir, + suffix='.mhd', + norm_min=0, + norm_max=255, + convert='RGB') + +convert_meds_into_pngs( + src_img_test_dir, + tgt_img_test_dir, + suffix='.mhd', + norm_min=0, + norm_max=255, + convert='RGB') + +convert_meds_into_pngs( + src_mask_train_dir, + tgt_mask_train_dir, + suffix='.mhd', + norm_min=0, + norm_max=1, + convert='L') diff --git a/Seg_All_In_One_MMSeg/projects/nvidia_jetson/README.md b/Seg_All_In_One_MMSeg/projects/nvidia_jetson/README.md new file mode 100644 index 0000000..6cebd9c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/nvidia_jetson/README.md @@ -0,0 +1,372 @@ +# 将 MMSeg 模型调优及部署到 NVIDIA Jetson 平台教程 + +- 请先查阅[MMSegmentation 模型部署](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/user_guides/5_deployment.md)文档。 +- **本教程所用 mmsegmentation 版本: v1.1.2** +- **本教程所用 NVIDIA Jetson 设备: NVIDIA Jetson AGX Orin 64G** + +

+ +## 1 配置 [mmsegmentation](https://github.com/open-mmlab/mmsegmentation) + +- 根据[安装和验证](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/get_started.md)文档,完成开发 [mmsegmentation](https://github.com/open-mmlab/mmsegmentation) 所需的 [`pytorch`](https://pytorch.org/get-started/locally/)、[`mmcv`](https://github.com/open-mmlab/mmcv)、[`mmengine`](https://github.com/open-mmlab/mmengine) 等环境依赖安装。 +- 从 GitHub 使用 git clone 命令完成 [mmsegmentation](https://github.com/open-mmlab/mmsegmentation) 下载。网络不好的同学,可通过 [MMSeg GitHub](https://github.com/open-mmlab/mmsegmentation) 页面进行 zip 的下载。 + ```bash + git clone https://github.com/open-mmlab/mmsegmentation.git + ``` +- 使用 `pip install -v -e.` 命令动态安装 mmsegmentation 。 + ```bash + cd mmsegmentation + pip install -v -e . + ``` + 提示成功安装后,可通过 `pip list` 命令查看到 mmsegmentation 已通过本地安装方式安装到了您的环境中。 + ![mmseg-install](https://github.com/AI-Tianlong/Useful-Tools/assets/50650583/a9c7bcc9-cdcc-40a4-bd7b-8153195549c8) + +## 2 准备您的数据集 + +- 本教程使用遥感图像语义分割数据集 [potsdam](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/user_guides/2_dataset_prepare.md#isprs-potsdam) 作为示例。 +- 根据 [potsdam 数据准备](https://github.com/open-mmlab/mmsegmentation/blob/main/docs/zh_cn/user_guides/2_dataset_prepare.md#isprs-potsdam)文档,进行数据集下载及 MMSeg 格式的准备。 +- 数据集介绍: potsdam 数据集是以德国一个典型的历史城市 Potsdam 命名的,该城市有着大建筑群、狭窄的街道和密集的建筑结构。 potsdam 数据集包含 38 幅 6000x6000 像素的图像,空间分辨率为 5cm,数据集的示例如下图: + ![potsdam-img](https://github.com/AI-Tianlong/Useful-Tools/assets/50650583/3bc0a75b-1693-4ae6-aeea-ad502e955068) + +## 3 从 config 页面下载模型的 pth 权重文件 + +这里以 [`deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py`](../../configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py) 配置文件举例,在 [configs](https://github.com/open-mmlab/mmsegmentation/tree/main/configs/deeplabv3plus#potsdam) 页面下载权重文件, +![pth](https://github.com/AI-Tianlong/Useful-Tools/assets/50650583/8f747362-caf4-406c-808d-4ca72babb209) + +## 4 通过 [OpenMMLab deployee](https://platform.openmmlab.com/deploee) 以交互式方式进行模型转换及测速 + +### 4.1 模型转换 + +在该部分中,[OpenMMLab 官网](https://platform.openmmlab.com/deploee)提供了模型转换及模型测速的交互界面,无需任何代码,即可通过选择对应选项完成模型 ONNX 格式`xxxx.onnx` 和 TensorRT `.engine`格式的转换。 +如您的自定义 config 文件中有相对引用关系,如: + +```python +# xxxx.py +_base_ = [ + '../_base_/models/deeplabv3plus_r50-d8.py', + '../_base_/datasets/potsdam.py', + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_80k.py' +] +``` + +您可以使用以下代码消除相对引用关系,以生成完整的 config 文件。 + +```python +import mmengine + +mmengine.Config.fromfile("configs/deeplabv3plus/deeplabv3plus_r101-d8_4xb4-80k_potsdam-512x512.py").dump("My_config.py") +``` + +使用上述代码后,您能够看到,在`My_config.py`包含着完整的配置文件,无相对引用。这时,上传模型 config 至网页内对应处。 + +#### 创建转换任务 + +按照下图提示及自己的需求,创建转换任务并提交。 + +
+ NVIDIA-Jetson +
+ +### 4.2 模型测速 + +在完成模型转换后可通过**模型测速**界面,完成在真实设备上的模型测速。 + +#### 创建测速任务 + +
+ NVIDIA-Jetson +
+ +
+ NVIDIA-Jetson +
+ +测速完成后,可在页面生成完整的测速报告。[查看测速报告示例](https://openmmlab-deploee.oss-cn-shanghai.aliyuncs.com/tmp/profile_speed/4352f5.txt) + +## 5 通过 OpenMMLab mmdeploy 以命令行将模型转换为ONNX格式 + +该部分可以通过 mmdeploy 库对 mmseg 训练好的模型进行推理格式的转换。这里给出一个示例,具体文档可见[ mmdeploy 模型转换文档](../../docs/zh_cn/user_guides/5_deployment.md)。 + +### 5.1 通过源码构建 mmdeploy 库 + +在您安装 mmsegmentation 库的虚拟环境下,通过 `git clone`命令从 GitHub 克隆 [mmdeploy](https://github.com/open-mmlab/mmdeploy) + +### 5.2 模型转换 + +如您的 config 中含有相对引用,仍需进行消除,如[4.1 模型转换](#4.1-模型转换)所述, +进入 mmdeploy 文件夹,执行以下命令,即可完成模型转换。 + +```bash +python tools/deploy.py \ + configs/mmseg/segmentation_onnxruntime_static-512x512.py \ + ../atl_config.py \ + ../deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601-75fd5bc3.pth \ + ../2_13_1024_5488_1536_6000.png \ + --work-dir ../atl_models \ + --device cpu \ + --show \ + --dump-info +``` + +```bash +# 使用方法 +python ./tools/deploy.py \ + ${部署配置文件路径} \ + ${模型配置文件路径} \ + ${模型权重路径} \ + ${输入图像路径} \ + --work-dir ${用来保存日志和模型文件路径} \ + --device ${cpu/cuda:0} \ + --show \ # 是否显示检测的结果 + --dump-info # 是否输出 SDK 信息 + +``` + +执行成功后,您将能够看到以下提示,即为转换成功。 + +```bash +10/08 17:40:44 - mmengine - INFO - visualize pytorch model success. +10/08 17:40:44 - mmengine - INFO - All process success. +``` + +
+ NVIDIA-Jetson +
+ +# 6 在 Jetson 平台进行转换及部署 + +## 6.1 环境准备 + +参考[如何在 Jetson 模组上安装 MMDeploy](https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/01-how-to-build/jetsons.md)文档,完成在 Jetson 上的环境准备工作。 +**注**:安装 Pytorch,可查阅 [NVIDIA Jetson Pytorch 安装文档](https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/01-how-to-build/jetsons.md)安装最新的 Pytorch。 + +### 6.1.1 创建虚拟环境 + +```bash +conda create -n {您虚拟环境的名字} python={python版本} +``` + +### 6.1.2 虚拟环境内安装Pytorch + +注意:这里不要安装最新的 pytorch 2.0,因为 pyTorch 1.11 是最后一个使用 USE_DISTRIBUTED 构建的wheel,否则会在用mmdeploy进行模型转换的时候提示`AttributeError: module 'torch.distributed' has no attribute 'ReduceOp'`的错误。参考以下链接:https://forums.developer.nvidia.com/t/module-torch-distributed-has-no-attribute-reduceop/256581/6 +下载`torch-1.11.0-cp38-cp38-linux_aarch64.whl`并安装 + +```bash +pip install torch-1.11.0-cp38-cp38-linux_aarch64.whl +``` + +执行以上命令后,您将能看到以下提示,即为安装成功。 + +```bash +Processing ./torch-1.11.0-cp38-cp38-linux_aarch64.whl +Requirement already satisfied: typing-extensions in /home/sirs/miniconda3/envs/openmmlab/lib/python3.8/site-packages (from torch==1.11.0) (4.7.1) +Installing collected packages: torch +Successfully installed torch-1.11.0 +``` + +### 6.1.3 将 Jetson Pack 自带的 tensorrt 拷贝至虚拟环境下 + +请参考[配置 TensorRT](https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/01-how-to-build/jetsons.md#%E9%85%8D%E7%BD%AE-tensorrt)。 +JetPack SDK 自带 TensorRT。 但是为了能够在 Conda 环境中成功导入,我们需要将 TensorRT 拷贝进先前创建的 Conda 环境中。 + +```bash +export PYTHON_VERSION=`python3 --version | cut -d' ' -f 2 | cut -d'.' -f1,2` +cp -r /usr/lib/python${PYTHON_VERSION}/dist-packages/tensorrt* ~/miniconda/envs/{您的虚拟环境名字}/lib/python${PYTHON_VERSION}/site-packages/ +``` + +### 6.1.4 安装 MMCV + +通过`mim install mmcv`或从源码对其进行编译。 + +```bash +pip install openmim +mim install mmcv +``` + +或者从源码对其进行编译。 + +```bash +sudo apt-get install -y libssl-dev +git clone https://github.com/open-mmlab/mmcv.git +cd mmcv +pip install -e . +``` + +注:pytorch版本发生变动后,需要重新编译mmcv。 + +### 6.1.5 安装 ONNX + +注:以下方式二选一 + +- conda + ```bash + conda install -c conda-forge onnx + ``` +- pip + ```bash + python3 -m pip install onnx + ``` + +### 6.1.6 安装 ONNX Runtime + +根据网页 [ONNX Runtime](https://elinux.org/Jetson_Zoo#ONNX_Runtime) 选择合适的ONNX Runtime版本进行下载安装。 +示例: + +```bash +# Install pip wheel +$ pip3 install onnxruntime_gpu-1.10.0-cp38-cp38-linux_aarch64.whl + +``` + +## 6.2 在 Jetson AGX Orin 进行模型转换及推理 + +### 6.2.1 ONNX 模型转换 + +同[4.1 模型转换](#4.1-模型转换)相同,在 Jetson 平台下进入安装好的虚拟环境,以及mmdeploy 目录,进行模型ONNX转换。 + +```bash +python tools/deploy.py \ + configs/mmseg/segmentation_onnxruntime_static-512x512.py \ + ../atl_config.py \ + ../deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601-75fd5bc3.pth \ + ../2_13_3584_2560_4096_3072.png \ + --work-dir ../atl_models \ + --device cpu \ + --show \ + --dump-info + +``` + +注: 如果报错提示内容: + +```none +AttributeError: module 'torch.distributed' has no attribute 'ReduceOp' +``` + +可参考以下链接进行解决:https://forums.developer.nvidia.com/t/module-torch-distributed-has-no-attribute-reduceop/256581/6,即安装 pytorch 1.11.0 版本。 + +转换成功后,您将会看到如下信息以及包含 ONNX 模型的文件夹: + +```bash +10/09 19:58:22 - mmengine - INFO - visualize pytorch model success. +10/09 19:58:22 - mmengine - INFO - All process success. +``` + +
+ NVIDIA-Jetson + NVIDIA-Jetson +
+ +### 6.2.2 TensorRT 模型转换 + +更换部署trt配置文件,进行 TensorRT 模型转换。 + +```bash +python tools/deploy.py \ + configs/mmseg/segmentation_tensorrt_static-512x512.py \ + ../atl_config.py \ + ../deeplabv3plus_r18-d8_512x512_80k_potsdam_20211219_020601-75fd5bc3.pth \ + ../2_13_3584_2560_4096_3072.png \ + --work-dir ../atl_trt_models \ + --device cuda:0 \ + --show \ + --dump-info + +``` + +转换成功后您将看到以下信息及 TensorRT 模型文件夹: + +```bash +10/09 20:15:50 - mmengine - INFO - visualize pytorch model success. +10/09 20:15:50 - mmengine - INFO - All process success. +``` + +
+ NVIDIA-Jetson + NVIDIA-Jetson +
+ +## 6.3 模型测速 + +执行以下命令完成模型测速,详细内容请查看[ profiler ](https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/02-how-to-run/useful_tools.md#profiler) + +```bash +python tools/profiler.py \ + ${DEPLOY_CFG} \ + ${MODEL_CFG} \ + ${IMAGE_DIR} \ + --model ${MODEL} \ + --device ${DEVICE} \ + --shape ${SHAPE} \ + --num-iter ${NUM_ITER} \ + --warmup ${WARMUP} \ + --cfg-options ${CFG_OPTIONS} \ + --batch-size ${BATCH_SIZE} \ + --img-ext ${IMG_EXT} +``` + +示例: + +```bash +python tools/profiler.py \ + configs/mmseg/segmentation_tensorrt_static-512x512.py \ + ../atl_config.py \ + ../atl_demo_img \ + --model /home/sirs/AI-Tianlong/OpenMMLab/atl_trt_models/end2end.engine \ + --device cuda:0 \ + --shape 512x512 \ + --num-iter 100 +``` + +测速结果 + +![image](https://github.com/AI-Tianlong/Useful-Tools/assets/50650583/874e9742-ee10-490c-9e69-17da0096c49b) + +## 6.4 模型推理 + +根据[6.2.2](#6.2.2-TensorRT-模型转换)中生成的TensorRT模型文件夹,进行模型推理。 + +```python +from mmdeploy.apis.utils import build_task_processor +from mmdeploy.utils import get_input_shape, load_config +import torch + +deploy_cfg='./mmdeploy/configs/mmseg/segmentation_tensorrt_static-512x512.py' +model_cfg='./atl_config.py' +device='cuda:0' +backend_model = ['./atl_trt_models/end2end.engine'] +image = './atl_demo_img/2_13_2048_1024_2560_1536.png' + +# read deploy_cfg and model_cfg +deploy_cfg, model_cfg = load_config(deploy_cfg, model_cfg) + +# build task and backend model +task_processor = build_task_processor(model_cfg, deploy_cfg, device) +model = task_processor.build_backend_model(backend_model) + +# process input image +input_shape = get_input_shape(deploy_cfg) +model_inputs, _ = task_processor.create_input(image, input_shape) + +# do model inference +with torch.no_grad(): + result = model.test_step(model_inputs) + +# visualize results +task_processor.visualize( + image=image, + model=model, + result=result[0], + window_name='visualize', + output_file='./output_segmentation.png') +``` + +即可得到推理结果: + +
+ NVIDIA-Jetson + NVIDIA-Jetson +
diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/README.md b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/README.md new file mode 100644 index 0000000..c9f9c12 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/README.md @@ -0,0 +1,123 @@ +# PP-MobileSeg: Exploring Transformer Blocks for Efficient Mobile Segmentation. + +## Reference + +> [PP-MobileSeg: Explore the Fast and Accurate Semantic Segmentation Model on Mobile Devices. ](https://arxiv.org/abs/2304.05152) + +## Introduction + +
Official Repo + +Code Snippet + +## Abstract + +With the success of transformers in computer vision, several attempts have been made to adapt transformers to mobile devices. However, their performance is not satisfied for some real world applications. Therefore, we propose PP-MobileSeg, a SOTA semantic segmentation model for mobile devices. + +It is composed of three newly proposed parts, the strideformer backbone, the Aggregated Attention Module(AAM), and the Valid Interpolate Module(VIM): + +- With the four-stage MobileNetV3 block as the feature extractor, we manage to extract rich local features of different receptive fields with little parameter overhead. Also, we further efficiently empower features from the last two stages with the global view using strided sea attention. +- To effectively fuse the features, we use AAM to filter the detail features with ensemble voting and add the semantic feature to it to enhance the semantic information to the most content. +- At last, we use VIM to upsample the downsampled feature to the original resolution and significantly decrease latency in model inference stage. It only interpolates classes present in the final prediction which only takes around 10% in the ADE20K dataset. This is a common scenario for datasets with large classes. Therefore it significantly decreases the latency of the final upsample process which takes the greatest part of the model's overall latency. + +Extensive experiments show that PP-MobileSeg achieves a superior params-accuracy-latency tradeoff compared to other SOTA methods. + +
+ +
+ +## Performance + +### ADE20K + +| Model | Backbone | Training Iters | Batchsize | Train Resolution | mIoU(%) | latency(ms)\* | params(M) | config | Links | +| ----------------- | ----------------- | -------------- | --------- | ---------------- | ------- | ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| PP-MobileSeg-Base | StrideFormer-Base | 80000 | 32 | 512x512 | 41.57% | 265.5 | 5.62 | [config](https://github.com/Yang-Changhui/mmsegmentation/tree/add_ppmobileseg/projects/pp_mobileseg/configs/pp_mobileseg) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pp_mobileseg/pp_mobileseg_mobilenetv3_2xb16_3rdparty-base_512x512-ade20k-f12b44f3.pth)\|[log](https://bj.bcebos.com/paddleseg/dygraph/ade20k/pp_mobileseg_base/train.log) | +| PP-MobileSeg-Tiny | StrideFormer-Tiny | 80000 | 32 | 512x512 | 36.39% | 215.3 | 1.61 | [config](https://github.com/Yang-Changhui/mmsegmentation/tree/add_ppmobileseg/projects/pp_mobileseg/configs/pp_mobileseg) | [model](https://download.openmmlab.com/mmsegmentation/v0.5/pp_mobileseg/pp_mobileseg_mobilenetv3_2xb16_3rdparty-tiny_512x512-ade20k-a351ebf5.pth)\|[log](https://bj.bcebos.com/paddleseg/dygraph/ade20k/pp_mobileseg_tiny/train.log) | + +## Usage + +Same as other models in MMsegmentation, you can run the following command to test the model at ${MMSEG_ROOT}: + +```shell +./tools/dist_test.sh projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_base.py checkpoints/pp_mobileseg_mobilenetv3_2xb16_3rdparty-base_512x512-ade20k-f12b44f3.pth 8 +``` + +## Inference with ONNXRuntime + +### Prerequisites + +**1. Install onnxruntime inference engine.** + +Choose one of the following ways to install onnxruntime. + +- CPU version + +```shell +pip install onnxruntime==1.15.1 +wget https://github.com/microsoft/onnxruntime/releases/download/v1.15.1/onnxruntime-linux-x64-1.15.1.tgz +tar -zxvf onnxruntime-linux-x64-1.15.1.tgz +export ONNXRUNTIME_DIR=$(pwd)/onnxruntime-linux-x64-1.15.1 +export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH +``` + +**2. Convert model to onnx file** + +- Install `mim` and `mmdeploy`. + +```shell +pip install openmim +mim install mmdeploy +git clone https://github.com/open-mmlab/mmdeploy.git +``` + +- Download pp_mobileseg model. + +```shell +wget https://download.openmmlab.com/mmsegmentation/v0.5/pp_mobileseg/pp_mobileseg_mobilenetv3_2xb16_3rdparty-tiny_512x512-ade20k-a351ebf5.pth +``` + +- Convert model to onnx files. + +```shell +python mmdeploy/tools/deploy.py mmdeploy/configs/mmseg/segmentation_onnxruntime_dynamic.py \ + configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_tiny.py \ + pp_mobileseg_mobilenetv3_2xb16_3rdparty-tiny_512x512-ade20k-a351ebf5.pth \ + ../../demo/demo.png \ + --work-dir mmdeploy_model/mmseg/ort \ + --show +``` + +**3. Run demo** + +```shell +python inference_onnx.py ${ONNX_FILE_PATH} ${IMAGE_PATH} [${MODEL_INPUT_SIZE} ${DEVICE} ${OUTPUT_IMAGE_PATH}] +``` + +Example: + +```shell +python inference_onnx.py mmdeploy_model/mmseg/ort/end2end.onnx ../../demo/demo.png +``` + +## Citation + +If you find our project useful in your research, please consider citing: + +``` +@misc{liu2021paddleseg, + title={PaddleSeg: A High-Efficient Development Toolkit for Image Segmentation}, + author={Yi Liu and Lutao Chu and Guowei Chen and Zewu Wu and Zeyu Chen and Baohua Lai and Yuying Hao}, + year={2021}, + eprint={2101.06175}, + archivePrefix={arXiv}, + primaryClass={cs.CV} +} + +@misc{paddleseg2019, + title={PaddleSeg, End-to-end image segmentation kit based on PaddlePaddle}, + author={PaddlePaddle Contributors}, + howpublished = {\url{https://github.com/PaddlePaddle/PaddleSeg}}, + year={2019} +} +``` diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/backbones/__init__.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/backbones/__init__.py new file mode 100644 index 0000000..244b33d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/backbones/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .strideformer import StrideFormer + +__all__ = ['StrideFormer'] diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/backbones/strideformer.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/backbones/strideformer.py new file mode 100644 index 0000000..3f09be5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/backbones/strideformer.py @@ -0,0 +1,958 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import math +import warnings + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, build_activation_layer +from mmcv.cnn.bricks.transformer import build_dropout +from mmengine.logging import print_log +from mmengine.model import BaseModule +from mmengine.runner.checkpoint import CheckpointLoader, load_state_dict + +from mmseg.registry import MODELS + + +@MODELS.register_module() +class StrideFormer(BaseModule): + """The StrideFormer implementation based on torch. + + The original article refers to:https://arxiv.org/abs/2304.05152 + Args: + mobileV3_cfg(list): Each sublist describe the config for a + MobileNetV3 block. + channels(list): The input channels for each MobileNetV3 block. + embed_dims(list): The channels of the features input to the sea + attention block. + key_dims(list, optional): The embeding dims for each head in + attention. + depths(list, optional): describes the depth of the attention block. + i,e: M,N. + num_heads(int, optional): The number of heads of the attention + blocks. + attn_ratios(int, optional): The expand ratio of V. + mlp_ratios(list, optional): The ratio of mlp blocks. + drop_path_rate(float, optional): The drop path rate in attention + block. + act_cfg(dict, optional): The activation layer of AAM: + Aggregate Attention Module. + inj_type(string, optional): The type of injection/AAM. + out_channels(int, optional): The output channels of the AAM. + dims(list, optional): The dimension of the fusion block. + out_feat_chs(list, optional): The input channels of the AAM. + stride_attention(bool, optional): whether to stride attention in + each attention layer. + pretrained(str, optional): the path of pretrained model. + """ + + def __init__( + self, + mobileV3_cfg, + channels, + embed_dims, + key_dims=[16, 24], + depths=[2, 2], + num_heads=8, + attn_ratios=2, + mlp_ratios=[2, 4], + drop_path_rate=0.1, + act_cfg=dict(type='ReLU'), + inj_type='AAM', + out_channels=256, + dims=(128, 160), + out_feat_chs=None, + stride_attention=True, + pretrained=None, + init_cfg=None, + ): + super().__init__(init_cfg=init_cfg) + assert not (init_cfg and pretrained + ), 'init_cfg and pretrained cannot be set at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is not None: + raise TypeError('pretrained must be a str or None') + + self.depths = depths + self.cfgs = mobileV3_cfg + self.dims = dims + for i in range(len(self.cfgs)): + smb = StackedMV3Block( + cfgs=self.cfgs[i], + stem=True if i == 0 else False, + in_channels=channels[i], + ) + setattr(self, f'smb{i + 1}', smb) + for i in range(len(depths)): + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, depths[i]) + ] + trans = BasicLayer( + block_num=depths[i], + embedding_dim=embed_dims[i], + key_dim=key_dims[i], + num_heads=num_heads, + mlp_ratio=mlp_ratios[i], + attn_ratio=attn_ratios, + drop=0, + attn_drop=0.0, + drop_path=dpr, + act_cfg=act_cfg, + stride_attention=stride_attention, + ) + setattr(self, f'trans{i + 1}', trans) + + self.inj_type = inj_type + if self.inj_type == 'AAM': + self.inj_module = InjectionMultiSumallmultiallsum( + in_channels=out_feat_chs, out_channels=out_channels) + self.feat_channels = [ + out_channels, + ] + elif self.inj_type == 'AAMSx8': + self.inj_module = InjectionMultiSumallmultiallsumSimpx8( + in_channels=out_feat_chs, out_channels=out_channels) + self.feat_channels = [ + out_channels, + ] + elif self.inj_type == 'origin': + for i in range(len(dims)): + fuse = FusionBlock( + out_feat_chs[0] if i == 0 else dims[i - 1], + out_feat_chs[i + 1], + embed_dim=dims[i], + act_cfg=None, + ) + setattr(self, f'fuse{i + 1}', fuse) + self.feat_channels = [ + dims[i], + ] + else: + raise NotImplementedError(self.inj_module + ' is not implemented') + + self.pretrained = pretrained + # self.init_weights() + + def init_weights(self): + if (isinstance(self.init_cfg, dict) + and self.init_cfg.get('type') == 'Pretrained'): + checkpoint = CheckpointLoader.load_checkpoint( + self.init_cfg['checkpoint'], logger=None, map_location='cpu') + + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + else: + state_dict = checkpoint + + if 'pos_embed' in state_dict.keys(): + if self.pos_embed.shape != state_dict['pos_embed'].shape: + print_log(msg=f'Resize the pos_embed shape from ' + f'{state_dict["pos_embed"].shape} to ' + f'{self.pos_embed.shape}') + h, w = self.img_size + pos_size = int( + math.sqrt(state_dict['pos_embed'].shape[1] - 1)) + state_dict['pos_embed'] = self.resize_pos_embed( + state_dict['pos_embed'], + (h // self.patch_size, w // self.patch_size), + (pos_size, pos_size), + self.interpolate_mode, + ) + + load_state_dict(self, state_dict, strict=False, logger=None) + + def forward(self, x): + x_hw = x.shape[2:] + outputs = [] + num_smb_stage = len(self.cfgs) + num_trans_stage = len(self.depths) + + for i in range(num_smb_stage): + smb = getattr(self, f'smb{i + 1}') + x = smb(x) + + # 1/8 shared feat + if i == 1: + outputs.append(x) + if num_trans_stage + i >= num_smb_stage: + trans = getattr( + self, f'trans{i + num_trans_stage - num_smb_stage + 1}') + x = trans(x) + outputs.append(x) + if self.inj_type == 'origin': + x_detail = outputs[0] + for i in range(len(self.dims)): + fuse = getattr(self, f'fuse{i + 1}') + + x_detail = fuse(x_detail, outputs[i + 1]) + output = x_detail + else: + output = self.inj_module(outputs) + + return [output, x_hw] + + +class StackedMV3Block(nn.Module): + """The MobileNetV3 block. + + Args: + cfgs (list): The MobileNetV3 config list of a stage. + stem (bool): Whether is the first stage or not. + in_channels (int, optional): The channels of input image. Default: 3. + scale: float=1.0. + The coefficient that controls the size of network parameters. + + Returns: + model: nn.Module. + A stage of specific MobileNetV3 model depends on args. + """ + + def __init__(self, + cfgs, + stem, + in_channels, + scale=1.0, + norm_cfg=dict(type='BN')): + super().__init__() + + self.scale = scale + self.stem = stem + + if self.stem: + self.conv = ConvModule( + in_channels=3, + out_channels=_make_divisible(in_channels * self.scale), + kernel_size=3, + stride=2, + padding=1, + groups=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=dict(type='HSwish'), + ) + + self.blocks = nn.ModuleList() + for i, (k, exp, c, se, act, s) in enumerate(cfgs): + self.blocks.append( + ResidualUnit( + in_channel=_make_divisible(in_channels * self.scale), + mid_channel=_make_divisible(self.scale * exp), + out_channel=_make_divisible(self.scale * c), + kernel_size=k, + stride=s, + use_se=se, + act=act, + dilation=1, + )) + in_channels = _make_divisible(self.scale * c) + + def forward(self, x): + if self.stem: + x = self.conv(x) + for i, block in enumerate(self.blocks): + x = block(x) + + return x + + +class ResidualUnit(nn.Module): + """The Residual module. + + Args: + in_channel (int, optional): The channels of input feature. + mid_channel (int, optional): The channels of middle process. + out_channel (int, optional): The channels of output feature. + kernel_size (int, optional): The size of the convolving kernel. + stride (int, optional): The stride size. + use_se (bool, optional): if to use the SEModule. + act (string, optional): activation layer. + dilation (int, optional): The dilation size. + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN', requires_grad=True). + """ + + def __init__( + self, + in_channel, + mid_channel, + out_channel, + kernel_size, + stride, + use_se, + act=None, + dilation=1, + norm_cfg=dict(type='BN'), + ): + super().__init__() + self.if_shortcut = stride == 1 and in_channel == out_channel + self.if_se = use_se + self.expand_conv = ConvModule( + in_channels=in_channel, + out_channels=mid_channel, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=dict(type=act) if act is not None else None, + ) + self.bottleneck_conv = ConvModule( + in_channels=mid_channel, + out_channels=mid_channel, + kernel_size=kernel_size, + stride=stride, + padding=int((kernel_size - 1) // 2) * dilation, + bias=False, + groups=mid_channel, + dilation=dilation, + norm_cfg=norm_cfg, + act_cfg=dict(type=act) if act is not None else None, + ) + if self.if_se: + self.mid_se = SEModule(mid_channel) + self.linear_conv = ConvModule( + in_channels=mid_channel, + out_channels=out_channel, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None, + ) + + def forward(self, x): + identity = x + x = self.expand_conv(x) + x = self.bottleneck_conv(x) + if self.if_se: + x = self.mid_se(x) + x = self.linear_conv(x) + if self.if_shortcut: + x = torch.add(identity, x) + return x + + +class SEModule(nn.Module): + """SE Module. + + Args: + channel (int, optional): The channels of input feature. + reduction (int, optional): The channel reduction rate. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='PReLU'). + """ + + def __init__(self, channel, reduction=4, act_cfg=dict(type='ReLU')): + super().__init__() + self.avg_pool = nn.AdaptiveAvgPool2d(1) + self.conv_act1 = ConvModule( + in_channels=channel, + out_channels=channel // reduction, + kernel_size=1, + norm_cfg=None, + act_cfg=act_cfg, + ) + + self.conv_act2 = ConvModule( + in_channels=channel // reduction, + out_channels=channel, + kernel_size=1, + norm_cfg=None, + act_cfg=dict(type='Hardsigmoid', slope=0.2, offset=0.5), + ) + + def forward(self, x): + identity = x + x = self.avg_pool(x) + x = self.conv_act1(x) + x = self.conv_act2(x) + return torch.mul(identity, x) + + +class BasicLayer(nn.Module): + """The transformer basic layer. + + Args: + block_num (int): the block nums of the transformer basic layer. + embedding_dim (int): The feature dimension. + key_dim (int): the key dim. + num_heads (int): Parallel attention heads. + mlp_ratio (float): the mlp ratio. + attn_ratio (float): the attention ratio. + drop (float): Probability of an element to be zeroed + after the feed forward layer.Default: 0.0. + attn_drop (float): The drop out rate for attention layer. + Default: 0.0. + drop_path (float): stochastic depth rate. Default 0.0. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='PReLU'). + stride_attention (bool, optional): whether to stride attention in + each attention layer. + """ + + def __init__( + self, + block_num, + embedding_dim, + key_dim, + num_heads, + mlp_ratio=4.0, + attn_ratio=2.0, + drop=0.0, + attn_drop=0.0, + drop_path=None, + act_cfg=None, + stride_attention=None, + ): + super().__init__() + self.block_num = block_num + + self.transformer_blocks = nn.ModuleList() + for i in range(self.block_num): + self.transformer_blocks.append( + Block( + embedding_dim, + key_dim=key_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + attn_ratio=attn_ratio, + drop=drop, + drop_path=drop_path[i] + if isinstance(drop_path, list) else drop_path, + act_cfg=act_cfg, + stride_attention=stride_attention, + )) + + def forward(self, x): + for i in range(self.block_num): + x = self.transformer_blocks[i](x) + return x + + +class Block(nn.Module): + """the block of the transformer basic layer. + + Args: + dim (int): The feature dimension. + key_dim (int): The key dimension. + num_heads (int): Parallel attention heads. + mlp_ratio (float): the mlp ratio. + attn_ratio (float): the attention ratio. + drop (float): Probability of an element to be zeroed + after the feed forward layer.Default: 0.0. + drop_path (float): stochastic depth rate. Default 0.0. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='PReLU'). + stride_attention (bool, optional): whether to stride attention in + each attention layer. + """ + + def __init__( + self, + dim, + key_dim, + num_heads, + mlp_ratio=4.0, + attn_ratio=2.0, + drop=0.0, + drop_path=0.0, + act_cfg=None, + stride_attention=None, + ): + super().__init__() + self.dim = dim + self.num_heads = num_heads + self.mlp_ratio = mlp_ratio + self.attn = SeaAttention( + dim, + key_dim=key_dim, + num_heads=num_heads, + attn_ratio=attn_ratio, + act_cfg=act_cfg, + stride_attention=stride_attention, + ) + self.drop_path = ( + build_dropout(dict(type='DropPath', drop_prob=drop_path)) + if drop_path > 0.0 else nn.Identity()) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp = MLP( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_cfg=act_cfg, + drop=drop, + ) + + def forward(self, x1): + x1 = x1 + self.drop_path(self.attn(x1)) + x1 = x1 + self.drop_path(self.mlp(x1)) + + return x1 + + +class SqueezeAxialPositionalEmbedding(nn.Module): + """the Squeeze Axial Positional Embedding. + + Args: + dim (int): The feature dimension. + shape (int): The patch size. + """ + + def __init__(self, dim, shape): + super().__init__() + self.pos_embed = nn.init.normal_( + nn.Parameter(torch.zeros(1, dim, shape))) + + def forward(self, x): + B, C, N = x.shape + x = x + F.interpolate( + self.pos_embed, size=(N, ), mode='linear', align_corners=False) + return x + + +class SeaAttention(nn.Module): + """The sea attention. + + Args: + dim (int): The feature dimension. + key_dim (int): The key dimension. + num_heads (int): number of attention heads. + attn_ratio (float): the attention ratio. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='PReLU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='LN') + stride_attention (bool, optional): whether to stride attention in + each attention layer. + """ + + def __init__( + self, + dim, + key_dim, + num_heads, + attn_ratio=4.0, + act_cfg=None, + norm_cfg=dict(type='BN'), + stride_attention=False, + ): + + super().__init__() + self.num_heads = num_heads + self.scale = key_dim**-0.5 + self.nh_kd = nh_kd = key_dim * num_heads + self.d = int(attn_ratio * key_dim) + self.dh = int(attn_ratio * key_dim) * num_heads + self.attn_ratio = attn_ratio + + self.to_q = ConvModule( + dim, nh_kd, 1, bias=False, norm_cfg=norm_cfg, act_cfg=None) + self.to_k = ConvModule( + dim, nh_kd, 1, bias=False, norm_cfg=norm_cfg, act_cfg=None) + + self.to_v = ConvModule( + dim, self.dh, 1, bias=False, norm_cfg=norm_cfg, act_cfg=None) + self.stride_attention = stride_attention + if self.stride_attention: + self.stride_conv = ConvModule( + dim, + dim, + kernel_size=3, + stride=2, + padding=1, + bias=True, + groups=dim, + norm_cfg=norm_cfg, + act_cfg=None, + ) + + self.proj = ConvModule( + self.dh, + dim, + 1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + order=('act', 'conv', 'norm'), + ) + self.proj_encode_row = ConvModule( + self.dh, + self.dh, + 1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + order=('act', 'conv', 'norm'), + ) + self.pos_emb_rowq = SqueezeAxialPositionalEmbedding(nh_kd, 16) + self.pos_emb_rowk = SqueezeAxialPositionalEmbedding(nh_kd, 16) + self.proj_encode_column = ConvModule( + self.dh, + self.dh, + 1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + order=('act', 'conv', 'norm'), + ) + self.pos_emb_columnq = SqueezeAxialPositionalEmbedding(nh_kd, 16) + self.pos_emb_columnk = SqueezeAxialPositionalEmbedding(nh_kd, 16) + self.dwconv = ConvModule( + 2 * self.dh, + 2 * self.dh, + 3, + padding=1, + groups=2 * self.dh, + bias=False, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + ) + self.pwconv = ConvModule( + 2 * self.dh, dim, 1, bias=False, norm_cfg=norm_cfg, act_cfg=None) + self.sigmoid = build_activation_layer(dict(type='HSigmoid')) + + def forward(self, x): + B, C, H_ori, W_ori = x.shape + if self.stride_attention: + x = self.stride_conv(x) + B, C, H, W = x.shape + + q = self.to_q(x) # [B, nhead*dim, H, W] + k = self.to_k(x) + v = self.to_v(x) + + qkv = torch.cat([q, k, v], dim=1) + qkv = self.dwconv(qkv) + qkv = self.pwconv(qkv) + + qrow = (self.pos_emb_rowq(q.mean(-1)).reshape( + [B, self.num_heads, -1, H]).permute( + (0, 1, 3, 2))) # [B, nhead, H, dim] + krow = self.pos_emb_rowk(k.mean(-1)).reshape( + [B, self.num_heads, -1, H]) # [B, nhead, dim, H] + vrow = (v.mean(-1).reshape([B, self.num_heads, -1, + H]).permute([0, 1, 3, 2]) + ) # [B, nhead, H, dim*attn_ratio] + + attn_row = torch.matmul(qrow, krow) * self.scale # [B, nhead, H, H] + attn_row = nn.functional.softmax(attn_row, dim=-1) + + xx_row = torch.matmul(attn_row, vrow) # [B, nhead, H, dim*attn_ratio] + xx_row = self.proj_encode_row( + xx_row.permute([0, 1, 3, 2]).reshape([B, self.dh, H, 1])) + + # squeeze column + qcolumn = ( + self.pos_emb_columnq(q.mean(-2)).reshape( + [B, self.num_heads, -1, W]).permute([0, 1, 3, 2])) + kcolumn = self.pos_emb_columnk(k.mean(-2)).reshape( + [B, self.num_heads, -1, W]) + vcolumn = ( + torch.mean(v, -2).reshape([B, self.num_heads, -1, + W]).permute([0, 1, 3, 2])) + + attn_column = torch.matmul(qcolumn, kcolumn) * self.scale + attn_column = nn.functional.softmax(attn_column, dim=-1) + + xx_column = torch.matmul(attn_column, vcolumn) # B nH W C + xx_column = self.proj_encode_column( + xx_column.permute([0, 1, 3, 2]).reshape([B, self.dh, 1, W])) + + xx = torch.add(xx_row, xx_column) # [B, self.dh, H, W] + xx = torch.add(v, xx) + + xx = self.proj(xx) + xx = self.sigmoid(xx) * qkv + if self.stride_attention: + xx = F.interpolate(xx, size=(H_ori, W_ori), mode='bilinear') + + return xx + + +class MLP(nn.Module): + """the Multilayer Perceptron. + + Args: + in_features (int): the input feature. + hidden_features (int): the hidden feature. + out_features (int): the output feature. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='PReLU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN') + drop (float): Probability of an element to be zeroed. + Default 0.0 + """ + + def __init__( + self, + in_features, + hidden_features=None, + out_features=None, + act_cfg=None, + norm_cfg=dict(type='BN'), + drop=0.0, + ): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = ConvModule( + in_features, + hidden_features, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None, + ) + self.dwconv = ConvModule( + hidden_features, + hidden_features, + kernel_size=3, + padding=1, + groups=hidden_features, + norm_cfg=None, + act_cfg=act_cfg, + ) + + self.fc2 = ConvModule( + hidden_features, + out_features, + 1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None, + ) + self.drop = build_dropout(dict(type='Dropout', drop_prob=drop)) + + def forward(self, x): + x = self.fc1(x) + x = self.dwconv(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class FusionBlock(nn.Module): + """The feature fusion block. + + Args: + in_channel (int): the input channel. + out_channel (int): the output channel. + embed_dim (int): embedding dimension. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN') + """ + + def __init__( + self, + in_channel, + out_channel, + embed_dim, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + ) -> None: + super().__init__() + self.local_embedding = ConvModule( + in_channels=in_channel, + out_channels=embed_dim, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None, + ) + + self.global_act = ConvModule( + in_channels=out_channel, + out_channels=embed_dim, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=act_cfg if act_cfg is not None else None, + ) + + def forward(self, x_l, x_g): + """ + x_g: global features + x_l: local features + """ + B, C, H, W = x_l.shape + + local_feat = self.local_embedding(x_l) + global_act = self.global_act(x_g) + sig_act = F.interpolate( + global_act, size=(H, W), mode='bilinear', align_corners=False) + + out = local_feat * sig_act + + return out + + +class InjectionMultiSumallmultiallsum(nn.Module): + """the Aggregate Attention Module. + + Args: + in_channels (tuple): the input channel. + out_channels (int): the output channel. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN') + """ + + def __init__( + self, + in_channels=(64, 128, 256, 384), + out_channels=256, + act_cfg=dict(type='Sigmoid'), + norm_cfg=dict(type='BN'), + ): + super().__init__() + self.embedding_list = nn.ModuleList() + self.act_embedding_list = nn.ModuleList() + self.act_list = nn.ModuleList() + for i in range(len(in_channels)): + self.embedding_list.append( + ConvModule( + in_channels=in_channels[i], + out_channels=out_channels, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None, + )) + self.act_embedding_list.append( + ConvModule( + in_channels=in_channels[i], + out_channels=out_channels, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + )) + + def forward(self, inputs): # x_x8, x_x16, x_x32, x_x64 + low_feat1 = F.interpolate(inputs[0], scale_factor=0.5, mode='bilinear') + low_feat1_act = self.act_embedding_list[0](low_feat1) + low_feat1 = self.embedding_list[0](low_feat1) + + low_feat2 = F.interpolate( + inputs[1], size=low_feat1.shape[-2:], mode='bilinear') + low_feat2_act = self.act_embedding_list[1](low_feat2) # x16 + low_feat2 = self.embedding_list[1](low_feat2) + + high_feat_act = F.interpolate( + self.act_embedding_list[2](inputs[2]), + size=low_feat2.shape[2:], + mode='bilinear', + ) + high_feat = F.interpolate( + self.embedding_list[2](inputs[2]), + size=low_feat2.shape[2:], + mode='bilinear') + + res = ( + low_feat1_act * low_feat2_act * high_feat_act * + (low_feat1 + low_feat2) + high_feat) + + return res + + +class InjectionMultiSumallmultiallsumSimpx8(nn.Module): + """the Aggregate Attention Module. + + Args: + in_channels (tuple): the input channel. + out_channels (int): the output channel. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN') + """ + + def __init__( + self, + in_channels=(64, 128, 256, 384), + out_channels=256, + act_cfg=dict(type='Sigmoid'), + norm_cfg=dict(type='BN'), + ): + super().__init__() + self.embedding_list = nn.ModuleList() + self.act_embedding_list = nn.ModuleList() + self.act_list = nn.ModuleList() + for i in range(len(in_channels)): + if i != 1: + self.embedding_list.append( + ConvModule( + in_channels=in_channels[i], + out_channels=out_channels, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=None, + )) + if i != 0: + self.act_embedding_list.append( + ConvModule( + in_channels=in_channels[i], + out_channels=out_channels, + kernel_size=1, + bias=False, + norm_cfg=norm_cfg, + act_cfg=act_cfg, + )) + + def forward(self, inputs): + # x_x8, x_x16, x_x32 + low_feat1 = self.embedding_list[0](inputs[0]) + + low_feat2 = F.interpolate( + inputs[1], size=low_feat1.shape[-2:], mode='bilinear') + low_feat2_act = self.act_embedding_list[0](low_feat2) + + high_feat_act = F.interpolate( + self.act_embedding_list[1](inputs[2]), + size=low_feat2.shape[2:], + mode='bilinear', + ) + high_feat = F.interpolate( + self.embedding_list[1](inputs[2]), + size=low_feat2.shape[2:], + mode='bilinear') + + res = low_feat2_act * high_feat_act * low_feat1 + high_feat + + return res + + +def _make_divisible(v, divisor=8, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +@MODELS.register_module() +class Hardsigmoid(nn.Module): + """the hardsigmoid activation. + + Args: + slope (float, optional): The slope of hardsigmoid function. + Default is 0.1666667. + offset (float, optional): The offset of hardsigmoid function. + Default is 0.5. + inplace (bool): can optionally do the operation in-place. + Default: ``False`` + """ + + def __init__(self, slope=0.1666667, offset=0.5, inplace=False): + super().__init__() + self.slope = slope + self.offset = offset + + def forward(self, x): + return (x * self.slope + self.offset).clamp(0, 1) diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/datasets/ade20k.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/datasets/ade20k.py new file mode 100644 index 0000000..48340d1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/datasets/ade20k.py @@ -0,0 +1,68 @@ +# dataset settings +dataset_type = 'ADE20KDataset' +data_root = 'data/ade/ADEChallengeData2016' +crop_size = (512, 512) +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations', reduce_zero_label=True), + dict( + type='RandomResize', + scale=(2048, 512), + ratio_range=(0.5, 2.0), + keep_ratio=True), + dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75), + dict(type='RandomFlip', prob=0.5), + dict(type='PhotoMetricDistortion'), + dict(type='PackSegInputs') +] +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75] +tta_pipeline = [ + dict(type='LoadImageFromFile', backend_args=None), + dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in img_ratios + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')] + ]) +] +train_dataloader = dict( + batch_size=4, + num_workers=4, + persistent_workers=True, + sampler=dict(type='InfiniteSampler', shuffle=True), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/training', seg_map_path='annotations/training'), + pipeline=train_pipeline)) +val_dataloader = dict( + batch_size=1, + num_workers=4, + persistent_workers=True, + sampler=dict(type='DefaultSampler', shuffle=False), + dataset=dict( + type=dataset_type, + data_root=data_root, + data_prefix=dict( + img_path='images/validation', + seg_map_path='annotations/validation'), + pipeline=test_pipeline)) +test_dataloader = val_dataloader + +val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU']) +test_evaluator = val_evaluator diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/default_runtime.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/default_runtime.py new file mode 100644 index 0000000..272b4d2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/default_runtime.py @@ -0,0 +1,15 @@ +default_scope = 'mmseg' +env_cfg = dict( + cudnn_benchmark=True, + mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0), + dist_cfg=dict(backend='nccl'), +) +vis_backends = [dict(type='LocalVisBackend')] +visualizer = dict( + type='SegLocalVisualizer', vis_backends=vis_backends, name='visualizer') +log_processor = dict(by_epoch=False) +log_level = 'INFO' +load_from = None +resume = False + +tta_model = dict(type='SegTTAModel') diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/models/pp_mobile.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/models/pp_mobile.py new file mode 100644 index 0000000..0c76956 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/models/pp_mobile.py @@ -0,0 +1,47 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255) + +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + # pretrained='open-mmlab://resnet50_v1c', + backbone=dict( + type='StrideFormer', + mobileV3_cfg=[ + # k t c, s + [[3, 16, 16, True, 'ReLU', 1], [3, 64, 32, False, 'ReLU', 2], + [3, 96, 32, False, 'ReLU', 1]], # cfg1 + [[5, 128, 64, True, 'HSwish', 2], [5, 240, 64, True, 'HSwish', + 1]], # cfg2 + [[5, 384, 128, True, 'HSwish', 2], + [5, 384, 128, True, 'HSwish', 1]], # cfg3 + [[5, 768, 192, True, 'HSwish', 2], + [5, 768, 192, True, 'HSwish', 1]], # cfg4 + ], + channels=[16, 32, 64, 128, 192], + depths=[3, 3], + embed_dims=[128, 192], + num_heads=8, + inj_type='AAMSx8', + out_feat_chs=[64, 128, 192], + act_cfg=dict(type='ReLU6'), + ), + decode_head=dict( + type='PPMobileSegHead', + num_classes=150, + in_channels=256, + dropout_ratio=0.1, + use_dw=True, + act_cfg=dict(type='ReLU'), + align_corners=False), + + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/schedules/schedule_80k.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/schedules/schedule_80k.py new file mode 100644 index 0000000..0dcd6c4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/_base_/schedules/schedule_80k.py @@ -0,0 +1,24 @@ +# optimizer +optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005) +optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + eta_min=1e-4, + power=0.9, + begin=0, + end=80000, + by_epoch=False) +] +# training schedule for 80k +train_cfg = dict(type='IterBasedTrainLoop', max_iters=80000, val_interval=8000) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=8000), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_base.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_base.py new file mode 100644 index 0000000..4b68a92 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_base.py @@ -0,0 +1,18 @@ +_base_ = [ + '../_base_/models/pp_mobile.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +# the custom import path is determined by your workspace path (i.e., where you run the command from) # noqa +custom_imports = dict( + imports=[ + 'projects.pp_mobileseg.backbones', 'projects.pp_mobileseg.decode_head' + ], + allow_failed_imports=False) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pp_mobileseg/pp_mobileseg_mobilenetv3_3rdparty-base-ed0be681.pth' # noqa +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size, test_cfg=dict(size_divisor=32)) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict(init_cfg=dict(type='Pretrained', checkpoint=checkpoint)), + decode_head=dict(num_classes=150, upsample='intepolate')) diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_tiny.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_tiny.py new file mode 100644 index 0000000..b78869e --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/configs/pp_mobileseg/pp_mobileseg_mobilenetv3_2x16_80k_ade20k_512x512_tiny.py @@ -0,0 +1,45 @@ +_base_ = [ + '../_base_/models/pp_mobile.py', '../_base_/datasets/ade20k.py', + '../_base_/default_runtime.py', '../_base_/schedules/schedule_80k.py' +] +# the custom import path is determined by your workspace path (i.e., where you run the command from) # noqa +custom_imports = dict( + imports=[ + 'projects.pp_mobileseg.backbones', 'projects.pp_mobileseg.decode_head' + ], + allow_failed_imports=False) +checkpoint = 'https://download.openmmlab.com/mmsegmentation/v0.5/pp_mobileseg/pp_mobileseg_mobilenetv3_3rdparty-tiny-e4b35e96.pth' # noqa +crop_size = (512, 512) +data_preprocessor = dict(size=crop_size, test_cfg=dict(size_divisor=32)) +norm_cfg = dict(type='SyncBN', requires_grad=True) +model = dict( + data_preprocessor=data_preprocessor, + backbone=dict( + init_cfg=dict(type='Pretrained', checkpoint=checkpoint), + type='StrideFormer', + mobileV3_cfg=[ + # k t c, s + [[3, 16, 16, True, 'ReLU', 1], [3, 64, 32, False, 'ReLU', 2], + [3, 48, 24, False, 'ReLU', 1]], # cfg1 + [[5, 96, 32, True, 'HSwish', 2], [5, 96, 32, True, 'HSwish', + 1]], # cfg2 + [[5, 160, 64, True, 'HSwish', 2], [5, 160, 64, True, 'HSwish', + 1]], # cfg3 + [[3, 384, 128, True, 'HSwish', 2], + [3, 384, 128, True, 'HSwish', 1]], # cfg4 + ], + channels=[16, 24, 32, 64, 128], + depths=[2, 2], + embed_dims=[64, 128], + num_heads=4, + inj_type='AAM', + out_feat_chs=[32, 64, 128], + act_cfg=dict(type='ReLU6'), + ), + decode_head=dict( + num_classes=150, + in_channels=256, + use_dw=True, + act_cfg=dict(type='ReLU'), + upsample='intepolate'), +) diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/decode_head/__init__.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/decode_head/__init__.py new file mode 100644 index 0000000..6f71b78 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/decode_head/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .pp_mobileseg_head import PPMobileSegHead + +__all__ = [ + 'PPMobileSegHead', +] diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/decode_head/pp_mobileseg_head.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/decode_head/pp_mobileseg_head.py new file mode 100644 index 0000000..243f026 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/decode_head/pp_mobileseg_head.py @@ -0,0 +1,94 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import List + +import torch +import torch.nn as nn +import torch.nn.functional as F +from mmcv.cnn import ConvModule, build_conv_layer +from torch import Tensor + +from mmseg.registry import MODELS + + +@MODELS.register_module() +class PPMobileSegHead(nn.Module): + """the segmentation head. + + Args: + num_classes (int): the classes num. + in_channels (int): the input channels. + use_dw (bool): if to use deepwith convolution. + dropout_ratio (float): Probability of an element to be zeroed. + Default 0.0。 + align_corners (bool, optional): Geometrically, we consider the pixels + of the input and output as squares rather than points. + upsample (str): the upsample method. + out_channels (int): the output channel. + conv_cfg (dict): Config dict for convolution layer. + act_cfg (dict): Config dict for activation layer. + Default: dict(type='ReLU'). + norm_cfg (dict): Config dict for normalization layer. + Default: dict(type='BN'). + """ + + def __init__(self, + num_classes, + in_channels, + use_dw=True, + dropout_ratio=0.1, + align_corners=False, + upsample='intepolate', + out_channels=None, + conv_cfg=dict(type='Conv'), + act_cfg=dict(type='ReLU'), + norm_cfg=dict(type='BN')): + + super().__init__() + self.align_corners = align_corners + self.last_channels = in_channels + self.upsample = upsample + self.num_classes = num_classes + self.out_channels = out_channels + self.linear_fuse = ConvModule( + in_channels=self.last_channels, + out_channels=self.last_channels, + kernel_size=1, + bias=False, + groups=self.last_channels if use_dw else 1, + norm_cfg=norm_cfg, + act_cfg=act_cfg) + self.dropout = nn.Dropout2d(dropout_ratio) + self.conv_seg = build_conv_layer( + conv_cfg, self.last_channels, self.num_classes, kernel_size=1) + + def forward(self, x): + x, x_hw = x[0], x[1] + x = self.linear_fuse(x) + x = self.dropout(x) + x = self.conv_seg(x) + if self.upsample == 'intepolate' or self.training or \ + self.num_classes < 30: + x = F.interpolate( + x, x_hw, mode='bilinear', align_corners=self.align_corners) + elif self.upsample == 'vim': + labelset = torch.unique(torch.argmax(x, 1)) + x = torch.gather(x, 1, labelset) + x = F.interpolate( + x, x_hw, mode='bilinear', align_corners=self.align_corners) + + pred = torch.argmax(x, 1) + pred_retrieve = torch.zeros(pred.shape, dtype=torch.int32) + for i, val in enumerate(labelset): + pred_retrieve[pred == i] = labelset[i].cast('int32') + + x = pred_retrieve + else: + raise NotImplementedError(self.upsample, ' is not implemented') + + return [x] + + def predict(self, inputs, batch_img_metas: List[dict], test_cfg, + **kwargs) -> List[Tensor]: + """Forward function for testing, only ``pam_cam`` is used.""" + seg_logits = self.forward(inputs)[0] + return seg_logits diff --git a/Seg_All_In_One_MMSeg/projects/pp_mobileseg/inference_onnx.py b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/inference_onnx.py new file mode 100644 index 0000000..139d1b1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/pp_mobileseg/inference_onnx.py @@ -0,0 +1,203 @@ +import argparse +import time +from typing import List, Tuple + +import cv2 +import loguru +import numpy as np +import onnxruntime as ort + +logger = loguru.logger + + +def parse_args(): + parser = argparse.ArgumentParser( + description='PP_Mobileseg ONNX inference demo.') + parser.add_argument('onnx_file', help='ONNX file path') + parser.add_argument('image_file', help='Input image file path') + parser.add_argument( + '--input-size', + type=int, + nargs='+', + default=[512, 512], + help='input image size') + parser.add_argument( + '--device', help='device type for inference', default='cpu') + parser.add_argument( + '--save-path', + help='path to save the output image', + default='output.jpg') + args = parser.parse_args() + return args + + +def preprocess( + img: np.ndarray, input_size: Tuple[int, int] = (512, 512) +) -> Tuple[np.ndarray, np.ndarray]: + """Preprocess image for inference.""" + img_shape = img.shape[:2] + # Resize + resized_img = cv2.resize(img, input_size) + + # Normalize + mean = np.array([123.575, 116.28, 103.53], dtype=np.float32) + std = np.array([58.395, 57.12, 57.375], dtype=np.float32) + resized_img = (resized_img - mean) / std + + return resized_img, img_shape + + +def build_session(onnx_file: str, device: str = 'cpu') -> ort.InferenceSession: + """Build onnxruntime session. + + Args: + onnx_file (str): ONNX file path. + device (str): Device type for inference. + + Returns: + sess (ort.InferenceSession): ONNXRuntime session. + """ + providers = ['CPUExecutionProvider' + ] if device == 'cpu' else ['CUDAExecutionProvider'] + sess = ort.InferenceSession(path_or_bytes=onnx_file, providers=providers) + + return sess + + +def inference(sess: ort.InferenceSession, img: np.ndarray) -> np.ndarray: + """Inference RTMPose model. + + Args: + sess (ort.InferenceSession): ONNXRuntime session. + img (np.ndarray): Input image in shape. + + Returns: + outputs (np.ndarray): Output of RTMPose model. + """ + # build input + input_img = [img.transpose(2, 0, 1).astype(np.float32)] + + # build output + sess_input = {sess.get_inputs()[0].name: input_img} + sess_output = [] + for out in sess.get_outputs(): + sess_output.append(out.name) + + # inference + outputs = sess.run(output_names=sess_output, input_feed=sess_input) + + return outputs + + +def postprocess(outputs: List[np.ndarray], + origin_shape: Tuple[int, int]) -> np.ndarray: + """Postprocess outputs of PP_Mobileseg model. + + Args: + outputs (List[np.ndarray]): Outputs of PP_Mobileseg model. + origin_shape (Tuple[int, int]): Input size of PP_Mobileseg model. + + Returns: + seg_map (np.ndarray): Segmentation map. + """ + seg_map = outputs[0][0][0] + seg_map = cv2.resize(seg_map.astype(np.float32), origin_shape) + return seg_map + + +def visualize(img: np.ndarray, + seg_map: np.ndarray, + filename: str = 'output.jpg', + opacity: float = 0.8) -> np.ndarray: + assert 0.0 <= opacity <= 1.0, 'opacity should be in range [0, 1]' + palette = np.array(PALETTE) + color_seg = np.zeros((seg_map.shape[0], seg_map.shape[1], 3), + dtype=np.uint8) + for label, color in enumerate(palette): + color_seg[seg_map == label, :] = color + # convert to BGR + color_seg = color_seg[..., ::-1] + + img = img * (1 - opacity) + color_seg * opacity + cv2.imwrite(filename, img) + + return img + + +def main(): + args = parse_args() + logger.info('Start running model inference...') + + # read image from file + logger.info(f'1. Read image from file {args.image_file}...') + img = cv2.imread(args.image_file) + + # build onnx model + logger.info(f'2. Build onnx model from {args.onnx_file}...') + sess = build_session(args.onnx_file, args.device) + + # preprocess + logger.info('3. Preprocess image...') + model_input_size = tuple(args.input_size) + assert len(model_input_size) == 2 + resized_img, origin_shape = preprocess(img, model_input_size) + + # inference + logger.info('4. Inference...') + start = time.time() + outputs = inference(sess, resized_img) + logger.info(f'Inference time: {time.time() - start:.4f}s') + + # postprocess + logger.info('5. Postprocess...') + h, w = origin_shape + seg_map = postprocess(outputs, (w, h)) + + # visualize + logger.info('6. Visualize...') + visualize(img, seg_map, args.save_path) + + logger.info('Done...') + + +PALETTE = [[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50], + [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255], + [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7], + [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82], + [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3], + [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255], + [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220], + [255, 9, 92], [112, 9, 255], [8, 255, 214], [7, 255, 224], + [255, 184, 6], [10, 255, 71], [255, 41, 10], [7, 255, 255], + [224, 255, 8], [102, 8, 255], [255, 61, 6], [255, 194, 7], + [255, 122, 8], [0, 255, 20], [255, 8, 41], [255, 5, 153], + [6, 51, 255], [235, 12, 255], [160, 150, 20], [0, 163, 255], + [140, 140, 140], [250, 10, 15], [20, 255, 0], [31, 255, 0], + [255, 31, 0], [255, 224, 0], [153, 255, 0], [0, 0, 255], + [255, 71, 0], [0, 235, 255], [0, 173, 255], [31, 0, 255], + [11, 200, 200], [255, 82, 0], [0, 255, 245], [0, 61, 255], + [0, 255, 112], [0, 255, 133], [255, 0, 0], [255, 163, 0], + [255, 102, 0], [194, 255, 0], [0, 143, 255], [51, 255, 0], + [0, 82, 255], [0, 255, 41], [0, 255, 173], [10, 0, 255], + [173, 255, 0], [0, 255, 153], [255, 92, 0], [255, 0, 255], + [255, 0, 245], [255, 0, 102], [255, 173, 0], [255, 0, 20], + [255, 184, 184], [0, 31, 255], [0, 255, 61], [0, 71, 255], + [255, 0, 204], [0, 255, 194], [0, 255, 82], [0, 10, 255], + [0, 112, 255], [51, 0, 255], [0, 194, 255], [0, 122, 255], + [0, 255, 163], [255, 153, 0], [0, 255, 10], [255, 112, 0], + [143, 255, 0], [82, 0, 255], [163, 255, 0], [255, 235, 0], + [8, 184, 170], [133, 0, 255], [0, 255, 92], [184, 0, 255], + [255, 0, 31], [0, 184, 255], [0, 214, 255], [255, 0, 112], + [92, 255, 0], [0, 224, 255], [112, 224, 255], [70, 184, 160], + [163, 0, 255], [153, 0, 255], [71, 255, 0], [255, 0, 163], + [255, 204, 0], [255, 0, 143], [0, 255, 235], [133, 255, 0], + [255, 0, 235], [245, 0, 255], [255, 0, 122], [255, 245, 0], + [10, 190, 212], [214, 255, 0], [0, 204, 255], [20, 0, 255], + [255, 255, 0], [0, 153, 255], [0, 41, 255], [0, 255, 204], + [41, 0, 255], [41, 255, 0], [173, 0, 255], [0, 245, 255], + [71, 0, 255], [122, 0, 255], [0, 255, 184], [0, 92, 255], + [184, 255, 0], [0, 133, 255], [255, 214, 0], [25, 194, 194], + [102, 255, 0], [92, 0, 255]] + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/README.md b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/README.md new file mode 100644 index 0000000..f8077b8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/README.md @@ -0,0 +1,40 @@ +# Introducing the Segment Anything Model (SAM) Inference Demo! + +Welcome to the Segment Anything (SA) Inference Demo, a user-friendly implementation based on the original Segment Anything project. Our demo allows you to experience the power and versatility of the Segment Anything Model (SAM) through an easy-to-use API. + +With this inference demo, you can explore the capabilities of the Segment Anything Model and witness its effectiveness in various tasks and image distributions. For more information on the original project, dataset, and model, please visit the official website at https://segment-anything.com. + +### Prerequisites + +- Python 3.10 +- PyTorch 1.13 +- MMEngine >= v0.7.2 +- MMCV >= v2.0.0 + +### Installation + +We assume that you have already installed PyTorch. If not, please follow the instructions on the [PyTorch website](https://pytorch.org/). + +**1. Install MMEngine & MMCV** + +```shell +pip install openmim +mim install mmengine +mim install 'mmcv>=2.0.0' +``` + +**2. Install MMPretrain** + +```shell +pip install git+https://github.com/open-mmlab/mmpretrain.git@dev +``` + +**3. Install MMSegmentation** + +```shell +pip install mmsegmentation +``` + +### Usage + +Open the `sam_image_demo.ipynb` notebook and follow the instructions to run the demo. diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/__init__.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/__init__.py new file mode 100644 index 0000000..82b6b78 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/__init__.py @@ -0,0 +1,2 @@ +from .modeling import * # noqa +from .utils import * # noqa diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/__init__.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/__init__.py new file mode 100644 index 0000000..9892a6b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from .mask_decoder import MaskDecoder +from .prompt_encoder import PromptEncoder +from .sam import SAM +from .transformer import TwoWayTransformer + +__all__ = ['SAM', 'MaskDecoder', 'PromptEncoder', 'TwoWayTransformer'] diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/common.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/common.py new file mode 100644 index 0000000..d289276 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/common.py @@ -0,0 +1,45 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Type + +import torch +import torch.nn as nn + + +class MLPBlock(nn.Module): + + def __init__( + self, + embedding_dim: int, + mlp_dim: int, + act: Type[nn.Module] = nn.GELU, + ) -> None: + super().__init__() + self.lin1 = nn.Linear(embedding_dim, mlp_dim) + self.lin2 = nn.Linear(mlp_dim, embedding_dim) + self.act = act() + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.lin2(self.act(self.lin1(x))) + + +# From https://github.com/facebookresearch/detectron2/blob/main/detectron2/layers/batch_norm.py # noqa +# Itself from https://github.com/facebookresearch/ConvNeXt/blob/d1fa8f6fef0a165b27399986cc2bdacc92777e40/models/convnext.py#L119 # noqa +class LayerNorm2d(nn.Module): + + def __init__(self, num_channels: int, eps: float = 1e-6) -> None: + super().__init__() + self.weight = nn.Parameter(torch.ones(num_channels)) + self.bias = nn.Parameter(torch.zeros(num_channels)) + self.eps = eps + + def forward(self, x: torch.Tensor) -> torch.Tensor: + u = x.mean(1, keepdim=True) + s = (x - u).pow(2).mean(1, keepdim=True) + x = (x - u) / torch.sqrt(s + self.eps) + x = self.weight[:, None, None] * x + self.bias[:, None, None] + return x diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/mask_decoder.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/mask_decoder.py new file mode 100644 index 0000000..9ad616b --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/mask_decoder.py @@ -0,0 +1,196 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +# Borrowed from https://github.com/facebookresearch/segment-anything + +from typing import List, Tuple + +import torch +from torch import Tensor, nn +from torch.nn import functional as F + +from mmseg.registry import MODELS +from .common import LayerNorm2d + + +@MODELS.register_module() +class MaskDecoder(nn.Module): + + def __init__( + self, + *, + transformer_dim: int, + transformer: dict, + num_multimask_outputs: int = 3, + act_cfg: dict = dict(type='GELU'), + iou_head_depth: int = 3, + iou_head_hidden_dim: int = 256, + ) -> None: + """Predicts masks given an image and prompt embeddings, using a + tranformer architecture. + + Borrowed from https://github.com/facebookresearch/segment-anything + + Arguments: + transformer_dim (int): the channel dimension of the transformer + transformer (nn.Module): the transformer used to predict masks + num_multimask_outputs (int): the number of masks to predict + when disambiguating masks + activation (nn.Module): the type of activation to use when + upscaling masks + iou_head_depth (int): the depth of the MLP used to predict + mask quality + iou_head_hidden_dim (int): the hidden dimension of the MLP + used to predict mask quality + """ + super().__init__() + self.transformer_dim = transformer_dim + self.transformer = MODELS.build(transformer) + + self.num_multimask_outputs = num_multimask_outputs + + self.iou_token = nn.Embedding(1, transformer_dim) + self.num_mask_tokens = num_multimask_outputs + 1 + self.mask_tokens = nn.Embedding(self.num_mask_tokens, transformer_dim) + + activation = MODELS.build(act_cfg) + self.output_upscaling = nn.Sequential( + nn.ConvTranspose2d( + transformer_dim, transformer_dim // 4, kernel_size=2, + stride=2), + LayerNorm2d(transformer_dim // 4), + activation, + nn.ConvTranspose2d( + transformer_dim // 4, + transformer_dim // 8, + kernel_size=2, + stride=2), + activation, + ) + self.output_hypernetworks_mlps = nn.ModuleList([ + MLP(transformer_dim, transformer_dim, transformer_dim // 8, 3) + for i in range(self.num_mask_tokens) + ]) + + self.iou_prediction_head = MLP(transformer_dim, iou_head_hidden_dim, + self.num_mask_tokens, iou_head_depth) + + def forward( + self, + image_embeddings: Tensor, + image_pe: Tensor, + sparse_prompt_embeddings: Tensor, + dense_prompt_embeddings: Tensor, + multimask_output: bool, + ) -> Tuple[Tensor, Tensor]: + """Predict masks given image and prompt embeddings. + + Borrowed from https://github.com/facebookresearch/segment-anything + + Arguments: + image_embeddings (Tensor): the embeddings from the image encoder + image_pe (Tensor): positional encoding with the shape of + image_embeddings + sparse_prompt_embeddings (Tensor): the embeddings of + the points and boxes + dense_prompt_embeddings (Tensor): the embeddings of the mask inputs + multimask_output (bool): Whether to return multiple masks or a single + mask. + + Returns: + Tensor: batched predicted masks + Tensor: batched predictions of mask quality + """ + masks, iou_pred = self.predict_masks( + image_embeddings=image_embeddings, + image_pe=image_pe, + sparse_prompt_embeddings=sparse_prompt_embeddings, + dense_prompt_embeddings=dense_prompt_embeddings, + ) + + # Select the correct mask or masks for output + if multimask_output: + mask_slice = slice(1, None) + else: + mask_slice = slice(0, 1) + masks = masks[:, mask_slice, :, :] + iou_pred = iou_pred[:, mask_slice] + + # Prepare output + return masks, iou_pred + + def predict_masks( + self, + image_embeddings: Tensor, + image_pe: Tensor, + sparse_prompt_embeddings: Tensor, + dense_prompt_embeddings: Tensor, + ) -> Tuple[Tensor, Tensor]: + """Predicts masks. + + See 'forward' for more details. + """ + # Concatenate output tokens + output_tokens = torch.cat( + [self.iou_token.weight, self.mask_tokens.weight], dim=0) + output_tokens = output_tokens.unsqueeze(0).expand( + sparse_prompt_embeddings.size(0), -1, -1) + tokens = torch.cat((output_tokens, sparse_prompt_embeddings), dim=1) + + # Expand per-image data in batch direction to be per-mask + src = torch.repeat_interleave(image_embeddings, tokens.shape[0], dim=0) + src = src + dense_prompt_embeddings + pos_src = torch.repeat_interleave(image_pe, tokens.shape[0], dim=0) + b, c, h, w = src.shape + + # Run the transformer + hs, src = self.transformer(src, pos_src, tokens) + iou_token_out = hs[:, 0, :] + mask_tokens_out = hs[:, 1:(1 + self.num_mask_tokens), :] + + # Upscale mask embeddings and predict masks using the mask tokens + src = src.transpose(1, 2).view(b, c, h, w) + upscaled_embedding = self.output_upscaling(src) + hyper_in_list: List[Tensor] = [] + for i in range(self.num_mask_tokens): + hyper_in_list.append(self.output_hypernetworks_mlps[i]( + mask_tokens_out[:, i, :])) + hyper_in = torch.stack(hyper_in_list, dim=1) + b, c, h, w = upscaled_embedding.shape + masks = (hyper_in @ upscaled_embedding.view(b, c, h * w)).view( + b, -1, h, w) + + # Generate mask quality predictions + iou_pred = self.iou_prediction_head(iou_token_out) + + return masks, iou_pred + + +# Lightly adapted from +# https://github.com/facebookresearch/MaskFormer/blob/main/mask_former/modeling/transformer/transformer_predictor.py # noqa +class MLP(nn.Module): + + def __init__( + self, + input_dim: int, + hidden_dim: int, + output_dim: int, + num_layers: int, + sigmoid_output: bool = False, + ) -> None: + super().__init__() + self.num_layers = num_layers + h = [hidden_dim] * (num_layers - 1) + self.layers = nn.ModuleList( + nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim])) + self.sigmoid_output = sigmoid_output + + def forward(self, x): + for i, layer in enumerate(self.layers): + x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x) + if self.sigmoid_output: + x = F.sigmoid(x) + return x diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/prompt_encoder.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/prompt_encoder.py new file mode 100644 index 0000000..6b7c083 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/prompt_encoder.py @@ -0,0 +1,227 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +# Borrowed from https://github.com/facebookresearch/segment-anything + +from typing import Any, Optional, Tuple, Type + +import numpy as np +import torch +from torch import nn + +from mmseg.registry import MODELS +from .common import LayerNorm2d + + +@MODELS.register_module() +class PromptEncoder(nn.Module): + + def __init__( + self, + embed_dim: int, + image_embedding_size: Tuple[int, int], + input_image_size: Tuple[int, int], + mask_in_chans: int, + activation: Type[nn.Module] = nn.GELU, + ) -> None: + """Encodes prompts for input to SAM's mask decoder. + + Arguments: + embed_dim (int): The prompts' embedding dimension + image_embedding_size (tuple(int, int)): The spatial size of the + image embedding, as (H, W). + input_image_size (int): The padded size of the image as input + to the image encoder, as (H, W). + mask_in_chans (int): The number of hidden channels used for + encoding input masks. + activation (nn.Module): The activation to use when encoding + input masks. + """ + super().__init__() + self.embed_dim = embed_dim + self.input_image_size = input_image_size + self.image_embedding_size = image_embedding_size + self.pe_layer = PositionEmbeddingRandom(embed_dim // 2) + + self.num_point_embeddings: int = 4 # pos/neg point + 2 box corners + point_embeddings = [ + nn.Embedding(1, embed_dim) + for i in range(self.num_point_embeddings) + ] + self.point_embeddings = nn.ModuleList(point_embeddings) + self.not_a_point_embed = nn.Embedding(1, embed_dim) + + self.mask_input_size = (4 * image_embedding_size[0], + 4 * image_embedding_size[1]) + self.mask_downscaling = nn.Sequential( + nn.Conv2d(1, mask_in_chans // 4, kernel_size=2, stride=2), + LayerNorm2d(mask_in_chans // 4), + activation(), + nn.Conv2d( + mask_in_chans // 4, mask_in_chans, kernel_size=2, stride=2), + LayerNorm2d(mask_in_chans), + activation(), + nn.Conv2d(mask_in_chans, embed_dim, kernel_size=1), + ) + self.no_mask_embed = nn.Embedding(1, embed_dim) + + def get_dense_pe(self) -> torch.Tensor: + """Returns the positional encoding used to encode point prompts, + applied to a dense set of points the shape of the image encoding. + + Returns: + torch.Tensor: Positional encoding with shape + 1x(embed_dim)x(embedding_h)x(embedding_w) + """ + return self.pe_layer(self.image_embedding_size).unsqueeze(0) + + def _embed_points( + self, + points: torch.Tensor, + labels: torch.Tensor, + pad: bool, + ) -> torch.Tensor: + """Embeds point prompts.""" + points = points + 0.5 # Shift to center of pixel + if pad: + padding_point = torch.zeros((points.shape[0], 1, 2), + device=points.device) + padding_label = -torch.ones( + (labels.shape[0], 1), device=labels.device) + points = torch.cat([points, padding_point], dim=1) + labels = torch.cat([labels, padding_label], dim=1) + point_embedding = self.pe_layer.forward_with_coords( + points, self.input_image_size) + point_embedding[labels == -1] = 0.0 + point_embedding[labels == -1] += self.not_a_point_embed.weight + point_embedding[labels == 0] += self.point_embeddings[0].weight + point_embedding[labels == 1] += self.point_embeddings[1].weight + return point_embedding + + def _embed_boxes(self, boxes: torch.Tensor) -> torch.Tensor: + """Embeds box prompts.""" + boxes = boxes + 0.5 # Shift to center of pixel + coords = boxes.reshape(-1, 2, 2) + corner_embedding = self.pe_layer.forward_with_coords( + coords, self.input_image_size) + corner_embedding[:, 0, :] += self.point_embeddings[2].weight + corner_embedding[:, 1, :] += self.point_embeddings[3].weight + return corner_embedding + + def _embed_masks(self, masks: torch.Tensor) -> torch.Tensor: + """Embeds mask inputs.""" + mask_embedding = self.mask_downscaling(masks) + return mask_embedding + + def _get_batch_size( + self, + points: Optional[Tuple[torch.Tensor, torch.Tensor]], + boxes: Optional[torch.Tensor], + masks: Optional[torch.Tensor], + ) -> int: + """Gets the batch size of the output given the batch size of the input + prompts.""" + if points is not None: + return points[0].shape[0] + elif boxes is not None: + return boxes.shape[0] + elif masks is not None: + return masks.shape[0] + else: + return 1 + + def _get_device(self) -> torch.device: + return self.point_embeddings[0].weight.device + + def forward( + self, + points: Optional[Tuple[torch.Tensor, torch.Tensor]], + boxes: Optional[torch.Tensor], + masks: Optional[torch.Tensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + """Embeds different types of prompts, returning both sparse and dense + embeddings. + + Arguments: + points (tuple(torch.Tensor, torch.Tensor) or none): point coordinates + and labels to embed. + boxes (torch.Tensor or none): boxes to embed + masks (torch.Tensor or none): masks to embed + + Returns: + torch.Tensor: sparse embeddings for the points and boxes, with shape + BxNx(embed_dim), where N is determined by the number of input points + and boxes. + torch.Tensor: dense embeddings for the masks, in the shape + Bx(embed_dim)x(embed_H)x(embed_W) + """ # noqa + bs = self._get_batch_size(points, boxes, masks) + sparse_embeddings = torch.empty((bs, 0, self.embed_dim), + device=self._get_device()) + if points is not None: + coords, labels = points + point_embeddings = self._embed_points( + coords, labels, pad=(boxes is None)) + sparse_embeddings = torch.cat( + [sparse_embeddings, point_embeddings], dim=1) + if boxes is not None: + box_embeddings = self._embed_boxes(boxes) + sparse_embeddings = torch.cat([sparse_embeddings, box_embeddings], + dim=1) + + if masks is not None: + dense_embeddings = self._embed_masks(masks) + else: + dense_embeddings = self.no_mask_embed.weight.reshape( + 1, -1, 1, 1).expand(bs, -1, self.image_embedding_size[0], + self.image_embedding_size[1]) + + return sparse_embeddings, dense_embeddings + + +class PositionEmbeddingRandom(nn.Module): + """Positional encoding using random spatial frequencies.""" + + def __init__(self, + num_pos_feats: int = 64, + scale: Optional[float] = None) -> None: + super().__init__() + if scale is None or scale <= 0.0: + scale = 1.0 + self.register_buffer( + 'positional_encoding_gaussian_matrix', + scale * torch.randn((2, num_pos_feats)), + ) + + def _pe_encoding(self, coords: torch.Tensor) -> torch.Tensor: + """Positionally encode points that are normalized to [0,1].""" + # assuming coords are in [0, 1]^2 square and have d_1 x ... x d_n x 2 shape # noqa + coords = 2 * coords - 1 + coords = coords @ self.positional_encoding_gaussian_matrix + coords = 2 * np.pi * coords + # outputs d_1 x ... x d_n x C shape + return torch.cat([torch.sin(coords), torch.cos(coords)], dim=-1) + + def forward(self, size: Tuple[int, int]) -> torch.Tensor: + """Generate positional encoding for a grid of the specified size.""" + h, w = size + device: Any = self.positional_encoding_gaussian_matrix.device + grid = torch.ones((h, w), device=device, dtype=torch.float32) + y_embed = grid.cumsum(dim=0) - 0.5 + x_embed = grid.cumsum(dim=1) - 0.5 + y_embed = y_embed / h + x_embed = x_embed / w + + pe = self._pe_encoding(torch.stack([x_embed, y_embed], dim=-1)) + return pe.permute(2, 0, 1) # C x H x W + + def forward_with_coords(self, coords_input: torch.Tensor, + image_size: Tuple[int, int]) -> torch.Tensor: + """Positionally encode points that are not normalized to [0,1].""" + coords = coords_input.clone() + coords[:, :, 0] = coords[:, :, 0] / image_size[1] + coords[:, :, 1] = coords[:, :, 1] / image_size[0] + return self._pe_encoding(coords.to(torch.float)) # B x N x C diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/sam.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/sam.py new file mode 100644 index 0000000..c61c1ec --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/sam.py @@ -0,0 +1,188 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +# Borrowed from https://github.com/facebookresearch/segment-anything + +from typing import Any, Dict, List, Tuple + +import torch +from torch import nn +from torch.nn import functional as F + +from mmseg.registry import MODELS +from .mask_decoder import MaskDecoder +from .prompt_encoder import PromptEncoder + + +@MODELS.register_module() +class SAM(nn.Module): + mask_threshold: float = 0.0 + image_format: str = 'RGB' + + def __init__( + self, + image_encoder_cfg: dict, + prompt_encoder_cfg: dict, + mask_decoder_cfg: dict, + pixel_mean: List[float] = [123.675, 116.28, 103.53], + pixel_std: List[float] = [58.395, 57.12, 57.375], + ) -> None: + """SAM predicts object masks from an image and input prompts. Borrowed + from https://github.com/facebookresearch/segment-anything. + + Arguments: + image_encoder (ViTSAM): The backbone used to encode the + image into image embeddings that allow for efficient mask + prediction. + prompt_encoder (PromptEncoder): Encodes various types of input + prompts. + mask_decoder (MaskDecoder): Predicts masks from the image embeddings + and encoded prompts. + pixel_mean (list(float)): Mean values for normalizing pixels in the + input image. + pixel_std (list(float)): Std values for normalizing pixels in the + input image. + """ + super().__init__() + self.image_encoder = MODELS.build(image_encoder_cfg) + self.prompt_encoder: PromptEncoder = MODELS.build(prompt_encoder_cfg) + self.mask_decoder: MaskDecoder = MODELS.build(mask_decoder_cfg) + self.register_buffer('pixel_mean', + torch.Tensor(pixel_mean).view(-1, 1, 1), False) + self.register_buffer('pixel_std', + torch.Tensor(pixel_std).view(-1, 1, 1), False) + + @property + def device(self) -> Any: + return self.pixel_mean.device + + @torch.no_grad() + def forward( + self, + batched_input: List[Dict[str, Any]], + multimask_output: bool, + ) -> List[Dict[str, torch.Tensor]]: + """Predicts masks end-to-end from provided images and prompts. If + prompts are not known in advance, using SamPredictor is recommended + over calling the model directly. + + Borrowed from https://github.com/facebookresearch/segment-anything + + Arguments: + batched_input (list(dict)): A list over input images, each a + dictionary with the following keys. A prompt key can be + excluded if it is not present. + 'image': The image as a torch tensor in 3xHxW format, + already transformed for input to the model. + 'original_size': (tuple(int, int)) The original size of + the image before transformation, as (H, W). + 'point_coords': (torch.Tensor) Batched point prompts for + this image, with shape BxNx2. Already transformed to the + input frame of the model. + 'point_labels': (torch.Tensor) Batched labels for point prompts, + with shape BxN. + 'boxes': (torch.Tensor) Batched box inputs, with shape Bx4. + Already transformed to the input frame of the model. + 'mask_inputs': (torch.Tensor) Batched mask inputs to the model, + in the form Bx1xHxW. + multimask_output (bool): Whether the model should predict multiple + disambiguating masks, or return a single mask. + + Returns: + (list(dict)): A list over input images, where each element is + as dictionary with the following keys. + 'masks': (torch.Tensor) Batched binary mask predictions, + with shape BxCxHxW, where B is the number of input prompts, + C is determiend by multimask_output, and (H, W) is the + original size of the image. + 'iou_predictions': (torch.Tensor) The model's predictions + of mask quality, in shape BxC. + 'low_res_logits': (torch.Tensor) Low resolution logits with + shape BxCxHxW, where H=W=256. Can be passed as mask input + to subsequent iterations of prediction. + """ + input_images = torch.stack( + [self.preprocess(x['image']) for x in batched_input], dim=0) + image_embeddings = self.image_encoder(input_images) + + outputs = [] + for image_record, curr_embedding in zip(batched_input, + image_embeddings): + if 'point_coords' in image_record: + points = (image_record['point_coords'], + image_record['point_labels']) + else: + points = None + sparse_embeddings, dense_embeddings = self.prompt_encoder( + points=points, + boxes=image_record.get('boxes', None), + masks=image_record.get('mask_inputs', None), + ) + low_res_masks, iou_predictions = self.mask_decoder( + image_embeddings=curr_embedding.unsqueeze(0), + image_pe=self.prompt_encoder.get_dense_pe(), + sparse_prompt_embeddings=sparse_embeddings, + dense_prompt_embeddings=dense_embeddings, + multimask_output=multimask_output, + ) + masks = self.postprocess_masks( + low_res_masks, + input_size=image_record['image'].shape[-2:], + original_size=image_record['original_size'], + ) + masks = masks > self.mask_threshold + outputs.append({ + 'masks': masks, + 'iou_predictions': iou_predictions, + 'low_res_logits': low_res_masks, + }) + return outputs + + def postprocess_masks( + self, + masks: torch.Tensor, + input_size: Tuple[int, ...], + original_size: Tuple[int, ...], + ) -> torch.Tensor: + """Remove padding and upscale masks to the original image size. + + Borrowed from https://github.com/facebookresearch/segment-anything + + Arguments: + masks (torch.Tensor): Batched masks from the mask_decoder, + in BxCxHxW format. + input_size (tuple(int, int)): The size of the image input to the + model, in (H, W) format. Used to remove padding. + original_size (tuple(int, int)): The original size of the image + before resizing for input to the model, in (H, W) format. + + Returns: + (torch.Tensor): Batched masks in BxCxHxW format, where (H, W) + is given by original_size. + """ + masks = F.interpolate( + masks, + self.image_encoder.img_size, + mode='bilinear', + align_corners=False, + ) + masks = masks[..., :input_size[0], :input_size[1]] + masks = F.interpolate( + masks, original_size, mode='bilinear', align_corners=False) + return masks + + def preprocess(self, x: torch.Tensor) -> torch.Tensor: + """Normalize pixel values and pad to a square input.""" + # Normalize colors + x = (x - self.pixel_mean) / self.pixel_std + + # Pad + h, w = x.shape[-2:] + img_size = max(self.image_encoder.img_size) + padh = img_size - h + padw = img_size - w + x = F.pad(x, (0, padw, 0, padh)) + return x diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/transformer.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/transformer.py new file mode 100644 index 0000000..c56f602 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/modeling/transformer.py @@ -0,0 +1,241 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import math +from typing import Tuple, Type + +import torch +from torch import Tensor, nn + +from mmseg.registry import MODELS +from .common import MLPBlock + + +@MODELS.register_module() +class TwoWayTransformer(nn.Module): + + def __init__( + self, + depth: int, + embedding_dim: int, + num_heads: int, + mlp_dim: int, + activation: Type[nn.Module] = nn.ReLU, + attention_downsample_rate: int = 2, + ) -> None: + """A transformer decoder that attends to an input image using queries + whose positional embedding is supplied. + + Args: + depth (int): number of layers in the transformer + embedding_dim (int): the channel dimension for the input embeddings + num_heads (int): the number of heads for multihead attention. Must + divide embedding_dim + mlp_dim (int): the channel dimension internal to the MLP block + activation (nn.Module): the activation to use in the MLP block + """ + super().__init__() + self.depth = depth + self.embedding_dim = embedding_dim + self.num_heads = num_heads + self.mlp_dim = mlp_dim + self.layers = nn.ModuleList() + + for i in range(depth): + self.layers.append( + TwoWayAttentionBlock( + embedding_dim=embedding_dim, + num_heads=num_heads, + mlp_dim=mlp_dim, + activation=activation, + attention_downsample_rate=attention_downsample_rate, + skip_first_layer_pe=(i == 0), + )) + + self.final_attn_token_to_image = Attention( + embedding_dim, + num_heads, + downsample_rate=attention_downsample_rate) + self.norm_final_attn = nn.LayerNorm(embedding_dim) + + def forward( + self, + image_embedding: Tensor, + image_pe: Tensor, + point_embedding: Tensor, + ) -> Tuple[Tensor, Tensor]: + """ + Args: + image_embedding (torch.Tensor): image to attend to. Should be shape + B x embedding_dim x h x w for any h and w. + image_pe (torch.Tensor): the positional encoding to add to the image. Must + have the same shape as image_embedding. + point_embedding (torch.Tensor): the embedding to add to the query points. + Must have shape B x N_points x embedding_dim for any N_points. + + Returns: + torch.Tensor: the processed point_embedding + torch.Tensor: the processed image_embedding + """ # noqa E501 + # BxCxHxW -> BxHWxC == B x N_image_tokens x C + bs, c, h, w = image_embedding.shape + image_embedding = image_embedding.flatten(2).permute(0, 2, 1) + image_pe = image_pe.flatten(2).permute(0, 2, 1) + + # Prepare queries + queries = point_embedding + keys = image_embedding + + # Apply transformer blocks and final layernorm + for layer in self.layers: + queries, keys = layer( + queries=queries, + keys=keys, + query_pe=point_embedding, + key_pe=image_pe, + ) + + # Apply the final attenion layer from the points to the image + q = queries + point_embedding + k = keys + image_pe + attn_out = self.final_attn_token_to_image(q=q, k=k, v=keys) + queries = queries + attn_out + queries = self.norm_final_attn(queries) + + return queries, keys + + +class TwoWayAttentionBlock(nn.Module): + + def __init__( + self, + embedding_dim: int, + num_heads: int, + mlp_dim: int = 2048, + activation: Type[nn.Module] = nn.ReLU, + attention_downsample_rate: int = 2, + skip_first_layer_pe: bool = False, + ) -> None: + """A transformer block with four layers: (1) self-attention of sparse + inputs, (2) cross attention of sparse inputs to dense inputs, (3) mlp + block on sparse inputs, and (4) cross attention of dense inputs to + sparse inputs. + + Arguments: + embedding_dim (int): the channel dimension of the embeddings + num_heads (int): the number of heads in the attention layers + mlp_dim (int): the hidden dimension of the mlp block + activation (nn.Module): the activation of the mlp block + skip_first_layer_pe (bool): skip the PE on the first layer + """ + super().__init__() + self.self_attn = Attention(embedding_dim, num_heads) + self.norm1 = nn.LayerNorm(embedding_dim) + + self.cross_attn_token_to_image = Attention( + embedding_dim, + num_heads, + downsample_rate=attention_downsample_rate) + self.norm2 = nn.LayerNorm(embedding_dim) + + self.mlp = MLPBlock(embedding_dim, mlp_dim, activation) + self.norm3 = nn.LayerNorm(embedding_dim) + + self.norm4 = nn.LayerNorm(embedding_dim) + self.cross_attn_image_to_token = Attention( + embedding_dim, + num_heads, + downsample_rate=attention_downsample_rate) + + self.skip_first_layer_pe = skip_first_layer_pe + + def forward(self, queries: Tensor, keys: Tensor, query_pe: Tensor, + key_pe: Tensor) -> Tuple[Tensor, Tensor]: + # Self attention block + if self.skip_first_layer_pe: + queries = self.self_attn(q=queries, k=queries, v=queries) + else: + q = queries + query_pe + attn_out = self.self_attn(q=q, k=q, v=queries) + queries = queries + attn_out + queries = self.norm1(queries) + + # Cross attention block, tokens attending to image embedding + q = queries + query_pe + k = keys + key_pe + attn_out = self.cross_attn_token_to_image(q=q, k=k, v=keys) + queries = queries + attn_out + queries = self.norm2(queries) + + # MLP block + mlp_out = self.mlp(queries) + queries = queries + mlp_out + queries = self.norm3(queries) + + # Cross attention block, image embedding attending to tokens + q = queries + query_pe + k = keys + key_pe + attn_out = self.cross_attn_image_to_token(q=k, k=q, v=queries) + keys = keys + attn_out + keys = self.norm4(keys) + + return queries, keys + + +class Attention(nn.Module): + """An attention layer that allows for downscaling the size of the embedding + after projection to queries, keys, and values.""" + + def __init__( + self, + embedding_dim: int, + num_heads: int, + downsample_rate: int = 1, + ) -> None: + super().__init__() + self.embedding_dim = embedding_dim + self.internal_dim = embedding_dim // downsample_rate + self.num_heads = num_heads + assert self.internal_dim % num_heads == 0, 'num_heads must divide embedding_dim.' # noqa E501 + + self.q_proj = nn.Linear(embedding_dim, self.internal_dim) + self.k_proj = nn.Linear(embedding_dim, self.internal_dim) + self.v_proj = nn.Linear(embedding_dim, self.internal_dim) + self.out_proj = nn.Linear(self.internal_dim, embedding_dim) + + def _separate_heads(self, x: Tensor, num_heads: int) -> Tensor: + b, n, c = x.shape + x = x.reshape(b, n, num_heads, c // num_heads) + return x.transpose(1, 2) # B x N_heads x N_tokens x C_per_head + + def _recombine_heads(self, x: Tensor) -> Tensor: + b, n_heads, n_tokens, c_per_head = x.shape + x = x.transpose(1, 2) + return x.reshape(b, n_tokens, n_heads * c_per_head) # B x N_tokens x C + + def forward(self, q: Tensor, k: Tensor, v: Tensor) -> Tensor: + # Input projections + q = self.q_proj(q) + k = self.k_proj(k) + v = self.v_proj(v) + + # Separate into heads + q = self._separate_heads(q, self.num_heads) + k = self._separate_heads(k, self.num_heads) + v = self._separate_heads(v, self.num_heads) + + # Attention + _, _, _, c_per_head = q.shape + attn = q @ k.permute(0, 1, 3, 2) # B x N_heads x N_tokens x N_tokens + attn = attn / math.sqrt(c_per_head) + attn = torch.softmax(attn, dim=-1) + + # Get output + out = attn @ v + out = self._recombine_heads(out) + out = self.out_proj(out) + + return out diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/sam_inferencer.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/sam_inferencer.py new file mode 100644 index 0000000..2da2e95 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/sam_inferencer.py @@ -0,0 +1,688 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Any, Dict, List, Optional, Tuple + +import numpy as np +import torch +from mmengine.runner.checkpoint import load_checkpoint +# yapf: disable +from sam.utils import (MaskData, area_from_rle, batch_iterator, + batched_mask_to_box, box_xyxy_to_xywh, + build_all_layer_point_grids, calculate_stability_score, + coco_encode_rle, generate_crop_boxes, + is_box_near_crop_edge, mask_to_rle_pytorch, + remove_small_regions, rle_to_mask, uncrop_boxes_xyxy, + uncrop_masks, uncrop_points) +from torchvision.ops.boxes import batched_nms, box_area + +from mmseg.registry import MODELS, TRANSFORMS + +# yapf: enable + +model_zoo = { + 'base': + 'https://download.openmmlab.com/mmsegmentation/v0.5/sam/sam_vit-base-p16_3rdparty_sa1b-1024x1024_20230413-78a25eed.pth', # noqa + 'large': + 'https://download.openmmlab.com/mmsegmentation/v0.5/sam/sam_vit-large-p16_3rdparty_sa1b-1024x1024_20230413-940520da.pth', # noqa + 'huge': + 'https://download.openmmlab.com/mmsegmentation/v0.5/sam/sam_vit-huge-p16_3rdparty_sa1b-1024x1024_20230413-faaf96f6.pth', # noqa +} + + +class SAMInferencer: + + def __init__(self, arch: str = 'base') -> None: + assert arch in ['base', 'large', 'huge'] + self.model = self.init_model(arch) + self.transform = TRANSFORMS.build( + dict( + type='ResizeLongestSide', + target_length=max(self.model.image_encoder.img_size))) + + def set_image( + self, + image: np.ndarray, + image_format: str = 'RGB', + ) -> None: + """Calculates the image embeddings for the provided image, allowing + masks to be predicted with the 'predict' method. + + Arguments: + image (np.ndarray): The image for calculating masks. Expects an + image in HWC uint8 format, with pixel values in [0, 255]. + image_format (str): The color format of the image, in ['RGB', 'BGR']. + """ + assert image_format in [ + 'RGB', + 'BGR', + ], f"image_format must be in ['RGB', 'BGR'], is {image_format}." + if image_format != self.model.image_format: + image = image[..., ::-1] + + # Transform the image to the form expected by the model + input_image = self.transform.apply_image(image) + input_image_torch = torch.as_tensor(input_image, device=self.device) + input_image_torch = input_image_torch.permute( + 2, 0, 1).contiguous()[None, :, :, :] + + self.set_torch_image(input_image_torch, image.shape[:2]) + + @torch.no_grad() + def set_torch_image( + self, + transformed_image: torch.Tensor, + original_image_size: Tuple[int, ...], + ) -> None: + """Calculates the image embeddings for the provided image, allowing + masks to be predicted with the 'predict' method. Expects the input + image to be already transformed to the format expected by the model. + + Arguments: + transformed_image (torch.Tensor): The input image, with shape + 1x3xHxW, which has been transformed with ResizeLongestSide. + original_image_size (tuple(int, int)): The size of the image + before transformation, in (H, W) format. + """ + assert (len(transformed_image.shape) == 4 + and transformed_image.shape[1] == 3 + and max(*transformed_image.shape[2:]) == max( + self.model.image_encoder.img_size) + ), 'set_torch_image input must be BCHW with long side' + f' {self.model.image_encoder.img_size}.' + self.reset_image() + + self.original_size = original_image_size + self.input_size = tuple(transformed_image.shape[-2:]) + input_image = self.model.preprocess(transformed_image) + self.features = self.model.image_encoder(input_image)[0] + self.is_image_set = True + + def predict( + self, + point_coords: Optional[np.ndarray] = None, + point_labels: Optional[np.ndarray] = None, + box: Optional[np.ndarray] = None, + mask_input: Optional[np.ndarray] = None, + multimask_output: bool = True, + return_logits: bool = False, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """Predict masks for the given input prompts, using the currently set + image. + + Arguments: + point_coords (np.ndarray or None): A Nx2 array of point prompts to the + model. Each point is in (X,Y) in pixels. + point_labels (np.ndarray or None): A length N array of labels for the + point prompts. 1 indicates a foreground point and 0 indicates a + background point. + box (np.ndarray or None): A length 4 array given a box prompt to the + model, in XYXY format. + mask_input (np.ndarray): A low resolution mask input to the model, typically + coming from a previous prediction iteration. Has form 1xHxW, where + for SAM, H=W=256. + multimask_output (bool): If true, the model will return three masks. + For ambiguous input prompts (such as a single click), this will often + produce better masks than a single prediction. If only a single + mask is needed, the model's predicted quality score can be used + to select the best mask. For non-ambiguous prompts, such as multiple + input prompts, multimask_output=False can give better results. + return_logits (bool): If true, returns un-thresholded masks logits + instead of a binary mask. + + Returns: + (np.ndarray): The output masks in CxHxW format, where C is the + number of masks, and (H, W) is the original image size. + (np.ndarray): An array of length C containing the model's + predictions for the quality of each mask. + (np.ndarray): An array of shape CxHxW, where C is the number + of masks and H=W=256. These low resolution logits can be passed to + a subsequent iteration as mask input. + """ # noqa + if not self.is_image_set: + raise RuntimeError( + 'An image must be set with .set_image(...) before mask' + 'prediction.') + + # Transform input prompts + coords_torch = None + labels_torch = None + box_torch = None + mask_input_torch = None + + if point_coords is not None: + assert ( + point_labels is not None + ), 'point_labels must be supplied if point_coords is supplied.' + point_coords = self.transform.apply_coords(point_coords, + self.original_size) + coords_torch = torch.as_tensor( + point_coords, dtype=torch.float, device=self.device) + labels_torch = torch.as_tensor( + point_labels, dtype=torch.int, device=self.device) + coords_torch, labels_torch = coords_torch[ + None, :, :], labels_torch[None, :] + if box is not None: + box = self.transform.apply_boxes(box, self.original_size) + box_torch = torch.as_tensor( + box, dtype=torch.float, device=self.device) + box_torch = box_torch[None, :] + if mask_input is not None: + mask_input_torch = torch.as_tensor( + mask_input, dtype=torch.float, device=self.device) + mask_input_torch = mask_input_torch[None, :, :, :] + + masks, iou_predictions, low_res_masks = self.predict_torch( + coords_torch, + labels_torch, + box_torch, + mask_input_torch, + multimask_output, + return_logits=return_logits, + ) + + masks = masks[0].detach().cpu().numpy() + iou_predictions = iou_predictions[0].detach().cpu().numpy() + low_res_masks = low_res_masks[0].detach().cpu().numpy() + return masks, iou_predictions, low_res_masks + + @torch.no_grad() + def predict_torch( + self, + point_coords: Optional[torch.Tensor], + point_labels: Optional[torch.Tensor], + boxes: Optional[torch.Tensor] = None, + mask_input: Optional[torch.Tensor] = None, + multimask_output: bool = True, + return_logits: bool = False, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """Predict masks for the given input prompts, using the currently set + image. Input prompts are batched torch tensors and are expected to + already be transformed to the input frame using ResizeLongestSide. + + Arguments: + point_coords (torch.Tensor or None): A BxNx2 array of point prompts to the + model. Each point is in (X,Y) in pixels. + point_labels (torch.Tensor or None): A BxN array of labels for the + point prompts. 1 indicates a foreground point and 0 indicates a + background point. + box (np.ndarray or None): A Bx4 array given a box prompt to the + model, in XYXY format. + mask_input (np.ndarray): A low resolution mask input to the model, typically + coming from a previous prediction iteration. Has form Bx1xHxW, where + for SAM, H=W=256. Masks returned by a previous iteration of the + predict method do not need further transformation. + multimask_output (bool): If true, the model will return three masks. + For ambiguous input prompts (such as a single click), this will often + produce better masks than a single prediction. If only a single + mask is needed, the model's predicted quality score can be used + to select the best mask. For non-ambiguous prompts, such as multiple + input prompts, multimask_output=False can give better results. + return_logits (bool): If true, returns un-thresholded masks logits + instead of a binary mask. + + Returns: + (torch.Tensor): The output masks in BxCxHxW format, where C is the + number of masks, and (H, W) is the original image size. + (torch.Tensor): An array of shape BxC containing the model's + predictions for the quality of each mask. + (torch.Tensor): An array of shape BxCxHxW, where C is the number + of masks and H=W=256. These low res logits can be passed to + a subsequent iteration as mask input. + """ # noqa + if not self.is_image_set: + raise RuntimeError( + 'An image must be set with .set_image(...) before mask ' + 'prediction.') + + if point_coords is not None: + points = (point_coords, point_labels) + else: + points = None + + # Embed prompts + sparse_embeddings, dense_embeddings = self.model.prompt_encoder( + points=points, + boxes=boxes, + masks=mask_input, + ) + + # Predict masks + low_res_masks, iou_predictions = self.model.mask_decoder( + image_embeddings=self.features, + image_pe=self.model.prompt_encoder.get_dense_pe(), + sparse_prompt_embeddings=sparse_embeddings, + dense_prompt_embeddings=dense_embeddings, + multimask_output=multimask_output, + ) + + # Upscale the masks to the original image resolution + masks = self.model.postprocess_masks(low_res_masks, self.input_size, + self.original_size) + + if not return_logits: + masks = masks > self.model.mask_threshold + + return masks, iou_predictions, low_res_masks + + def get_image_embedding(self) -> torch.Tensor: + """Returns the image embeddings for the currently set image, with shape + 1xCxHxW, where C is the embedding dimension and (H,W) are the embedding + spatial dimension of SAM (typically C=256, H=W=64).""" + if not self.is_image_set: + raise RuntimeError( + 'An image must be set with .set_image(...) to generate an ' + 'embedding.') + assert self.features is not None, 'Features must exist if an image has' + ' been set.' + return self.features + + @property + def device(self) -> torch.device: + return self.model.device + + def reset_image(self) -> None: + """Resets the currently set image.""" + self.is_image_set = False + self.features = None + self.orig_h = None + self.orig_w = None + self.input_h = None + self.input_w = None + + def init_model(self, arch: str): + model = MODELS.build( + dict( + type='SAM', + image_encoder_cfg=dict( + type='mmpretrain.ViTSAM', + arch=arch, + img_size=1024, + patch_size=16, + out_channels=256, + use_abs_pos=True, + use_rel_pos=True, + window_size=14, + ), + prompt_encoder_cfg=dict( + type='PromptEncoder', + embed_dim=256, + image_embedding_size=(64, 64), + input_image_size=(1024, 1024), + mask_in_chans=16, + ), + mask_decoder_cfg=dict( + type='MaskDecoder', + num_multimask_outputs=3, + transformer=dict( + type='TwoWayTransformer', + depth=2, + embedding_dim=256, + mlp_dim=2048, + num_heads=8, + ), + transformer_dim=256, + iou_head_depth=3, + iou_head_hidden_dim=256, + ))) + load_checkpoint(model, model_zoo.get(arch), strict=True) + if torch.cuda.is_available(): + model = model.cuda() + return model + + +class SamAutomaticMaskGenerator: + + def __init__( + self, + arch: str = 'base', + points_per_side: Optional[int] = 32, + points_per_batch: int = 64, + pred_iou_thresh: float = 0.88, + stability_score_thresh: float = 0.95, + stability_score_offset: float = 1.0, + box_nms_thresh: float = 0.7, + crop_n_layers: int = 0, + crop_nms_thresh: float = 0.7, + crop_overlap_ratio: float = 512 / 1500, + crop_n_points_downscale_factor: int = 1, + point_grids: Optional[List[np.ndarray]] = None, + min_mask_region_area: int = 0, + output_mode: str = 'binary_mask', + ) -> None: + """Using a SAM model, generates masks for the entire image. Generates a + grid of point prompts over the image, then filters low quality and + duplicate masks. The default settings are chosen for SAM with a ViT-H + backbone. + + Arguments: + arch (str): The SAM model to use for mask prediction. + points_per_side (int or None): The number of points to be sampled + along one side of the image. The total number of points is + points_per_side**2. If None, 'point_grids' must provide explicit + point sampling. + points_per_batch (int): Sets the number of points run simultaneously + by the model. Higher numbers may be faster but use more GPU memory. + pred_iou_thresh (float): A filtering threshold in [0,1], using the + model's predicted mask quality. + stability_score_thresh (float): A filtering threshold in [0,1], using + the stability of the mask under changes to the cutoff used to binarize + the model's mask predictions. + stability_score_offset (float): The amount to shift the cutoff when + calculated the stability score. + box_nms_thresh (float): The box IoU cutoff used by non-maximal + suppression to filter duplicate masks. + crops_n_layers (int): If >0, mask prediction will be run again on + crops of the image. Sets the number of layers to run, where each + layer has 2**i_layer number of image crops. + crops_nms_thresh (float): The box IoU cutoff used by non-maximal + suppression to filter duplicate masks between different crops. + crop_overlap_ratio (float): Sets the degree to which crops overlap. + In the first crop layer, crops will overlap by this fraction of + the image length. Later layers with more crops scale down this overlap. + crop_n_points_downscale_factor (int): The number of points-per-side + sampled in layer n is scaled down by crop_n_points_downscale_factor**n. + point_grids (list(np.ndarray) or None): A list over explicit grids + of points used for sampling, normalized to [0,1]. The nth grid in the + list is used in the nth crop layer. Exclusive with points_per_side. + min_mask_region_area (int): If >0, postprocessing will be applied + to remove disconnected regions and holes in masks with area smaller + than min_mask_region_area. Requires opencv. + output_mode (str): The form masks are returned in. Can be 'binary_mask', + 'uncompressed_rle', or 'coco_rle'. 'coco_rle' requires pycocotools. + For large resolutions, 'binary_mask' may consume large amounts of + memory. + """ # noqa + + assert (points_per_side is None) != ( + point_grids is None + ), 'Exactly one of points_per_side or point_grid must be provided.' + if points_per_side is not None: + self.point_grids = build_all_layer_point_grids( + points_per_side, + crop_n_layers, + crop_n_points_downscale_factor, + ) + elif point_grids is not None: + self.point_grids = point_grids + else: + raise ValueError( + "Can't have both points_per_side and point_grid be None.") + + assert output_mode in [ + 'binary_mask', + 'uncompressed_rle', + 'coco_rle', + ], f'Unknown output_mode {output_mode}.' + if output_mode == 'coco_rle': + from pycocotools import \ + mask as mask_utils # type: ignore # noqa: F401 + + if min_mask_region_area > 0: + import cv2 # type: ignore # noqa: F401 + + self.predictor = SAMInferencer(arch) + self.points_per_batch = points_per_batch + self.pred_iou_thresh = pred_iou_thresh + self.stability_score_thresh = stability_score_thresh + self.stability_score_offset = stability_score_offset + self.box_nms_thresh = box_nms_thresh + self.crop_n_layers = crop_n_layers + self.crop_nms_thresh = crop_nms_thresh + self.crop_overlap_ratio = crop_overlap_ratio + self.crop_n_points_downscale_factor = crop_n_points_downscale_factor + self.min_mask_region_area = min_mask_region_area + self.output_mode = output_mode + + @torch.no_grad() + def generate(self, image: np.ndarray) -> List[Dict[str, Any]]: + """Generates masks for the given image. + + Arguments: + image (np.ndarray): The image to generate masks for, in HWC uint8 format. + + Returns: + list(dict(str, any)): A list over records for masks. Each record is + a dict containing the following keys: + segmentation (dict(str, any) or np.ndarray): The mask. If + output_mode='binary_mask', is an array of shape HW. Otherwise, + is a dictionary containing the RLE. + bbox (list(float)): The box around the mask, in XYWH format. + area (int): The area in pixels of the mask. + predicted_iou (float): The model's own prediction of the mask's + quality. This is filtered by the pred_iou_thresh parameter. + point_coords (list(list(float))): The point coordinates input + to the model to generate this mask. + stability_score (float): A measure of the mask's quality. This + is filtered on using the stability_score_thresh parameter. + crop_box (list(float)): The crop of the image used to generate + the mask, given in XYWH format. + """ # noqa + + # Generate masks + mask_data = self._generate_masks(image) + + # Filter small disconnected regions and holes in masks + if self.min_mask_region_area > 0: + mask_data = self.postprocess_small_regions( + mask_data, + self.min_mask_region_area, + max(self.box_nms_thresh, self.crop_nms_thresh), + ) + + # Encode masks + if self.output_mode == 'coco_rle': + mask_data['segmentations'] = [ + coco_encode_rle(rle) for rle in mask_data['rles'] + ] + elif self.output_mode == 'binary_mask': + mask_data['segmentations'] = [ + rle_to_mask(rle) for rle in mask_data['rles'] + ] + else: + mask_data['segmentations'] = mask_data['rles'] + + # Write mask records + curr_anns = [] + for idx in range(len(mask_data['segmentations'])): + ann = { + 'segmentation': + mask_data['segmentations'][idx], + 'area': + area_from_rle(mask_data['rles'][idx]), + 'bbox': + box_xyxy_to_xywh(mask_data['boxes'][idx]).tolist(), + 'predicted_iou': + mask_data['iou_preds'][idx].item(), + 'point_coords': [mask_data['points'][idx].tolist()], + 'stability_score': + mask_data['stability_score'][idx].item(), + 'crop_box': + box_xyxy_to_xywh(mask_data['crop_boxes'][idx]).tolist(), + } + curr_anns.append(ann) + + return curr_anns + + def _generate_masks(self, image: np.ndarray) -> MaskData: + orig_size = image.shape[:2] + crop_boxes, layer_idxs = generate_crop_boxes(orig_size, + self.crop_n_layers, + self.crop_overlap_ratio) + + # Iterate over image crops + data = MaskData() + for crop_box, layer_idx in zip(crop_boxes, layer_idxs): + crop_data = self._process_crop(image, crop_box, layer_idx, + orig_size) + data.cat(crop_data) + + # Remove duplicate masks between crops + if len(crop_boxes) > 1: + # Prefer masks from smaller crops + scores = 1 / box_area(data['crop_boxes']) + scores = scores.to(data['boxes'].device) + keep_by_nms = batched_nms( + data['boxes'].float(), + scores, + torch.zeros(len(data['boxes'])), # categories + iou_threshold=self.crop_nms_thresh, + ) + data.filter(keep_by_nms) + + data.to_numpy() + return data + + def _process_crop( + self, + image: np.ndarray, + crop_box: List[int], + crop_layer_idx: int, + orig_size: Tuple[int, ...], + ) -> MaskData: + # Crop the image and calculate embeddings + x0, y0, x1, y1 = crop_box + cropped_im = image[y0:y1, x0:x1, :] + cropped_im_size = cropped_im.shape[:2] + self.predictor.set_image(cropped_im) + + # Get points for this crop + points_scale = np.array(cropped_im_size)[None, ::-1] + points_for_image = self.point_grids[crop_layer_idx] * points_scale + + # Generate masks for this crop in batches + data = MaskData() + for (points, ) in batch_iterator(self.points_per_batch, + points_for_image): + batch_data = self._process_batch(points, cropped_im_size, crop_box, + orig_size) + data.cat(batch_data) + del batch_data + self.predictor.reset_image() + + # Remove duplicates within this crop. + keep_by_nms = batched_nms( + data['boxes'].float(), + data['iou_preds'], + torch.zeros(len(data['boxes'])), # categories + iou_threshold=self.box_nms_thresh, + ) + data.filter(keep_by_nms) + + # Return to the original image frame + data['boxes'] = uncrop_boxes_xyxy(data['boxes'], crop_box) + data['points'] = uncrop_points(data['points'], crop_box) + data['crop_boxes'] = torch.tensor( + [crop_box for _ in range(len(data['rles']))]) + + return data + + def _process_batch( + self, + points: np.ndarray, + im_size: Tuple[int, ...], + crop_box: List[int], + orig_size: Tuple[int, ...], + ) -> MaskData: + orig_h, orig_w = orig_size + + # Run model on this batch + transformed_points = self.predictor.transform.apply_coords( + points, im_size) + in_points = torch.as_tensor( + transformed_points, device=self.predictor.device) + in_labels = torch.ones( + in_points.shape[0], dtype=torch.int, device=in_points.device) + masks, iou_preds, _ = self.predictor.predict_torch( + in_points[:, None, :], + in_labels[:, None], + multimask_output=True, + return_logits=True, + ) + + # Serialize predictions and store in MaskData + data = MaskData( + masks=masks.flatten(0, 1), + iou_preds=iou_preds.flatten(0, 1), + points=torch.as_tensor(points.repeat(masks.shape[1], axis=0)), + ) + del masks + + # Filter by predicted IoU + if self.pred_iou_thresh > 0.0: + keep_mask = data['iou_preds'] > self.pred_iou_thresh + data.filter(keep_mask) + + # Calculate stability score + data['stability_score'] = calculate_stability_score( + data['masks'], self.predictor.model.mask_threshold, + self.stability_score_offset) + if self.stability_score_thresh > 0.0: + keep_mask = data['stability_score'] >= self.stability_score_thresh + data.filter(keep_mask) + + # Threshold masks and calculate boxes + data['masks'] = data['masks'] > self.predictor.model.mask_threshold + data['boxes'] = batched_mask_to_box(data['masks']) + + # Filter boxes that touch crop boundaries + keep_mask = ~is_box_near_crop_edge(data['boxes'], crop_box, + [0, 0, orig_w, orig_h]) + if not torch.all(keep_mask): + data.filter(keep_mask) + + # Compress to RLE + data['masks'] = uncrop_masks(data['masks'], crop_box, orig_h, orig_w) + data['rles'] = mask_to_rle_pytorch(data['masks']) + del data['masks'] + + return data + + @staticmethod + def postprocess_small_regions(mask_data: MaskData, min_area: int, + nms_thresh: float) -> MaskData: + """Removes small disconnected regions and holes in masks, then reruns + box NMS to remove any new duplicates. + + Edits mask_data in place. + + Requires open-cv as a dependency. + """ + if len(mask_data['rles']) == 0: + return mask_data + + # Filter small disconnected regions and holes + new_masks = [] + scores = [] + for rle in mask_data['rles']: + mask = rle_to_mask(rle) + + mask, changed = remove_small_regions(mask, min_area, mode='holes') + unchanged = not changed + mask, changed = remove_small_regions( + mask, min_area, mode='islands') + unchanged = unchanged and not changed + + new_masks.append(torch.as_tensor(mask).unsqueeze(0)) + # Give score=0 to changed masks and score=1 to unchanged masks + # so NMS will prefer ones that didn't need postprocessing + scores.append(float(unchanged)) + + # Recalculate boxes and remove any new duplicates + masks = torch.cat(new_masks, dim=0) + boxes = batched_mask_to_box(masks) + keep_by_nms = batched_nms( + boxes.float(), + torch.as_tensor(scores), + torch.zeros(len(boxes)), # categories + iou_threshold=nms_thresh, + ) + + # Only recalculate RLEs for masks that have changed + for i_mask in keep_by_nms: + if scores[i_mask] == 0.0: + mask_torch = masks[i_mask].unsqueeze(0) + mask_data['rles'][i_mask] = mask_to_rle_pytorch(mask_torch)[0] + mask_data['boxes'][i_mask] = boxes[ + i_mask] # update res directly + mask_data.filter(keep_by_nms) + + return mask_data diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/__init__.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/__init__.py new file mode 100644 index 0000000..5d33e33 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/__init__.py @@ -0,0 +1,2 @@ +from .amg import * # noqa: F403 F401 +from .transforms import ResizeLongestSide # noqa: F403 F401 diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/amg.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/amg.py new file mode 100644 index 0000000..3ba3599 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/amg.py @@ -0,0 +1,355 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +# https://github.com/facebookresearch/segment-anything + +import math +from copy import deepcopy +from itertools import product +from typing import Any, Dict, Generator, ItemsView, List, Tuple + +import numpy as np +import torch + + +class MaskData: + """A structure for storing masks and their related data in batched format. + + Implements basic filtering and concatenation. + """ + + def __init__(self, **kwargs) -> None: + for v in kwargs.values(): + assert isinstance( + v, (list, np.ndarray, torch.Tensor) + ), 'MaskData only supports list, numpy arrays, and torch tensors.' + self._stats = dict(**kwargs) + + def __setitem__(self, key: str, item: Any) -> None: + assert isinstance( + item, (list, np.ndarray, torch.Tensor) + ), 'MaskData only supports list, numpy arrays, and torch tensors.' + self._stats[key] = item + + def __delitem__(self, key: str) -> None: + del self._stats[key] + + def __getitem__(self, key: str) -> Any: + return self._stats[key] + + def items(self) -> ItemsView[str, Any]: + return self._stats.items() + + def filter(self, keep: torch.Tensor) -> None: + for k, v in self._stats.items(): + if v is None: + self._stats[k] = None + elif isinstance(v, torch.Tensor): + self._stats[k] = v[torch.as_tensor(keep, device=v.device)] + elif isinstance(v, np.ndarray): + self._stats[k] = v[keep.detach().cpu().numpy()] + elif isinstance(v, list) and keep.dtype == torch.bool: + self._stats[k] = [a for i, a in enumerate(v) if keep[i]] + elif isinstance(v, list): + self._stats[k] = [v[i] for i in keep] + else: + raise TypeError( + f'MaskData key {k} has an unsupported type {type(v)}.') + + def cat(self, new_stats: 'MaskData') -> None: + for k, v in new_stats.items(): + if k not in self._stats or self._stats[k] is None: + self._stats[k] = deepcopy(v) + elif isinstance(v, torch.Tensor): + self._stats[k] = torch.cat([self._stats[k], v], dim=0) + elif isinstance(v, np.ndarray): + self._stats[k] = np.concatenate([self._stats[k], v], axis=0) + elif isinstance(v, list): + self._stats[k] = self._stats[k] + deepcopy(v) + else: + raise TypeError( + f'MaskData key {k} has an unsupported type {type(v)}.') + + def to_numpy(self) -> None: + for k, v in self._stats.items(): + if isinstance(v, torch.Tensor): + self._stats[k] = v.detach().cpu().numpy() + + +def is_box_near_crop_edge(boxes: torch.Tensor, + crop_box: List[int], + orig_box: List[int], + atol: float = 20.0) -> torch.Tensor: + """Filter masks at the edge of a crop, but not at the edge of the original + image.""" + crop_box_torch = torch.as_tensor( + crop_box, dtype=torch.float, device=boxes.device) + orig_box_torch = torch.as_tensor( + orig_box, dtype=torch.float, device=boxes.device) + boxes = uncrop_boxes_xyxy(boxes, crop_box).float() + near_crop_edge = torch.isclose( + boxes, crop_box_torch[None, :], atol=atol, rtol=0) + near_image_edge = torch.isclose( + boxes, orig_box_torch[None, :], atol=atol, rtol=0) + near_crop_edge = torch.logical_and(near_crop_edge, ~near_image_edge) + return torch.any(near_crop_edge, dim=1) + + +def box_xyxy_to_xywh(box_xyxy: torch.Tensor) -> torch.Tensor: + box_xywh = deepcopy(box_xyxy) + box_xywh[2] = box_xywh[2] - box_xywh[0] + box_xywh[3] = box_xywh[3] - box_xywh[1] + return box_xywh + + +def batch_iterator(batch_size: int, *args) -> Generator[List[Any], None, None]: + assert len(args) > 0 and all( + len(a) == len(args[0]) for a in + args), 'Batched iteration must have inputs of all the same size.' + n_batches = len(args[0]) // batch_size + int( + len(args[0]) % batch_size != 0) + for b in range(n_batches): + yield [arg[b * batch_size:(b + 1) * batch_size] for arg in args] + + +def mask_to_rle_pytorch(tensor: torch.Tensor) -> List[Dict[str, Any]]: + """Encodes masks to an uncompressed RLE, in the format expected by pycoco + tools.""" + # Put in fortran order and flatten h,w + b, h, w = tensor.shape + tensor = tensor.permute(0, 2, 1).flatten(1) + + # Compute change indices + diff = tensor[:, 1:] ^ tensor[:, :-1] + change_indices = diff.nonzero() + + # Encode run length + out = [] + for i in range(b): + cur_idxs = change_indices[change_indices[:, 0] == i, 1] + cur_idxs = torch.cat([ + torch.tensor([0], dtype=cur_idxs.dtype, device=cur_idxs.device), + cur_idxs + 1, + torch.tensor([h * w], dtype=cur_idxs.dtype, + device=cur_idxs.device), + ]) + btw_idxs = cur_idxs[1:] - cur_idxs[:-1] + counts = [] if tensor[i, 0] == 0 else [0] + counts.extend(btw_idxs.detach().cpu().tolist()) + out.append({'size': [h, w], 'counts': counts}) + return out + + +def rle_to_mask(rle: Dict[str, Any]) -> np.ndarray: + """Compute a binary mask from an uncompressed RLE.""" + h, w = rle['size'] + mask = np.empty(h * w, dtype=bool) + idx = 0 + parity = False + for count in rle['counts']: + mask[idx:idx + count] = parity + idx += count + parity ^= True + mask = mask.reshape(w, h) + return mask.transpose() # Put in C order + + +def area_from_rle(rle: Dict[str, Any]) -> int: + return sum(rle['counts'][1::2]) + + +def calculate_stability_score(masks: torch.Tensor, mask_threshold: float, + threshold_offset: float) -> torch.Tensor: + """Computes the stability score for a batch of masks. + + The stability score is the IoU between the binary masks obtained by + thresholding the predicted mask logits at high and low values. + """ + # One mask is always contained inside the other. + # Save memory by preventing unnecessary cast to torch.int64 + intersections = ((masks > (mask_threshold + threshold_offset)).sum( + -1, dtype=torch.int16).sum(-1, dtype=torch.int32)) + unions = ((masks > (mask_threshold - threshold_offset)).sum( + -1, dtype=torch.int16).sum(-1, dtype=torch.int32)) + return intersections / unions + + +def build_point_grid(n_per_side: int) -> np.ndarray: + """Generates a 2D grid of points evenly spaced in [0,1]x[0,1].""" + offset = 1 / (2 * n_per_side) + points_one_side = np.linspace(offset, 1 - offset, n_per_side) + points_x = np.tile(points_one_side[None, :], (n_per_side, 1)) + points_y = np.tile(points_one_side[:, None], (1, n_per_side)) + points = np.stack([points_x, points_y], axis=-1).reshape(-1, 2) + return points + + +def build_all_layer_point_grids(n_per_side: int, n_layers: int, + scale_per_layer: int) -> List[np.ndarray]: + """Generates point grids for all crop layers.""" + points_by_layer = [] + for i in range(n_layers + 1): + n_points = int(n_per_side / (scale_per_layer**i)) + points_by_layer.append(build_point_grid(n_points)) + return points_by_layer + + +def generate_crop_boxes( + im_size: Tuple[int, ...], n_layers: int, + overlap_ratio: float) -> Tuple[List[List[int]], List[int]]: + """Generates a list of crop boxes of different sizes. + + Each layer has (2**i)**2 boxes for the ith layer. + """ + crop_boxes, layer_idxs = [], [] + im_h, im_w = im_size + short_side = min(im_h, im_w) + + # Original image + crop_boxes.append([0, 0, im_w, im_h]) + layer_idxs.append(0) + + def crop_len(orig_len, n_crops, overlap): + return int(math.ceil((overlap * (n_crops - 1) + orig_len) / n_crops)) + + for i_layer in range(n_layers): + n_crops_per_side = 2**(i_layer + 1) + overlap = int(overlap_ratio * short_side * (2 / n_crops_per_side)) + + crop_w = crop_len(im_w, n_crops_per_side, overlap) + crop_h = crop_len(im_h, n_crops_per_side, overlap) + + crop_box_x0 = [ + int((crop_w - overlap) * i) for i in range(n_crops_per_side) + ] + crop_box_y0 = [ + int((crop_h - overlap) * i) for i in range(n_crops_per_side) + ] + + # Crops in XYWH format + for x0, y0 in product(crop_box_x0, crop_box_y0): + box = [x0, y0, min(x0 + crop_w, im_w), min(y0 + crop_h, im_h)] + crop_boxes.append(box) + layer_idxs.append(i_layer + 1) + + return crop_boxes, layer_idxs + + +def uncrop_boxes_xyxy(boxes: torch.Tensor, + crop_box: List[int]) -> torch.Tensor: + x0, y0, _, _ = crop_box + offset = torch.tensor([[x0, y0, x0, y0]], device=boxes.device) + # Check if boxes has a channel dimension + if len(boxes.shape) == 3: + offset = offset.unsqueeze(1) + return boxes + offset + + +def uncrop_points(points: torch.Tensor, crop_box: List[int]) -> torch.Tensor: + x0, y0, _, _ = crop_box + offset = torch.tensor([[x0, y0]], device=points.device) + # Check if points has a channel dimension + if len(points.shape) == 3: + offset = offset.unsqueeze(1) + return points + offset + + +def uncrop_masks(masks: torch.Tensor, crop_box: List[int], orig_h: int, + orig_w: int) -> torch.Tensor: + x0, y0, x1, y1 = crop_box + if x0 == 0 and y0 == 0 and x1 == orig_w and y1 == orig_h: + return masks + # Coordinate transform masks + pad_x, pad_y = orig_w - (x1 - x0), orig_h - (y1 - y0) + pad = (x0, pad_x - x0, y0, pad_y - y0) + return torch.nn.functional.pad(masks, pad, value=0) + + +def remove_small_regions(mask: np.ndarray, area_thresh: float, + mode: str) -> Tuple[np.ndarray, bool]: + """Removes small disconnected regions and holes in a mask. + + Returns the mask and an indicator of if the mask has been modified. + """ + import cv2 # type: ignore + + assert mode in ['holes', 'islands'] + correct_holes = mode == 'holes' + working_mask = (correct_holes ^ mask).astype(np.uint8) + n_labels, regions, stats, _ = cv2.connectedComponentsWithStats( + working_mask, 8) + sizes = stats[:, -1][1:] # Row 0 is background label + small_regions = [i + 1 for i, s in enumerate(sizes) if s < area_thresh] + if len(small_regions) == 0: + return mask, False + fill_labels = [0] + small_regions + if not correct_holes: + fill_labels = [i for i in range(n_labels) if i not in fill_labels] + # If every region is below threshold, keep largest + if len(fill_labels) == 0: + fill_labels = [int(np.argmax(sizes)) + 1] + mask = np.isin(regions, fill_labels) + return mask, True + + +def coco_encode_rle(uncompressed_rle: Dict[str, Any]) -> Dict[str, Any]: + from pycocotools import mask as mask_utils # type: ignore + + h, w = uncompressed_rle['size'] + rle = mask_utils.frPyObjects(uncompressed_rle, h, w) + rle['counts'] = rle['counts'].decode( + 'utf-8') # Necessary to serialize with json + return rle + + +def batched_mask_to_box(masks: torch.Tensor) -> torch.Tensor: + """Calculates boxes in XYXY format around masks. + + Return [0,0,0,0] for an empty mask. For input shape C1xC2x...xHxW, the + output shape is C1xC2x...x4. + """ + # torch.max below raises an error on empty inputs, just skip in this case + if torch.numel(masks) == 0: + return torch.zeros(*masks.shape[:-2], 4, device=masks.device) + + # Normalize shape to CxHxW + shape = masks.shape + h, w = shape[-2:] + if len(shape) > 2: + masks = masks.flatten(0, -3) + else: + masks = masks.unsqueeze(0) + + # Get top and bottom edges + in_height, _ = torch.max(masks, dim=-1) + in_height_coords = in_height * torch.arange( + h, device=in_height.device)[None, :] + bottom_edges, _ = torch.max(in_height_coords, dim=-1) + in_height_coords = in_height_coords + h * (~in_height) + top_edges, _ = torch.min(in_height_coords, dim=-1) + + # Get left and right edges + in_width, _ = torch.max(masks, dim=-2) + in_width_coords = in_width * torch.arange( + w, device=in_width.device)[None, :] + right_edges, _ = torch.max(in_width_coords, dim=-1) + in_width_coords = in_width_coords + w * (~in_width) + left_edges, _ = torch.min(in_width_coords, dim=-1) + + # If the mask is empty the right edge will be to the left of the left edge. + # Replace these boxes with [0, 0, 0, 0] + empty_filter = (right_edges < left_edges) | (bottom_edges < top_edges) + out = torch.stack([left_edges, top_edges, right_edges, bottom_edges], + dim=-1) + out = out * (~empty_filter).unsqueeze(-1) + + # Return to original shape + if len(shape) > 2: + out = out.reshape(*shape[:-2], 4) + else: + out = out[0] + + return out diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/transforms.py b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/transforms.py new file mode 100644 index 0000000..484fd66 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam/utils/transforms.py @@ -0,0 +1,110 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from copy import deepcopy +from typing import Tuple + +import numpy as np +import torch +from torch.nn import functional as F +from torchvision.transforms.functional import resize # type: ignore +from torchvision.transforms.functional import to_pil_image + +from mmseg.registry import TRANSFORMS + + +@TRANSFORMS.register_module() +class ResizeLongestSide: + """Resizes images to longest side 'target_length', as well as provides + methods for resizing coordinates and boxes. + + Provides methods for transforming both numpy array and batched torch + tensors. + """ + + def __init__(self, target_length: int) -> None: + self.target_length = target_length + + def apply_image(self, image: np.ndarray) -> np.ndarray: + """Expects a numpy array with shape HxWxC in uint8 format.""" + target_size = self.get_preprocess_shape(image.shape[0], image.shape[1], + self.target_length) + return np.array(resize(to_pil_image(image), target_size)) + + def apply_coords(self, coords: np.ndarray, + original_size: Tuple[int, ...]) -> np.ndarray: + """Expects a numpy array of length 2 in the final dimension. + + Requires the original image size in (H, W) format. + """ + old_h, old_w = original_size + new_h, new_w = self.get_preprocess_shape(original_size[0], + original_size[1], + self.target_length) + coords = deepcopy(coords).astype(float) + coords[..., 0] = coords[..., 0] * (new_w / old_w) + coords[..., 1] = coords[..., 1] * (new_h / old_h) + return coords + + def apply_boxes(self, boxes: np.ndarray, + original_size: Tuple[int, ...]) -> np.ndarray: + """Expects a numpy array shape Bx4. + + Requires the original image size in (H, W) format. + """ + boxes = self.apply_coords(boxes.reshape(-1, 2, 2), original_size) + return boxes.reshape(-1, 4) + + def apply_image_torch(self, image: torch.Tensor) -> torch.Tensor: + """Expects batched images with shape BxCxHxW and float format. + + This transformation may not exactly match apply_image. apply_image is + the transformation expected by the model. + """ + # Expects an image in BCHW format. May not exactly match apply_image. + target_size = self.get_preprocess_shape(image.shape[0], image.shape[1], + self.target_length) + return F.interpolate( + image, + target_size, + mode='bilinear', + align_corners=False, + antialias=True) + + def apply_coords_torch(self, coords: torch.Tensor, + original_size: Tuple[int, ...]) -> torch.Tensor: + """Expects a torch tensor with length 2 in the last dimension. + + Requires the original image size in (H, W) format. + """ + old_h, old_w = original_size + new_h, new_w = self.get_preprocess_shape(original_size[0], + original_size[1], + self.target_length) + coords = deepcopy(coords).to(torch.float) + coords[..., 0] = coords[..., 0] * (new_w / old_w) + coords[..., 1] = coords[..., 1] * (new_h / old_h) + return coords + + def apply_boxes_torch(self, boxes: torch.Tensor, + original_size: Tuple[int, ...]) -> torch.Tensor: + """Expects a torch tensor with shape Bx4. + + Requires the original image size in (H, W) format. + """ + boxes = self.apply_coords_torch(boxes.reshape(-1, 2, 2), original_size) + return boxes.reshape(-1, 4) + + @staticmethod + def get_preprocess_shape(oldh: int, oldw: int, + long_side_length: int) -> Tuple[int, int]: + """Compute the output size given input size and target long side + length.""" + scale = long_side_length * 1.0 / max(oldh, oldw) + newh, neww = oldh * scale, oldw * scale + neww = int(neww + 0.5) + newh = int(newh + 0.5) + return (newh, neww) diff --git a/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam_image_demo.ipynb b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam_image_demo.ipynb new file mode 100644 index 0000000..1cb433f --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/sam_inference_demo/sam_image_demo.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import cv2\n", + "\n", + "import sam # noqa: F401\n", + "from sam.sam_inferencer import SAMInferencer\n", + "\n", + "\n", + "def show_mask(mask, ax, random_color=False):\n", + " if random_color:\n", + " color = np.concatenate([np.random.random(3), np.array([0.6])], axis=0)\n", + " else:\n", + " color = np.array([30/255, 144/255, 255/255, 0.6])\n", + " h, w = mask.shape[-2:]\n", + " mask_image = mask.reshape(h, w, 1) * color.reshape(1, 1, -1)\n", + " ax.imshow(mask_image)\n", + " \n", + "def show_points(coords, labels, ax, marker_size=375):\n", + " pos_points = coords[labels==1]\n", + " neg_points = coords[labels==0]\n", + " ax.scatter(pos_points[:, 0], pos_points[:, 1], color='green', marker='*', s=marker_size, edgecolor='white', linewidth=1.25)\n", + " ax.scatter(neg_points[:, 0], neg_points[:, 1], color='red', marker='*', s=marker_size, edgecolor='white', linewidth=1.25) \n", + " \n", + "def show_box(box, ax):\n", + " x0, y0 = box[0], box[1]\n", + " w, h = box[2] - box[0], box[3] - box[1]\n", + " ax.add_patch(plt.Rectangle((x0, y0), w, h, edgecolor='green', facecolor=(0,0,0,0), lw=2))\n", + "\n", + "image = cv2.imread('../../demo/demo.png')\n", + "image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n", + "plt.figure(figsize=(10,10))\n", + "plt.imshow(image)\n", + "plt.axis('on')\n", + "plt.show()\n", + "print(image.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inferencer = SAMInferencer(arch='huge')\n", + "inferencer.set_image(image)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "input_point = np.array([[280, 230], [500, 300]])\n", + "input_label = np.array([1, 1])\n", + "plt.figure(figsize=(10,10))\n", + "plt.imshow(image)\n", + "show_points(input_point, input_label, plt.gca())\n", + "plt.axis('on')\n", + "plt.show() " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "masks, scores, logits = inferencer.predict(\n", + " point_coords=input_point,\n", + " point_labels=input_label,\n", + " multimask_output=True,\n", + ")\n", + "for i, (mask, score) in enumerate(zip(masks, scores)):\n", + " plt.figure(figsize=(10,10))\n", + " plt.imshow(image)\n", + " show_mask(mask, plt.gca(), random_color=True)\n", + " show_points(input_point, input_label, plt.gca())\n", + " plt.title(f\"Mask {i+1}, Score: {score:.3f}\", fontsize=18)\n", + " plt.axis('off')\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pt1.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Seg_All_In_One_MMSeg/projects/van/README.md b/Seg_All_In_One_MMSeg/projects/van/README.md new file mode 100644 index 0000000..be0ba36 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/README.md @@ -0,0 +1,101 @@ +# Visual Attention Network (VAN) for Segmentation + +This repo is a PyTorch implementation of applying **VAN** (**Visual Attention Network**) to semantic segmentation. + +The code is an integration from [VAN-Segmentation](https://github.com/Visual-Attention-Network/VAN-Segmentation/blob/main/README.md?plain=1) + +More details can be found in [**Visual Attention Network**](https://arxiv.org/abs/2202.09741). + +## Citation + +```bib +@article{guo2022visual, + title={Visual Attention Network}, + author={Guo, Meng-Hao and Lu, Cheng-Ze and Liu, Zheng-Ning and Cheng, Ming-Ming and Hu, Shi-Min}, + journal={arXiv preprint arXiv:2202.09741}, + year={2022} +} +``` + +## Results + +**Notes**: Pre-trained models can be found in [TsingHua Cloud](https://cloud.tsinghua.edu.cn/d/0100f0cea37d41ba8d08/). + +Results can be found in [VAN-Segmentation](https://github.com/Visual-Attention-Network/VAN-Segmentation/blob/main/README.md?plain=1) + +We provide evaluation results of the converted weights. + +| Method | Backbone | mIoU | Download | +| :-----: | :----------: | :---: | :--------------------------------------------------------------------------------------------------------------------------------------------: | +| UPerNet | VAN-B2 | 49.35 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b2-in1kpre_upernet_3rdparty_512x512-ade20k_20230522-19c58aee.pth) | +| UPerNet | VAN-B3 | 49.71 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b3-in1kpre_upernet_3rdparty_512x512-ade20k_20230522-653bd6b7.pth) | +| UPerNet | VAN-B4 | 51.56 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b4-in1kpre_upernet_3rdparty_512x512-ade20k_20230522-653bd6b7.pth) | +| UPerNet | VAN-B4-in22k | 52.61 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b4-in22kpre_upernet_3rdparty_512x512-ade20k_20230522-4a4d744a.pth) | +| UPerNet | VAN-B5-in22k | 53.11 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b5-in22kpre_upernet_3rdparty_512x512-ade20k_20230522-5bb6f2b4.pth) | +| UPerNet | VAN-B6-in22k | 54.25 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b6-in22kpre_upernet_3rdparty_512x512-ade20k_20230522-e226b363.pth) | +| FPN | VAN-B0 | 38.65 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b0-in1kpre_fpn_3rdparty_512x512-ade20k_20230522-75a76298.pth) | +| FPN | VAN-B1 | 43.22 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b1-in1kpre_fpn_3rdparty_512x512-ade20k_20230522-104499ff.pth) | +| FPN | VAN-B2 | 46.84 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b2-in1kpre_fpn_3rdparty_512x512-ade20k_20230522-7074e6f8.pth) | +| FPN | VAN-B3 | 48.32 | [model](https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b3-in1kpre_fpn_3rdparty_512x512-ade20k_20230522-2c3b7f5e.pth) | + +## Preparation + +Install MMSegmentation and download ADE20K according to the guidelines in MMSegmentation. + +## Requirement + +**Step 0.** Install [MMCV](https://github.com/open-mmlab/mmcv) using [MIM](https://github.com/open-mmlab/mim). + +```shell +pip install -U openmim +mim install mmengine +mim install "mmcv>=2.0.0" +``` + +**Step 1.** Install MMSegmentation. + +Case a: If you develop and run mmseg directly, install it from source: + +```shell +git clone -b main https://github.com/open-mmlab/mmsegmentation.git +cd mmsegmentation +pip install -v -e . +``` + +Case b: If you use mmsegmentation as a dependency or third-party package, install it with pip: + +```shell +pip install "mmsegmentation>=1.0.0" +``` + +## Training + +If you use 4 GPUs for training by default. Run: + +```bash +bash tools/dist_train.sh projects/van/configs/van/van-b2_pre1k_upernet_4xb2-160k_ade20k-512x512.py 4 +``` + +## Evaluation + +To evaluate the model, an example is: + +```bash +bash tools/dist_train.sh projects/van/configs/van/van-b2_pre1k_upernet_4xb2-160k_ade20k-512x512.py work_dirs/van-b2_pre1k_upernet_4xb2-160k_ade20k-512x512/iter_160000.pth 4 --eval mIoU +``` + +## FLOPs + +To calculate FLOPs for a model, run: + +```bash +bash tools/analysis_tools/get_flops.py projects/van/configs/van/van-b2_pre1k_upernet_4xb2-160k_ade20k-512x512.py --shape 512 512 +``` + +## Acknowledgment + +Our implementation is mainly based on [mmsegmentation](https://github.com/open-mmlab/mmsegmentation/tree/v0.12.0), [Swin-Transformer](https://github.com/SwinTransformer/Swin-Transformer-Semantic-Segmentation), [PoolFormer](https://github.com/sail-sg/poolformer), [Enjoy-Hamburger](https://github.com/Gsunshine/Enjoy-Hamburger) and [VAN-Segmentation](https://github.com/Visual-Attention-Network/VAN-Segmentation/blob/main/README.md?plain=1). Thanks for their authors. + +## LICENSE + +This repo is under the Apache-2.0 license. For commercial use, please contact the authors. diff --git a/Seg_All_In_One_MMSeg/projects/van/backbones/__init__.py b/Seg_All_In_One_MMSeg/projects/van/backbones/__init__.py new file mode 100644 index 0000000..071995d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/backbones/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from .van import VAN + +__all__ = ['VAN'] diff --git a/Seg_All_In_One_MMSeg/projects/van/backbones/van.py b/Seg_All_In_One_MMSeg/projects/van/backbones/van.py new file mode 100644 index 0000000..301834a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/backbones/van.py @@ -0,0 +1,124 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import warnings + +import torch +import torch.nn as nn +from mmengine.model import BaseModule + +from mmseg.models.backbones.mscan import (MSCAN, MSCABlock, + MSCASpatialAttention, + OverlapPatchEmbed) +from mmseg.registry import MODELS + + +class VANAttentionModule(BaseModule): + + def __init__(self, in_channels): + super().__init__() + self.conv0 = nn.Conv2d( + in_channels, in_channels, 5, padding=2, groups=in_channels) + self.conv_spatial = nn.Conv2d( + in_channels, + in_channels, + 7, + stride=1, + padding=9, + groups=in_channels, + dilation=3) + self.conv1 = nn.Conv2d(in_channels, in_channels, 1) + + def forward(self, x): + u = x.clone() + attn = self.conv0(x) + attn = self.conv_spatial(attn) + attn = self.conv1(attn) + return u * attn + + +class VANSpatialAttention(MSCASpatialAttention): + + def __init__(self, in_channels, act_cfg=dict(type='GELU')): + super().__init__(in_channels, act_cfg=act_cfg) + self.spatial_gating_unit = VANAttentionModule(in_channels) + + +class VANBlock(MSCABlock): + + def __init__(self, + channels, + mlp_ratio=4., + drop=0., + drop_path=0., + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='SyncBN', requires_grad=True)): + super().__init__( + channels, + mlp_ratio=mlp_ratio, + drop=drop, + drop_path=drop_path, + act_cfg=act_cfg, + norm_cfg=norm_cfg) + self.attn = VANSpatialAttention(channels) + + +@MODELS.register_module() +class VAN(MSCAN): + + def __init__(self, + in_channels=3, + embed_dims=[64, 128, 256, 512], + mlp_ratios=[8, 8, 4, 4], + drop_rate=0., + drop_path_rate=0., + depths=[3, 4, 6, 3], + num_stages=4, + act_cfg=dict(type='GELU'), + norm_cfg=dict(type='SyncBN', requires_grad=True), + pretrained=None, + init_cfg=None): + super(MSCAN, self).__init__(init_cfg=init_cfg) + + assert not (init_cfg and pretrained), \ + 'init_cfg and pretrained cannot be set at the same time' + if isinstance(pretrained, str): + warnings.warn('DeprecationWarning: pretrained is deprecated, ' + 'please use "init_cfg" instead') + self.init_cfg = dict(type='Pretrained', checkpoint=pretrained) + elif pretrained is not None: + raise TypeError('pretrained must be a str or None') + + self.depths = depths + self.num_stages = num_stages + + # stochastic depth decay rule + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, sum(depths)) + ] + cur = 0 + + for i in range(num_stages): + patch_embed = OverlapPatchEmbed( + patch_size=7 if i == 0 else 3, + stride=4 if i == 0 else 2, + in_channels=in_channels if i == 0 else embed_dims[i - 1], + embed_dim=embed_dims[i], + norm_cfg=norm_cfg) + + block = nn.ModuleList([ + VANBlock( + channels=embed_dims[i], + mlp_ratio=mlp_ratios[i], + drop=drop_rate, + drop_path=dpr[cur + j], + act_cfg=act_cfg, + norm_cfg=norm_cfg) for j in range(depths[i]) + ]) + norm = nn.LayerNorm(embed_dims[i]) + cur += depths[i] + + setattr(self, f'patch_embed{i + 1}', patch_embed) + setattr(self, f'block{i + 1}', block) + setattr(self, f'norm{i + 1}', norm) + + def init_weights(self): + return super().init_weights() diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/_base_/datasets/ade20k.py b/Seg_All_In_One_MMSeg/projects/van/configs/_base_/datasets/ade20k.py new file mode 100644 index 0000000..69b3c2a --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/_base_/datasets/ade20k.py @@ -0,0 +1,14 @@ +# dataset settings +_base_ = '../../../../../configs/_base_/datasets/ade20k.py' + +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=(2048, 512), keep_ratio=True), + dict(type='ResizeToMultiple', size_divisor=32), + # add loading annotation after ``Resize`` because ground truth + # does not need to do resize data transform + dict(type='LoadAnnotations', reduce_zero_label=True), + dict(type='PackSegInputs') +] +val_dataloader = dict(dataset=dict(pipeline=test_pipeline)) +test_dataloader = val_dataloader diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/_base_/models/van_fpn.py b/Seg_All_In_One_MMSeg/projects/van/configs/_base_/models/van_fpn.py new file mode 100644 index 0000000..c7fd739 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/_base_/models/van_fpn.py @@ -0,0 +1,43 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) + +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size=(512, 512)) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='VAN', + embed_dims=[32, 64, 160, 256], + drop_rate=0.0, + drop_path_rate=0.1, + depths=[3, 3, 5, 2], + act_cfg=dict(type='GELU'), + norm_cfg=norm_cfg, + init_cfg=dict()), + neck=dict( + type='FPN', + in_channels=[32, 64, 160, 256], + out_channels=256, + num_outs=4), + decode_head=dict( + type='FPNHead', + in_channels=[256, 256, 256, 256], + in_index=[0, 1, 2, 3], + feature_strides=[4, 8, 16, 32], + channels=128, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/_base_/models/van_upernet.py b/Seg_All_In_One_MMSeg/projects/van/configs/_base_/models/van_upernet.py new file mode 100644 index 0000000..8f94c0d --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/_base_/models/van_upernet.py @@ -0,0 +1,51 @@ +# model settings +norm_cfg = dict(type='SyncBN', requires_grad=True) + +data_preprocessor = dict( + type='SegDataPreProcessor', + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + bgr_to_rgb=True, + pad_val=0, + seg_pad_val=255, + size=(512, 512)) +model = dict( + type='EncoderDecoder', + data_preprocessor=data_preprocessor, + backbone=dict( + type='VAN', + embed_dims=[32, 64, 160, 256], + drop_rate=0.0, + drop_path_rate=0.1, + depths=[3, 3, 5, 2], + act_cfg=dict(type='GELU'), + norm_cfg=norm_cfg, + init_cfg=dict()), + decode_head=dict( + type='UPerHead', + in_channels=[32, 64, 160, 256], + in_index=[0, 1, 2, 3], + pool_scales=(1, 2, 3, 6), + channels=512, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)), + auxiliary_head=dict( + type='FCNHead', + in_channels=160, + in_index=2, + channels=256, + num_convs=1, + concat_input=False, + dropout_ratio=0.1, + num_classes=150, + norm_cfg=norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)), + # model training and testing settings + train_cfg=dict(), + test_cfg=dict(mode='whole')) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b0_fpn_8xb4-40k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b0_fpn_8xb4-40k_ade20k-512x512.py new file mode 100644 index 0000000..2faf378 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b0_fpn_8xb4-40k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = './van-b2_fpn_8xb4-40k_ade20k-512x512.py' +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b0_3rdparty_20230522-956f5e0d.pth' # noqa +model = dict( + backbone=dict( + embed_dims=[32, 64, 160, 256], + depths=[3, 3, 5, 2], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path)), + neck=dict(in_channels=[32, 64, 160, 256])) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b1_fpn_8xb4-40k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b1_fpn_8xb4-40k_ade20k-512x512.py new file mode 100644 index 0000000..cf64a71 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b1_fpn_8xb4-40k_ade20k-512x512.py @@ -0,0 +1,6 @@ +_base_ = './van-b2_fpn_8xb4-40k_ade20k-512x512.py' +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b1_3rdparty_20230522-3adb117f.pth' # noqa +model = dict( + backbone=dict( + depths=[2, 2, 4, 2], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path))) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b2_fpn_8xb4-40k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b2_fpn_8xb4-40k_ade20k-512x512.py new file mode 100644 index 0000000..965fa1c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b2_fpn_8xb4-40k_ade20k-512x512.py @@ -0,0 +1,53 @@ +_base_ = [ + '../_base_/models/van_fpn.py', + '../_base_/datasets/ade20k.py', + '../../../../configs/_base_/default_runtime.py', +] +custom_imports = dict(imports=['projects.van.backbones']) +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b2_3rdparty_20230522-636fac93.pth' # noqa +model = dict( + type='EncoderDecoder', + backbone=dict( + embed_dims=[64, 128, 320, 512], + depths=[3, 3, 12, 3], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path), + drop_path_rate=0.2), + neck=dict(in_channels=[64, 128, 320, 512]), + decode_head=dict(num_classes=150)) + +train_dataloader = dict(batch_size=4) + +# we use 8 gpu instead of 4 in mmsegmentation, so lr*2 and max_iters/2 +gpu_multiples = 2 +max_iters = 80000 // gpu_multiples +interval = 8000 // gpu_multiples +optim_wrapper = dict( + type='OptimWrapper', + optimizer=dict( + type='AdamW', + lr=0.0001 * gpu_multiples, + # betas=(0.9, 0.999), + weight_decay=0.0001), + clip_grad=None) +# learning policy +param_scheduler = [ + dict( + type='PolyLR', + power=0.9, + eta_min=0.0, + begin=0, + end=max_iters, + by_epoch=False, + ) +] +train_cfg = dict( + type='IterBasedTrainLoop', max_iters=max_iters, val_interval=interval) +val_cfg = dict(type='ValLoop') +test_cfg = dict(type='TestLoop') +default_hooks = dict( + timer=dict(type='IterTimerHook'), + logger=dict(type='LoggerHook', interval=50, log_metric_by_epoch=False), + param_scheduler=dict(type='ParamSchedulerHook'), + checkpoint=dict(type='CheckpointHook', by_epoch=False, interval=interval), + sampler_seed=dict(type='DistSamplerSeedHook'), + visualization=dict(type='SegVisualizationHook')) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b2_upernet_4xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b2_upernet_4xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..c529606 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b2_upernet_4xb2-160k_ade20k-512x512.py @@ -0,0 +1,46 @@ +_base_ = [ + '../_base_/models/van_upernet.py', '../_base_/datasets/ade20k.py', + '../../../../configs/_base_/default_runtime.py', + '../../../../configs/_base_/schedules/schedule_160k.py' +] +custom_imports = dict(imports=['projects.van.backbones']) +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b2_3rdparty_20230522-636fac93.pth' # noqa +model = dict( + type='EncoderDecoder', + backbone=dict( + embed_dims=[64, 128, 320, 512], + depths=[3, 3, 12, 3], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path)), + decode_head=dict(in_channels=[64, 128, 320, 512], num_classes=150), + auxiliary_head=dict(in_channels=320, num_classes=150)) + +# AdamW optimizer +# no weight decay for position embedding & layer norm in backbone +optim_wrapper = dict( + _delete_=True, + type='OptimWrapper', + optimizer=dict( + type='AdamW', lr=0.00006, betas=(0.9, 0.999), weight_decay=0.01), + clip_grad=None, + paramwise_cfg=dict( + custom_keys={ + 'absolute_pos_embed': dict(decay_mult=0.), + 'relative_position_bias_table': dict(decay_mult=0.), + 'norm': dict(decay_mult=0.) + })) +# learning policy +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=_base_.train_cfg.max_iters, + eta_min=0.0, + by_epoch=False, + ) +] + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=2) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b3_fpn_8xb4-40k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b3_fpn_8xb4-40k_ade20k-512x512.py new file mode 100644 index 0000000..b0493fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b3_fpn_8xb4-40k_ade20k-512x512.py @@ -0,0 +1,11 @@ +_base_ = './van-b2_fpn_8xb4-40k_ade20k-512x512.py' +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b3_3rdparty_20230522-a184e051.pth' # noqa +model = dict( + type='EncoderDecoder', + backbone=dict( + embed_dims=[64, 128, 320, 512], + depths=[3, 5, 27, 3], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path), + drop_path_rate=0.3), + neck=dict(in_channels=[64, 128, 320, 512])) +train_dataloader = dict(batch_size=4) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b3_upernet_4xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b3_upernet_4xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..8201801 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b3_upernet_4xb2-160k_ade20k-512x512.py @@ -0,0 +1,8 @@ +_base_ = './van-b2_upernet_4xb2-160k_ade20k-512x512.py' +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b3_3rdparty_20230522-a184e051.pth' # noqa +model = dict( + type='EncoderDecoder', + backbone=dict( + depths=[3, 5, 27, 3], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path), + drop_path_rate=0.3)) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b4-in22kpre_upernet_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b4-in22kpre_upernet_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..15c8f7c --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b4-in22kpre_upernet_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = './van-b2_upernet_4xb2-160k_ade20k-512x512.py' +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b4-in22k_3rdparty_20230522-5e31cafb.pth' # noqa +model = dict( + backbone=dict( + depths=[3, 6, 40, 3], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path), + drop_path_rate=0.4)) + +# By default, models are trained on 8 GPUs with 2 images per GPU +train_dataloader = dict(batch_size=4) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b4_upernet_4xb4-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b4_upernet_4xb4-160k_ade20k-512x512.py new file mode 100644 index 0000000..33ae049 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b4_upernet_4xb4-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = './van-b2_upernet_4xb2-160k_ade20k-512x512.py' +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b4_3rdparty_20230522-1d71c077.pth' # noqa +model = dict( + backbone=dict( + depths=[3, 6, 40, 3], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path), + drop_path_rate=0.4)) + +# By default, models are trained on 4 GPUs with 4 images per GPU +train_dataloader = dict(batch_size=4) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b5-in22kpre_upernet_4xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b5-in22kpre_upernet_4xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..f36c624 --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b5-in22kpre_upernet_4xb2-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = './van-b2_upernet_4xb2-160k_ade20k-512x512.py' +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b5-in22k_3rdparty_20230522-b26134d7.pth' # noqa +model = dict( + backbone=dict( + embed_dims=[96, 192, 480, 768], + depths=[3, 3, 24, 3], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path), + drop_path_rate=0.4), + decode_head=dict(in_channels=[96, 192, 480, 768], num_classes=150), + auxiliary_head=dict(in_channels=480, num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b6-in22kpre_upernet_4xb2-160k_ade20k-512x512.py b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b6-in22kpre_upernet_4xb2-160k_ade20k-512x512.py new file mode 100644 index 0000000..aa529ef --- /dev/null +++ b/Seg_All_In_One_MMSeg/projects/van/configs/van/van-b6-in22kpre_upernet_4xb2-160k_ade20k-512x512.py @@ -0,0 +1,10 @@ +_base_ = './van-b2_upernet_4xb2-160k_ade20k-512x512.py' +ckpt_path = 'https://download.openmmlab.com/mmsegmentation/v0.5/van_3rdparty/van-b6-in22k_3rdparty_20230522-5e5172a3.pth' # noqa +model = dict( + backbone=dict( + embed_dims=[96, 192, 384, 768], + depths=[6, 6, 90, 6], + init_cfg=dict(type='Pretrained', checkpoint=ckpt_path), + drop_path_rate=0.5), + decode_head=dict(in_channels=[96, 192, 384, 768], num_classes=150), + auxiliary_head=dict(in_channels=384, num_classes=150)) diff --git a/Seg_All_In_One_MMSeg/requirements.txt b/Seg_All_In_One_MMSeg/requirements.txt new file mode 100644 index 0000000..501bddc --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements.txt @@ -0,0 +1,4 @@ +-r requirements/optional.txt +-r requirements/runtime.txt +-r requirements/tests.txt +-r requirements/multimodal.txt diff --git a/Seg_All_In_One_MMSeg/requirements/albu.txt b/Seg_All_In_One_MMSeg/requirements/albu.txt new file mode 100644 index 0000000..f421fbb --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements/albu.txt @@ -0,0 +1 @@ +albumentations>=0.3.2 --no-binary qudida,albumentations diff --git a/Seg_All_In_One_MMSeg/requirements/docs.txt b/Seg_All_In_One_MMSeg/requirements/docs.txt new file mode 100644 index 0000000..19632d3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements/docs.txt @@ -0,0 +1,7 @@ +docutils==0.16.0 +myst-parser +-e git+https://github.com/open-mmlab/pytorch_sphinx_theme.git#egg=pytorch_sphinx_theme +sphinx==4.0.2 +sphinx_copybutton +sphinx_markdown_tables +urllib3<2.0.0 diff --git a/Seg_All_In_One_MMSeg/requirements/mminstall.txt b/Seg_All_In_One_MMSeg/requirements/mminstall.txt new file mode 100644 index 0000000..5732d34 --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements/mminstall.txt @@ -0,0 +1,2 @@ +mmcv>=2.0.0rc4,<2.2.0 +mmengine>=0.5.0,<1.0.0 diff --git a/Seg_All_In_One_MMSeg/requirements/multimodal.txt b/Seg_All_In_One_MMSeg/requirements/multimodal.txt new file mode 100644 index 0000000..2195d0d --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements/multimodal.txt @@ -0,0 +1,2 @@ +ftfy +regex diff --git a/Seg_All_In_One_MMSeg/requirements/optional.txt b/Seg_All_In_One_MMSeg/requirements/optional.txt new file mode 100644 index 0000000..b0310f5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements/optional.txt @@ -0,0 +1,22 @@ +cityscapesscripts +-e git+https://github.com/openai/CLIP.git@main#egg=clip + +# for vpd model +diffusers +einops==0.3.0 +imageio==2.9.0 +imageio-ffmpeg==0.4.2 +invisible-watermark +kornia==0.6 +-e git+https://github.com/CompVis/stable-diffusion@21f890f#egg=latent-diffusion +nibabel +omegaconf==2.1.1 +pudb==2019.2 +pytorch-lightning==1.4.2 +streamlit>=0.73.1 +-e git+https://github.com/CompVis/taming-transformers.git@master#egg=taming-transformers +test-tube>=0.7.5 +timm +torch-fidelity==0.3.0 +torchmetrics==0.6.0 +transformers==4.19.2 diff --git a/Seg_All_In_One_MMSeg/requirements/readthedocs.txt b/Seg_All_In_One_MMSeg/requirements/readthedocs.txt new file mode 100644 index 0000000..9627504 --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements/readthedocs.txt @@ -0,0 +1,6 @@ +mmcv>=2.0.0rc1,<2.1.0 +mmengine>=0.4.0,<1.0.0 +prettytable +scipy +torch +torchvision diff --git a/Seg_All_In_One_MMSeg/requirements/runtime.txt b/Seg_All_In_One_MMSeg/requirements/runtime.txt new file mode 100644 index 0000000..3e24258 --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements/runtime.txt @@ -0,0 +1,5 @@ +matplotlib +numpy +packaging +prettytable +scipy diff --git a/Seg_All_In_One_MMSeg/requirements/tests.txt b/Seg_All_In_One_MMSeg/requirements/tests.txt new file mode 100644 index 0000000..3fff252 --- /dev/null +++ b/Seg_All_In_One_MMSeg/requirements/tests.txt @@ -0,0 +1,8 @@ +codecov +flake8 +ftfy +interrogate +pytest +regex +xdoctest>=0.10.0 +yapf diff --git a/Seg_All_In_One_MMSeg/setup.cfg b/Seg_All_In_One_MMSeg/setup.cfg new file mode 100644 index 0000000..2ea0760 --- /dev/null +++ b/Seg_All_In_One_MMSeg/setup.cfg @@ -0,0 +1,19 @@ +[yapf] +based_on_style = pep8 +blank_line_before_nested_class_or_def = true +split_before_expression_after_opening_paren = true + +[isort] +line_length = 79 +multi_line_output = 0 +extra_standard_library = setuptools +known_first_party = mmseg +known_third_party = PIL,cityscapesscripts,cv2,detail,matplotlib,mmcv,numpy,onnxruntime,packaging,prettytable,pytest,pytorch_sphinx_theme,requests,scipy,seaborn,torch,ts +no_lines_before = STDLIB,LOCALFOLDER +default_section = THIRDPARTY + +[codespell] +skip = *.po,*.ts,*.ipynb +count = +quiet-level = 3 +ignore-words-list = formating,sur,hist,dota,warmup,damon diff --git a/Seg_All_In_One_MMSeg/setup.py b/Seg_All_In_One_MMSeg/setup.py new file mode 100644 index 0000000..45d923d --- /dev/null +++ b/Seg_All_In_One_MMSeg/setup.py @@ -0,0 +1,200 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +import platform +import shutil +import sys +import warnings +from setuptools import find_packages, setup + + +def readme(): + with open('README.md', encoding='utf-8') as f: + content = f.read() + return content + + +version_file = 'mmseg/version.py' + + +def get_version(): + with open(version_file) as f: + exec(compile(f.read(), version_file, 'exec')) + return locals()['__version__'] + + +def parse_requirements(fname='requirements.txt', with_version=True): + """Parse the package dependencies listed in a requirements file but strips + specific versioning information. + + Args: + fname (str): path to requirements file + with_version (bool, default=False): if True include version specs + + Returns: + List[str]: list of requirements items + + CommandLine: + python -c "import setup; print(setup.parse_requirements())" + """ + import re + import sys + from os.path import exists + require_fpath = fname + + def parse_line(line): + """Parse information from a line in a requirements text file.""" + if line.startswith('-r '): + # Allow specifying requirements in other files + target = line.split(' ')[1] + for info in parse_require_file(target): + yield info + else: + info = {'line': line} + if line.startswith('-e '): + info['package'] = line.split('#egg=')[1] + else: + # Remove versioning from the package + pat = '(' + '|'.join(['>=', '==', '>']) + ')' + parts = re.split(pat, line, maxsplit=1) + parts = [p.strip() for p in parts] + + info['package'] = parts[0] + if len(parts) > 1: + op, rest = parts[1:] + if ';' in rest: + # Handle platform specific dependencies + # http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies + version, platform_deps = map(str.strip, + rest.split(';')) + info['platform_deps'] = platform_deps + else: + version = rest # NOQA + info['version'] = (op, version) + yield info + + def parse_require_file(fpath): + with open(fpath) as f: + for line in f.readlines(): + line = line.strip() + if line and not line.startswith('#'): + yield from parse_line(line) + + def gen_packages_items(): + if exists(require_fpath): + for info in parse_require_file(require_fpath): + parts = [info['package']] + if with_version and 'version' in info: + parts.extend(info['version']) + if not sys.version.startswith('3.4'): + # apparently package_deps are broken in 3.4 + platform_deps = info.get('platform_deps') + if platform_deps is not None: + parts.append(';' + platform_deps) + item = ''.join(parts) + yield item + + packages = list(gen_packages_items()) + return packages + + +def add_mim_extension(): + """Add extra files that are required to support MIM into the package. + + These files will be added by creating a symlink to the originals if the + package is installed in `editable` mode (e.g. pip install -e .), or by + copying from the originals otherwise. + """ + + # parse installment mode + if 'develop' in sys.argv: + # installed by `pip install -e .` + if platform.system() == 'Windows': + # set `copy` mode here since symlink fails on Windows. + mode = 'copy' + else: + mode = 'symlink' + elif 'sdist' in sys.argv or 'bdist_wheel' in sys.argv or \ + platform.system() == 'Windows': + # installed by `pip install .` + # or create source distribution by `python setup.py sdist` + # set `copy` mode here since symlink fails with WinError on Windows. + mode = 'copy' + else: + return + + filenames = ['tools', 'configs', 'model-index.yml', 'dataset-index.yml'] + repo_path = osp.dirname(__file__) + mim_path = osp.join(repo_path, 'mmseg', '.mim') + os.makedirs(mim_path, exist_ok=True) + + for filename in filenames: + if osp.exists(filename): + src_path = osp.join(repo_path, filename) + tar_path = osp.join(mim_path, filename) + + if osp.isfile(tar_path) or osp.islink(tar_path): + os.remove(tar_path) + elif osp.isdir(tar_path): + shutil.rmtree(tar_path) + + if mode == 'symlink': + src_relpath = osp.relpath(src_path, osp.dirname(tar_path)) + try: + os.symlink(src_relpath, tar_path) + except OSError: + # Creating a symbolic link on windows may raise an + # `OSError: [WinError 1314]` due to privilege. If + # the error happens, the src file will be copied + mode = 'copy' + warnings.warn( + f'Failed to create a symbolic link for {src_relpath}, ' + f'and it will be copied to {tar_path}') + else: + continue + + if mode == 'copy': + if osp.isfile(src_path): + shutil.copyfile(src_path, tar_path) + elif osp.isdir(src_path): + shutil.copytree(src_path, tar_path) + else: + warnings.warn(f'Cannot copy file {src_path}.') + else: + raise ValueError(f'Invalid mode {mode}') + + +if __name__ == '__main__': + add_mim_extension() + setup( + name='mmsegmentation', + version=get_version(), + description='Open MMLab Semantic Segmentation Toolbox and Benchmark', + long_description=readme(), + long_description_content_type='text/markdown', + author='MMSegmentation Contributors', + author_email='openmmlab@gmail.com', + keywords='computer vision, semantic segmentation', + url='https://github.com/open-mmlab/mmsegmentation', + packages=find_packages(exclude=('configs', 'tools', 'demo')), + include_package_data=True, + classifiers=[ + 'Development Status :: 4 - Beta', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + ], + license='Apache License 2.0', + install_requires=parse_requirements('requirements/runtime.txt'), + extras_require={ + 'all': parse_requirements('requirements.txt'), + 'tests': parse_requirements('requirements/tests.txt'), + 'optional': parse_requirements('requirements/optional.txt'), + 'mim': parse_requirements('requirements/mminstall.txt'), + 'multimodal': parse_requirements('requirements/multimodal.txt'), + }, + ext_modules=[], + zip_safe=False) diff --git a/Seg_All_In_One_MMSeg/tests/__init__.py b/Seg_All_In_One_MMSeg/tests/__init__.py new file mode 100644 index 0000000..ef101fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/__init__.py @@ -0,0 +1 @@ +# Copyright (c) OpenMMLab. All rights reserved. diff --git a/Seg_All_In_One_MMSeg/tests/test_apis/test_inferencer.py b/Seg_All_In_One_MMSeg/tests/test_apis/test_inferencer.py new file mode 100644 index 0000000..d8dbce8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_apis/test_inferencer.py @@ -0,0 +1,60 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import tempfile + +import numpy as np +import torch +from mmengine import ConfigDict +from utils import * # noqa: F401, F403 + +from mmseg.apis import MMSegInferencer +from mmseg.registry import MODELS +from mmseg.utils import register_all_modules + + +def test_inferencer(): + register_all_modules() + + visualizer = dict( + type='SegLocalVisualizer', + vis_backends=[dict(type='LocalVisBackend')], + name='visualizer') + + cfg_dict = dict( + model=dict( + type='InferExampleModel', + data_preprocessor=dict(type='SegDataPreProcessor'), + backbone=dict(type='InferExampleBackbone'), + decode_head=dict(type='InferExampleHead'), + test_cfg=dict(mode='whole')), + visualizer=visualizer, + test_dataloader=dict( + dataset=dict( + type='ExampleDataset', + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') + ]), )) + cfg = ConfigDict(cfg_dict) + model = MODELS.build(cfg.model) + + ckpt = model.state_dict() + ckpt_filename = tempfile.mktemp() + torch.save(ckpt, ckpt_filename) + + # test initialization + infer = MMSegInferencer(cfg, ckpt_filename) + + # test forward + img = np.random.randint(0, 256, (4, 4, 3)) + infer(img) + + imgs = [img, img] + infer(imgs) + results = infer(imgs, out_dir=tempfile.gettempdir()) + + # test results + assert 'predictions' in results + assert 'visualization' in results + assert len(results['predictions']) == 2 + assert results['predictions'][0].shape == (4, 4) diff --git a/Seg_All_In_One_MMSeg/tests/test_apis/test_rs_inferencer.py b/Seg_All_In_One_MMSeg/tests/test_apis/test_rs_inferencer.py new file mode 100644 index 0000000..03423d9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_apis/test_rs_inferencer.py @@ -0,0 +1,73 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +from unittest import TestCase + +import numpy as np +from mmengine import ConfigDict, init_default_scope +from utils import * # noqa: F401, F403 + +from mmseg.apis import RSImage, RSInferencer +from mmseg.registry import MODELS + + +class TestRSImage(TestCase): + + def test_read_whole_image(self): + init_default_scope('mmseg') + img_path = osp.join( + osp.dirname(__file__), + '../data/pseudo_loveda_dataset/img_dir/0.png') + rs_image = RSImage(img_path) + window_size = (16, 16) + rs_image.create_grids(window_size) + image_data = rs_image.read(rs_image.grids[0]) + self.assertIsNotNone(image_data) + + def test_write_image_data(self): + init_default_scope('mmseg') + img_path = osp.join( + osp.dirname(__file__), + '../data/pseudo_loveda_dataset/img_dir/0.png') + rs_image = RSImage(img_path) + window_size = (16, 16) + rs_image.create_grids(window_size) + data = np.random.random((16, 16)).astype(np.int8) + rs_image.write(data, rs_image.grids[0]) + + +class TestRSInferencer(TestCase): + + def test_read_and_inference(self): + init_default_scope('mmseg') + cfg_dict = dict( + model=dict( + type='InferExampleModel', + data_preprocessor=dict(type='SegDataPreProcessor'), + backbone=dict(type='InferExampleBackbone'), + decode_head=dict(type='InferExampleHead'), + test_cfg=dict(mode='whole')), + test_dataloader=dict( + dataset=dict( + type='ExampleDataset', + pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') + ])), + test_pipeline=[ + dict(type='LoadImageFromFile'), + dict(type='LoadAnnotations'), + dict(type='PackSegInputs') + ]) + cfg = ConfigDict(cfg_dict) + model = MODELS.build(cfg.model) + model.cfg = cfg + inferencer = RSInferencer.from_model(model) + + img_path = osp.join( + osp.dirname(__file__), + '../data/pseudo_loveda_dataset/img_dir/0.png') + rs_image = RSImage(img_path) + window_size = (16, 16) + stride = (16, 16) + inferencer.run(rs_image, window_size, stride) diff --git a/Seg_All_In_One_MMSeg/tests/test_apis/utils.py b/Seg_All_In_One_MMSeg/tests/test_apis/utils.py new file mode 100644 index 0000000..0a9928f --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_apis/utils.py @@ -0,0 +1,38 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch.nn as nn + +from mmseg.models import EncoderDecoder +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.registry import MODELS + + +@MODELS.register_module(name='InferExampleHead') +class ExampleDecodeHead(BaseDecodeHead): + + def __init__(self, num_classes=19, out_channels=None): + super().__init__( + 3, 3, num_classes=num_classes, out_channels=out_channels) + + def forward(self, inputs): + return self.cls_seg(inputs[0]) + + +@MODELS.register_module(name='InferExampleBackbone') +class ExampleBackbone(nn.Module): + + def __init__(self): + super().__init__() + self.conv = nn.Conv2d(3, 3, 3) + + def init_weights(self, pretrained=None): + pass + + def forward(self, x): + return [self.conv(x)] + + +@MODELS.register_module(name='InferExampleModel') +class ExampleModel(EncoderDecoder): + + def __init__(self, **kwargs): + super().__init__(**kwargs) diff --git a/Seg_All_In_One_MMSeg/tests/test_config.py b/Seg_All_In_One_MMSeg/tests/test_config.py new file mode 100644 index 0000000..cdd85ff --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_config.py @@ -0,0 +1,175 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import glob +import os +from os.path import dirname, exists, isdir, join, relpath + +import numpy as np +from mmengine import Config +from mmengine.dataset import Compose +from mmengine.registry import init_default_scope +from torch import nn + +from mmseg.models import build_segmentor + + +def _get_config_directory(): + """Find the predefined segmentor config directory.""" + try: + # Assume we are running in the source mmsegmentation repo + repo_dpath = dirname(dirname(__file__)) + except NameError: + # For IPython development when this __file__ is not defined + import mmseg + repo_dpath = dirname(dirname(mmseg.__file__)) + config_dpath = join(repo_dpath, 'configs') + if not exists(config_dpath): + raise Exception('Cannot find config path') + return config_dpath + + +def test_config_build_segmentor(): + """Test that all segmentation models defined in the configs can be + initialized.""" + init_default_scope('mmseg') + config_dpath = _get_config_directory() + print(f'Found config_dpath = {config_dpath!r}') + + config_fpaths = [] + # one config each sub folder + for sub_folder in os.listdir(config_dpath): + if isdir(sub_folder): + config_fpaths.append( + list(glob.glob(join(config_dpath, sub_folder, '*.py')))[0]) + config_fpaths = [p for p in config_fpaths if p.find('_base_') == -1] + config_names = [relpath(p, config_dpath) for p in config_fpaths] + + print(f'Using {len(config_names)} config files') + + for config_fname in config_names: + config_fpath = join(config_dpath, config_fname) + config_mod = Config.fromfile(config_fpath) + + config_mod.model + print(f'Building segmentor, config_fpath = {config_fpath!r}') + + # Remove pretrained keys to allow for testing in an offline environment + if 'pretrained' in config_mod.model: + config_mod.model['pretrained'] = None + + print(f'building {config_fname}') + segmentor = build_segmentor(config_mod.model) + assert segmentor is not None + + head_config = config_mod.model['decode_head'] + _check_decode_head(head_config, segmentor.decode_head) + + +def test_config_data_pipeline(): + """Test whether the data pipeline is valid and can process corner cases. + + CommandLine: + xdoctest -m tests/test_config.py test_config_build_data_pipeline + """ + + init_default_scope('mmseg') + config_dpath = _get_config_directory() + print(f'Found config_dpath = {config_dpath!r}') + + import glob + config_fpaths = list(glob.glob(join(config_dpath, '**', '*.py'))) + config_fpaths = [p for p in config_fpaths if p.find('_base_') == -1] + config_names = [relpath(p, config_dpath) for p in config_fpaths] + + print(f'Using {len(config_names)} config files') + + for config_fname in config_names: + config_fpath = join(config_dpath, config_fname) + print(f'Building data pipeline, config_fpath = {config_fpath!r}') + config_mod = Config.fromfile(config_fpath) + + # remove loading pipeline + load_img_pipeline = config_mod.train_pipeline.pop(0) + to_float32 = load_img_pipeline.get('to_float32', False) + del config_mod.train_pipeline[0] + del config_mod.test_pipeline[0] + # remove loading annotation in test pipeline + load_anno_idx = -1 + for i in range(len(config_mod.test_pipeline)): + if config_mod.test_pipeline[i].type in ('LoadAnnotations', + 'LoadDepthAnnotation'): + load_anno_idx = i + del config_mod.test_pipeline[load_anno_idx] + + train_pipeline = Compose(config_mod.train_pipeline) + test_pipeline = Compose(config_mod.test_pipeline) + + img = np.random.randint(0, 255, size=(1024, 2048, 3), dtype=np.uint8) + if to_float32: + img = img.astype(np.float32) + seg = np.random.randint(0, 255, size=(1024, 2048, 1), dtype=np.uint8) + depth = np.random.rand(1024, 2048).astype(np.float32) + + results = dict( + filename='test_img.png', + ori_filename='test_img.png', + img=img, + img_shape=img.shape, + ori_shape=img.shape, + gt_seg_map=seg, + gt_depth_map=depth) + results['seg_fields'] = ['gt_seg_map'] + _check_concat_cd_input(config_mod, results) + print(f'Test training data pipeline: \n{train_pipeline!r}') + output_results = train_pipeline(results) + assert output_results is not None + + _check_concat_cd_input(config_mod, results) + print(f'Test testing data pipeline: \n{test_pipeline!r}') + output_results = test_pipeline(results) + assert output_results is not None + + +def _check_concat_cd_input(config_mod: Config, results: dict): + keys = [] + pipeline = config_mod.train_pipeline.copy() + pipeline.extend(config_mod.test_pipeline) + for t in pipeline: + keys.append(t.type) + if 'ConcatCDInput' in keys: + results.update({'img2': results['img']}) + + +def _check_decode_head(decode_head_cfg, decode_head): + if isinstance(decode_head_cfg, list): + assert isinstance(decode_head, nn.ModuleList) + assert len(decode_head_cfg) == len(decode_head) + num_heads = len(decode_head) + for i in range(num_heads): + _check_decode_head(decode_head_cfg[i], decode_head[i]) + return + # check consistency between head_config and roi_head + assert decode_head_cfg['type'] == decode_head.__class__.__name__ + + assert decode_head_cfg['type'] == decode_head.__class__.__name__ + + in_channels = decode_head_cfg.in_channels + input_transform = decode_head.input_transform + assert input_transform in ['resize_concat', 'multiple_select', None] + if input_transform is not None: + assert isinstance(in_channels, (list, tuple)) + assert isinstance(decode_head.in_index, (list, tuple)) + assert len(in_channels) == len(decode_head.in_index) + elif input_transform == 'resize_concat': + assert sum(in_channels) == decode_head.in_channels + else: + assert in_channels == decode_head.in_channels + + if decode_head_cfg['type'] == 'PointHead': + assert decode_head_cfg.channels+decode_head_cfg.num_classes == \ + decode_head.fc_seg.in_channels + assert decode_head.fc_seg.out_channels == decode_head_cfg.num_classes + elif decode_head_cfg['type'] == 'VPDDepthHead': + assert decode_head.out_channels == 1 + else: + assert decode_head_cfg.channels == decode_head.conv_seg.in_channels + assert decode_head.conv_seg.out_channels == decode_head_cfg.num_classes diff --git a/Seg_All_In_One_MMSeg/tests/test_datasets/test_dataset.py b/Seg_All_In_One_MMSeg/tests/test_datasets/test_dataset.py new file mode 100644 index 0000000..2904e09 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_datasets/test_dataset.py @@ -0,0 +1,475 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +import tempfile + +import pytest + +from mmseg.datasets import (ADE20KDataset, BaseSegDataset, BDD100KDataset, + CityscapesDataset, COCOStuffDataset, + DecathlonDataset, DSDLSegDataset, ISPRSDataset, + LIPDataset, LoveDADataset, MapillaryDataset_v1, + MapillaryDataset_v2, NYUDataset, PascalVOCDataset, + PotsdamDataset, REFUGEDataset, SynapseDataset, + iSAIDDataset) +from mmseg.registry import DATASETS +from mmseg.utils import get_classes, get_palette + +try: + from dsdl.dataset import DSDLDataset +except ImportError: + DSDLDataset = None + + +def test_classes(): + assert list( + CityscapesDataset.METAINFO['classes']) == get_classes('cityscapes') + assert list(PascalVOCDataset.METAINFO['classes']) == get_classes( + 'voc') == get_classes('pascal_voc') + assert list(ADE20KDataset.METAINFO['classes']) == get_classes( + 'ade') == get_classes('ade20k') + assert list( + COCOStuffDataset.METAINFO['classes']) == get_classes('cocostuff') + assert list(LoveDADataset.METAINFO['classes']) == get_classes('loveda') + assert list(PotsdamDataset.METAINFO['classes']) == get_classes('potsdam') + assert list(ISPRSDataset.METAINFO['classes']) == get_classes('vaihingen') + assert list(iSAIDDataset.METAINFO['classes']) == get_classes('isaid') + assert list( + MapillaryDataset_v1.METAINFO['classes']) == get_classes('mapillary_v1') + assert list( + MapillaryDataset_v2.METAINFO['classes']) == get_classes('mapillary_v2') + assert list(BDD100KDataset.METAINFO['classes']) == get_classes('bdd100k') + with pytest.raises(ValueError): + get_classes('unsupported') + + +def test_classes_file_path(): + tmp_file = tempfile.NamedTemporaryFile() + classes_path = f'{tmp_file.name}.txt' + train_pipeline = [] + kwargs = dict( + pipeline=train_pipeline, + data_prefix=dict(img_path='./', seg_map_path='./'), + metainfo=dict(classes=classes_path)) + + # classes.txt with full categories + categories = get_classes('cityscapes') + with open(classes_path, 'w') as f: + f.write('\n'.join(categories)) + dataset = CityscapesDataset(**kwargs) + assert list(dataset.metainfo['classes']) == categories + assert dataset.label_map is None + + # classes.txt with sub categories + categories = ['road', 'sidewalk', 'building'] + with open(classes_path, 'w') as f: + f.write('\n'.join(categories)) + dataset = CityscapesDataset(**kwargs) + assert list(dataset.metainfo['classes']) == categories + assert dataset.label_map is not None + + # classes.txt with unknown categories + categories = ['road', 'sidewalk', 'unknown'] + with open(classes_path, 'w') as f: + f.write('\n'.join(categories)) + + with pytest.raises(ValueError): + CityscapesDataset(**kwargs) + + tmp_file.close() + os.remove(classes_path) + assert not osp.exists(classes_path) + + +def test_palette(): + assert CityscapesDataset.METAINFO['palette'] == get_palette('cityscapes') + assert PascalVOCDataset.METAINFO['palette'] == get_palette( + 'voc') == get_palette('pascal_voc') + assert ADE20KDataset.METAINFO['palette'] == get_palette( + 'ade') == get_palette('ade20k') + assert LoveDADataset.METAINFO['palette'] == get_palette('loveda') + assert PotsdamDataset.METAINFO['palette'] == get_palette('potsdam') + assert COCOStuffDataset.METAINFO['palette'] == get_palette('cocostuff') + assert iSAIDDataset.METAINFO['palette'] == get_palette('isaid') + assert list( + MapillaryDataset_v1.METAINFO['palette']) == get_palette('mapillary_v1') + assert list( + MapillaryDataset_v2.METAINFO['palette']) == get_palette('mapillary_v2') + assert list(BDD100KDataset.METAINFO['palette']) == get_palette('bdd100k') + with pytest.raises(ValueError): + get_palette('unsupported') + + +def test_custom_dataset(): + + # with 'img_path' and 'seg_map_path' in data_prefix + train_dataset = BaseSegDataset( + data_root=osp.join(osp.dirname(__file__), '../data/pseudo_dataset'), + data_prefix=dict( + img_path='imgs/', + seg_map_path='gts/', + ), + img_suffix='img.jpg', + seg_map_suffix='gt.png') + assert len(train_dataset) == 5 + + # with 'img_path' and 'seg_map_path' in data_prefix and ann_file + train_dataset = BaseSegDataset( + data_root=osp.join(osp.dirname(__file__), '../data/pseudo_dataset'), + data_prefix=dict( + img_path='imgs/', + seg_map_path='gts/', + ), + img_suffix='img.jpg', + seg_map_suffix='gt.png', + ann_file='splits/train.txt') + assert len(train_dataset) == 4 + + # no data_root + train_dataset = BaseSegDataset( + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), '../data/pseudo_dataset/imgs'), + seg_map_path=osp.join( + osp.dirname(__file__), '../data/pseudo_dataset/gts')), + img_suffix='img.jpg', + seg_map_suffix='gt.png') + assert len(train_dataset) == 5 + + # with data_root but 'img_path' and 'seg_map_path' in data_prefix are + # abs path + train_dataset = BaseSegDataset( + data_root=osp.join(osp.dirname(__file__), '../data/pseudo_dataset'), + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), '../data/pseudo_dataset/imgs'), + seg_map_path=osp.join( + osp.dirname(__file__), '../data/pseudo_dataset/gts')), + img_suffix='img.jpg', + seg_map_suffix='gt.png') + assert len(train_dataset) == 5 + + # test_mode=True + test_dataset = BaseSegDataset( + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), '../data/pseudo_dataset/imgs')), + img_suffix='img.jpg', + test_mode=True, + metainfo=dict(classes=('pseudo_class', ))) + assert len(test_dataset) == 5 + + # training data get + train_data = train_dataset[0] + assert isinstance(train_data, dict) + assert 'img_path' in train_data and osp.isfile(train_data['img_path']) + assert 'seg_map_path' in train_data and osp.isfile( + train_data['seg_map_path']) + + # test data get + test_data = test_dataset[0] + assert isinstance(test_data, dict) + assert 'img_path' in train_data and osp.isfile(train_data['img_path']) + assert 'seg_map_path' in train_data and osp.isfile( + train_data['seg_map_path']) + + +def test_ade(): + test_dataset = ADE20KDataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), '../data/pseudo_dataset/imgs'))) + assert len(test_dataset) == 5 + + +def test_cityscapes(): + test_dataset = CityscapesDataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_cityscapes_dataset/leftImg8bit/val'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_cityscapes_dataset/gtFine/val'))) + assert len(test_dataset) == 1 + + +def test_loveda(): + test_dataset = LoveDADataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_loveda_dataset/img_dir'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_loveda_dataset/ann_dir'))) + assert len(test_dataset) == 3 + + +def test_potsdam(): + test_dataset = PotsdamDataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_potsdam_dataset/img_dir'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_potsdam_dataset/ann_dir'))) + assert len(test_dataset) == 1 + + +def test_vaihingen(): + test_dataset = ISPRSDataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_vaihingen_dataset/img_dir'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_vaihingen_dataset/ann_dir'))) + assert len(test_dataset) == 1 + + +def test_synapse(): + test_dataset = SynapseDataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_synapse_dataset/img_dir'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_synapse_dataset/ann_dir'))) + assert len(test_dataset) == 2 + + +def test_refuge(): + test_dataset = REFUGEDataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_refuge_dataset/img_dir'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_refuge_dataset/ann_dir'))) + assert len(test_dataset) == 1 + + +def test_isaid(): + test_dataset = iSAIDDataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), '../data/pseudo_isaid_dataset/img_dir'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_isaid_dataset/ann_dir'))) + assert len(test_dataset) == 2 + test_dataset = iSAIDDataset( + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), '../data/pseudo_isaid_dataset/img_dir'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_isaid_dataset/ann_dir')), + ann_file=osp.join( + osp.dirname(__file__), + '../data/pseudo_isaid_dataset/splits/train.txt')) + assert len(test_dataset) == 1 + + +def test_decathlon(): + data_root = osp.join(osp.dirname(__file__), '../data') + # test load training dataset + test_dataset = DecathlonDataset( + pipeline=[], data_root=data_root, ann_file='dataset.json') + assert len(test_dataset) == 1 + + # test load test dataset + test_dataset = DecathlonDataset( + pipeline=[], + data_root=data_root, + ann_file='dataset.json', + test_mode=True) + assert len(test_dataset) == 3 + + +def test_lip(): + data_root = osp.join(osp.dirname(__file__), '../data/pseudo_lip_dataset') + # train load training dataset + train_dataset = LIPDataset( + pipeline=[], + data_root=data_root, + data_prefix=dict( + img_path='train_images', seg_map_path='train_segmentations')) + assert len(train_dataset) == 1 + + # test load training dataset + test_dataset = LIPDataset( + pipeline=[], + data_root=data_root, + data_prefix=dict( + img_path='val_images', seg_map_path='val_segmentations')) + assert len(test_dataset) == 1 + + +def test_mapillary(): + test_dataset = MapillaryDataset_v1( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_mapillary_dataset/images'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_mapillary_dataset/v1.2'))) + assert len(test_dataset) == 1 + + +def test_bdd100k(): + test_dataset = BDD100KDataset( + pipeline=[], + data_prefix=dict( + img_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_bdd100k_dataset/images/10k/val'), + seg_map_path=osp.join( + osp.dirname(__file__), + '../data/pseudo_bdd100k_dataset/labels/sem_seg/masks/val'))) + assert len(test_dataset) == 3 + + +@pytest.mark.parametrize('dataset, classes', [ + ('ADE20KDataset', ('wall', 'building')), + ('CityscapesDataset', ('road', 'sidewalk')), + ('BaseSegDataset', ('bus', 'car')), + ('PascalVOCDataset', ('aeroplane', 'bicycle')), +]) +def test_custom_classes_override_default(dataset, classes): + + dataset_class = DATASETS.get(dataset) + original_classes = dataset_class.METAINFO.get('classes', None) + + tmp_file = tempfile.NamedTemporaryFile() + ann_file = tmp_file.name + img_path = tempfile.mkdtemp() + + # Test setting classes as a tuple + custom_dataset = dataset_class( + data_prefix=dict(img_path=img_path), + ann_file=ann_file, + metainfo=dict(classes=classes), + test_mode=True, + lazy_init=True) + + assert custom_dataset.metainfo['classes'] != original_classes + assert custom_dataset.metainfo['classes'] == classes + if not isinstance(custom_dataset, BaseSegDataset): + assert isinstance(custom_dataset.label_map, dict) + + # Test setting classes as a list + custom_dataset = dataset_class( + data_prefix=dict(img_path=img_path), + ann_file=ann_file, + metainfo=dict(classes=list(classes)), + test_mode=True, + lazy_init=True) + + assert custom_dataset.metainfo['classes'] != original_classes + assert custom_dataset.metainfo['classes'] == list(classes) + if not isinstance(custom_dataset, BaseSegDataset): + assert isinstance(custom_dataset.label_map, dict) + + # Test overriding not a subset + custom_dataset = dataset_class( + ann_file=ann_file, + data_prefix=dict(img_path=img_path), + metainfo=dict(classes=[classes[0]]), + test_mode=True, + lazy_init=True) + + assert custom_dataset.metainfo['classes'] != original_classes + assert custom_dataset.metainfo['classes'] == [classes[0]] + if not isinstance(custom_dataset, BaseSegDataset): + assert isinstance(custom_dataset.label_map, dict) + + # Test default behavior + if dataset_class is BaseSegDataset: + with pytest.raises(AssertionError): + custom_dataset = dataset_class( + ann_file=ann_file, + data_prefix=dict(img_path=img_path), + metainfo=None, + test_mode=True, + lazy_init=True) + else: + custom_dataset = dataset_class( + data_prefix=dict(img_path=img_path), + ann_file=ann_file, + metainfo=None, + test_mode=True, + lazy_init=True) + + assert custom_dataset.METAINFO['classes'] == original_classes + assert custom_dataset.label_map is None + + +def test_custom_dataset_random_palette_is_generated(): + dataset = BaseSegDataset( + pipeline=[], + data_prefix=dict(img_path=tempfile.mkdtemp()), + ann_file=tempfile.mkdtemp(), + metainfo=dict(classes=('bus', 'car')), + lazy_init=True, + test_mode=True) + assert len(dataset.metainfo['palette']) == 2 + for class_color in dataset.metainfo['palette']: + assert len(class_color) == 3 + assert all(x >= 0 and x <= 255 for x in class_color) + + +def test_custom_dataset_custom_palette(): + dataset = BaseSegDataset( + data_prefix=dict(img_path=tempfile.mkdtemp()), + ann_file=tempfile.mkdtemp(), + metainfo=dict( + classes=('bus', 'car'), palette=[[100, 100, 100], [200, 200, + 200]]), + lazy_init=True, + test_mode=True) + assert tuple(dataset.metainfo['palette']) == tuple([[100, 100, 100], + [200, 200, 200]]) + # test custom class and palette don't match + with pytest.raises(ValueError): + dataset = BaseSegDataset( + data_prefix=dict(img_path=tempfile.mkdtemp()), + ann_file=tempfile.mkdtemp(), + metainfo=dict(classes=('bus', 'car'), palette=[[200, 200, 200]]), + lazy_init=True) + + +def test_dsdlseg_dataset(): + if DSDLDataset is not None: + dataset = DSDLSegDataset( + data_root='tests/data/dsdl_seg', ann_file='set-train/train.yaml') + assert len(dataset) == 3 + assert len(dataset.metainfo['classes']) == 21 + else: + ImportWarning('Package `dsdl` is not installed.') + + +def test_nyu_dataset(): + dataset = NYUDataset( + data_root='tests/data/pseudo_nyu_dataset', + data_prefix=dict(img_path='images', depth_map_path='annotations'), + ) + assert len(dataset) == 1 + data = dataset[0] + assert data.get('depth_map_path', None) is not None + assert data.get('category_id', -1) == 26 diff --git a/Seg_All_In_One_MMSeg/tests/test_datasets/test_dataset_builder.py b/Seg_All_In_One_MMSeg/tests/test_datasets/test_dataset_builder.py new file mode 100644 index 0000000..b67b1e7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_datasets/test_dataset_builder.py @@ -0,0 +1,152 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp + +from mmengine.dataset import ConcatDataset, RepeatDataset +from mmengine.registry import init_default_scope + +from mmseg.datasets import MultiImageMixDataset +from mmseg.registry import DATASETS + +init_default_scope('mmseg') + + +@DATASETS.register_module() +class ToyDataset: + + def __init__(self, cnt=0): + self.cnt = cnt + + def __item__(self, idx): + return idx + + def __len__(self): + return 100 + + +def test_build_dataset(): + cfg = dict(type='ToyDataset') + dataset = DATASETS.build(cfg) + assert isinstance(dataset, ToyDataset) + assert dataset.cnt == 0 + dataset = DATASETS.build(cfg, default_args=dict(cnt=1)) + assert isinstance(dataset, ToyDataset) + assert dataset.cnt == 1 + + data_root = osp.join(osp.dirname(__file__), '../data/pseudo_dataset') + data_prefix = dict(img_path='imgs/', seg_map_path='gts/') + + # test RepeatDataset + cfg = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=data_prefix, + serialize_data=False) + dataset = DATASETS.build(cfg) + dataset_repeat = RepeatDataset(dataset=dataset, times=5) + assert isinstance(dataset_repeat, RepeatDataset) + assert len(dataset_repeat) == 25 + + # test ConcatDataset + # We use same dir twice for simplicity + # with data_prefix.seg_map_path + cfg1 = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=data_prefix, + serialize_data=False) + cfg2 = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=data_prefix, + serialize_data=False) + dataset1 = DATASETS.build(cfg1) + dataset2 = DATASETS.build(cfg2) + dataset_concat = ConcatDataset(datasets=[dataset1, dataset2]) + assert isinstance(dataset_concat, ConcatDataset) + assert len(dataset_concat) == 10 + + # test MultiImageMixDataset + dataset = MultiImageMixDataset(dataset=dataset_concat, pipeline=[]) + assert isinstance(dataset, MultiImageMixDataset) + assert len(dataset) == 10 + + cfg = dict(type='ConcatDataset', datasets=[cfg1, cfg2]) + + dataset = MultiImageMixDataset(dataset=cfg, pipeline=[]) + assert isinstance(dataset, MultiImageMixDataset) + assert len(dataset) == 10 + + # with data_prefix.seg_map_path, ann_file + cfg1 = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=data_prefix, + ann_file='splits/train.txt', + serialize_data=False) + cfg2 = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=data_prefix, + ann_file='splits/val.txt', + serialize_data=False) + + dataset1 = DATASETS.build(cfg1) + dataset2 = DATASETS.build(cfg2) + dataset_concat = ConcatDataset(datasets=[dataset1, dataset2]) + assert isinstance(dataset_concat, ConcatDataset) + assert len(dataset_concat) == 5 + + # test mode + cfg1 = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=dict(img_path='imgs/'), + test_mode=True, + metainfo=dict(classes=('pseudo_class', )), + serialize_data=False) + cfg2 = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=dict(img_path='imgs/'), + test_mode=True, + metainfo=dict(classes=('pseudo_class', )), + serialize_data=False) + + dataset1 = DATASETS.build(cfg1) + dataset2 = DATASETS.build(cfg2) + dataset_concat = ConcatDataset(datasets=[dataset1, dataset2]) + assert isinstance(dataset_concat, ConcatDataset) + assert len(dataset_concat) == 10 + + # test mode with ann_files + cfg1 = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=dict(img_path='imgs/'), + ann_file='splits/val.txt', + test_mode=True, + metainfo=dict(classes=('pseudo_class', )), + serialize_data=False) + cfg2 = dict( + type='BaseSegDataset', + pipeline=[], + data_root=data_root, + data_prefix=dict(img_path='imgs/'), + ann_file='splits/val.txt', + test_mode=True, + metainfo=dict(classes=('pseudo_class', )), + serialize_data=False) + + dataset1 = DATASETS.build(cfg1) + dataset2 = DATASETS.build(cfg2) + dataset_concat = ConcatDataset(datasets=[dataset1, dataset2]) + assert isinstance(dataset_concat, ConcatDataset) + assert len(dataset_concat) == 2 diff --git a/Seg_All_In_One_MMSeg/tests/test_datasets/test_formatting.py b/Seg_All_In_One_MMSeg/tests/test_datasets/test_formatting.py new file mode 100644 index 0000000..d0e5820 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_datasets/test_formatting.py @@ -0,0 +1,61 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +import unittest + +import numpy as np +import pytest +from mmengine.structures import BaseDataElement + +from mmseg.datasets.transforms import PackSegInputs +from mmseg.structures import SegDataSample + + +class TestPackSegInputs(unittest.TestCase): + + def setUp(self): + """Setup the model and optimizer which are used in every test method. + + TestCase calls functions in this order: setUp() -> testMethod() -> + tearDown() -> cleanUp() + """ + data_prefix = osp.join(osp.dirname(__file__), '../../data') + img_path = osp.join(data_prefix, 'color.jpg') + rng = np.random.RandomState(0) + self.results = { + 'img_path': img_path, + 'ori_shape': (300, 400), + 'pad_shape': (600, 800), + 'img_shape': (600, 800), + 'scale_factor': 2.0, + 'flip': False, + 'flip_direction': 'horizontal', + 'img_norm_cfg': None, + 'img': rng.rand(300, 400), + 'gt_seg_map': rng.rand(300, 400), + } + self.meta_keys = ('img_path', 'ori_shape', 'img_shape', 'pad_shape', + 'scale_factor', 'flip', 'flip_direction') + + def test_transform(self): + transform = PackSegInputs(meta_keys=self.meta_keys) + results = transform(copy.deepcopy(self.results)) + self.assertIn('data_samples', results) + self.assertIsInstance(results['data_samples'], SegDataSample) + self.assertIsInstance(results['data_samples'].gt_sem_seg, + BaseDataElement) + self.assertEqual(results['data_samples'].ori_shape, + results['data_samples'].gt_sem_seg.shape) + results = copy.deepcopy(self.results) + # test dataset shape is not 2D + results['gt_seg_map'] = np.random.rand(3, 300, 400) + msg = 'the segmentation map is 2D' + with pytest.warns(UserWarning, match=msg): + results = transform(results) + self.assertEqual(results['data_samples'].ori_shape, + results['data_samples'].gt_sem_seg.shape) + + def test_repr(self): + transform = PackSegInputs(meta_keys=self.meta_keys) + self.assertEqual( + repr(transform), f'PackSegInputs(meta_keys={self.meta_keys})') diff --git a/Seg_All_In_One_MMSeg/tests/test_datasets/test_loading.py b/Seg_All_In_One_MMSeg/tests/test_datasets/test_loading.py new file mode 100644 index 0000000..3eea6e3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_datasets/test_loading.py @@ -0,0 +1,295 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +import tempfile + +import mmcv +import numpy as np +from mmcv.transforms import LoadImageFromFile + +from mmseg.datasets.transforms import LoadAnnotations # noqa +from mmseg.datasets.transforms import (LoadBiomedicalAnnotation, + LoadBiomedicalData, + LoadBiomedicalImageFromFile, + LoadDepthAnnotation, + LoadImageFromNDArray) + + +class TestLoading: + + @classmethod + def setup_class(cls): + cls.data_prefix = osp.join(osp.dirname(__file__), '../data') + + def test_load_img(self): + results = dict(img_path=osp.join(self.data_prefix, 'color.jpg')) + transform = LoadImageFromFile() + results = transform(copy.deepcopy(results)) + assert results['img_path'] == osp.join(self.data_prefix, 'color.jpg') + assert results['img'].shape == (288, 512, 3) + assert results['img'].dtype == np.uint8 + assert results['ori_shape'] == results['img'].shape[:2] + assert repr(transform) == transform.__class__.__name__ + \ + "(ignore_empty=False, to_float32=False, color_type='color'," + \ + " imdecode_backend='cv2', backend_args=None)" + + # to_float32 + transform = LoadImageFromFile(to_float32=True) + results = transform(copy.deepcopy(results)) + assert results['img'].dtype == np.float32 + + # gray image + results = dict(img_path=osp.join(self.data_prefix, 'gray.jpg')) + transform = LoadImageFromFile() + results = transform(copy.deepcopy(results)) + assert results['img'].shape == (288, 512, 3) + assert results['img'].dtype == np.uint8 + + transform = LoadImageFromFile(color_type='unchanged') + results = transform(copy.deepcopy(results)) + assert results['img'].shape == (288, 512) + assert results['img'].dtype == np.uint8 + + def test_load_seg(self): + seg_path = osp.join(self.data_prefix, 'seg.png') + results = dict( + seg_map_path=seg_path, reduce_zero_label=True, seg_fields=[]) + transform = LoadAnnotations() + results = transform(copy.deepcopy(results)) + assert results['gt_seg_map'].shape == (288, 512) + assert results['gt_seg_map'].dtype == np.uint8 + assert repr(transform) == transform.__class__.__name__ + \ + "(reduce_zero_label=True, imdecode_backend='pillow', " + \ + 'backend_args=None)' + + # reduce_zero_label + transform = LoadAnnotations(reduce_zero_label=True) + results = transform(copy.deepcopy(results)) + assert results['gt_seg_map'].shape == (288, 512) + assert results['gt_seg_map'].dtype == np.uint8 + + def test_load_seg_custom_classes(self): + + test_img = np.random.rand(10, 10) + test_gt = np.zeros_like(test_img) + test_gt[2:4, 2:4] = 1 + test_gt[2:4, 6:8] = 2 + test_gt[6:8, 2:4] = 3 + test_gt[6:8, 6:8] = 4 + + tmp_dir = tempfile.TemporaryDirectory() + img_path = osp.join(tmp_dir.name, 'img.jpg') + gt_path = osp.join(tmp_dir.name, 'gt.png') + + mmcv.imwrite(test_img, img_path) + mmcv.imwrite(test_gt, gt_path) + + # test only train with label with id 3 + results = dict( + img_path=img_path, + seg_map_path=gt_path, + label_map={ + 0: 0, + 1: 0, + 2: 0, + 3: 1, + 4: 0 + }, + reduce_zero_label=False, + seg_fields=[]) + + load_imgs = LoadImageFromFile() + results = load_imgs(copy.deepcopy(results)) + + load_anns = LoadAnnotations() + results = load_anns(copy.deepcopy(results)) + + gt_array = results['gt_seg_map'] + + true_mask = np.zeros_like(gt_array) + true_mask[6:8, 2:4] = 1 + + assert results['seg_fields'] == ['gt_seg_map'] + assert gt_array.shape == (10, 10) + assert gt_array.dtype == np.uint8 + np.testing.assert_array_equal(gt_array, true_mask) + + # test only train with label with id 4 and 3 + results = dict( + img_path=osp.join(self.data_prefix, 'color.jpg'), + seg_map_path=gt_path, + label_map={ + 0: 0, + 1: 0, + 2: 0, + 3: 2, + 4: 1 + }, + reduce_zero_label=False, + seg_fields=[]) + + load_imgs = LoadImageFromFile() + results = load_imgs(copy.deepcopy(results)) + + load_anns = LoadAnnotations() + results = load_anns(copy.deepcopy(results)) + + gt_array = results['gt_seg_map'] + + true_mask = np.zeros_like(gt_array) + true_mask[6:8, 2:4] = 2 + true_mask[6:8, 6:8] = 1 + + assert results['seg_fields'] == ['gt_seg_map'] + assert gt_array.shape == (10, 10) + assert gt_array.dtype == np.uint8 + np.testing.assert_array_equal(gt_array, true_mask) + + # test with removing a class and reducing zero label simultaneously + results = dict( + img_path=img_path, + seg_map_path=gt_path, + # since reduce_zero_label is True, there are only 4 real classes. + # if the full set of classes is ["A", "B", "C", "D"], the + # following label map simulates the dataset option + # classes=["A", "C", "D"] which removes class "B". + label_map={ + 0: 0, + 1: 255, # simulate removing class 1 + 2: 1, + 3: 2 + }, + reduce_zero_label=True, # reduce zero label + seg_fields=[]) + + load_imgs = LoadImageFromFile() + results = load_imgs(copy.deepcopy(results)) + + # reduce zero label + load_anns = LoadAnnotations() + results = load_anns(copy.deepcopy(results)) + + gt_array = results['gt_seg_map'] + + true_mask = np.ones_like(gt_array) * 255 # all zeros get mapped to 255 + true_mask[2:4, 2:4] = 0 # 1s are reduced to class 0 mapped to class 0 + true_mask[2:4, 6:8] = 255 # 2s are reduced to class 1 which is removed + true_mask[6:8, 2:4] = 1 # 3s are reduced to class 2 mapped to class 1 + true_mask[6:8, 6:8] = 2 # 4s are reduced to class 3 mapped to class 2 + + assert results['seg_fields'] == ['gt_seg_map'] + assert gt_array.shape == (10, 10) + assert gt_array.dtype == np.uint8 + np.testing.assert_array_equal(gt_array, true_mask) + + # test no custom classes + results = dict( + img_path=img_path, + seg_map_path=gt_path, + reduce_zero_label=False, + seg_fields=[]) + + load_imgs = LoadImageFromFile() + results = load_imgs(copy.deepcopy(results)) + + load_anns = LoadAnnotations() + results = load_anns(copy.deepcopy(results)) + + gt_array = results['gt_seg_map'] + + assert results['seg_fields'] == ['gt_seg_map'] + assert gt_array.shape == (10, 10) + assert gt_array.dtype == np.uint8 + np.testing.assert_array_equal(gt_array, test_gt) + + tmp_dir.cleanup() + + def test_load_image_from_ndarray(self): + results = {'img': np.zeros((256, 256, 3), dtype=np.uint8)} + transform = LoadImageFromNDArray() + results = transform(results) + + assert results['img'].shape == (256, 256, 3) + assert results['img'].dtype == np.uint8 + assert results['img_shape'] == (256, 256) + assert results['ori_shape'] == (256, 256) + + # to_float32 + transform = LoadImageFromNDArray(to_float32=True) + results = transform(copy.deepcopy(results)) + assert results['img'].dtype == np.float32 + + # test repr + transform = LoadImageFromNDArray() + assert repr(transform) == ('LoadImageFromNDArray(' + 'ignore_empty=False, ' + 'to_float32=False, ' + "color_type='color', " + "imdecode_backend='cv2', " + 'backend_args=None)') + + def test_load_biomedical_img(self): + results = dict( + img_path=osp.join(self.data_prefix, 'biomedical.nii.gz')) + transform = LoadBiomedicalImageFromFile() + results = transform(copy.deepcopy(results)) + assert results['img_path'] == osp.join(self.data_prefix, + 'biomedical.nii.gz') + assert len(results['img'].shape) == 4 + assert results['img'].dtype == np.float32 + assert results['ori_shape'] == results['img'].shape[1:] + assert repr(transform) == ('LoadBiomedicalImageFromFile(' + "decode_backend='nifti', " + 'to_xyz=False, ' + 'to_float32=True, ' + 'backend_args=None)') + + def test_load_biomedical_annotation(self): + results = dict( + seg_map_path=osp.join(self.data_prefix, 'biomedical_ann.nii.gz')) + transform = LoadBiomedicalAnnotation() + results = transform(copy.deepcopy(results)) + assert len(results['gt_seg_map'].shape) == 3 + assert results['gt_seg_map'].dtype == np.float32 + + def test_load_biomedical_data(self): + input_results = dict( + img_path=osp.join(self.data_prefix, 'biomedical.npy')) + transform = LoadBiomedicalData(with_seg=True) + results = transform(copy.deepcopy(input_results)) + assert results['img_path'] == osp.join(self.data_prefix, + 'biomedical.npy') + assert results['img'][0].shape == results['gt_seg_map'].shape + assert results['img'].dtype == np.float32 + assert results['ori_shape'] == results['img'].shape[1:] + assert repr(transform) == ('LoadBiomedicalData(' + 'with_seg=True, ' + "decode_backend='numpy', " + 'to_xyz=False, ' + 'backend_args=None)') + + transform = LoadBiomedicalData(with_seg=False) + results = transform(copy.deepcopy(input_results)) + assert len(results['img'].shape) == 4 + assert results.get('gt_seg_map') is None + assert repr(transform) == ('LoadBiomedicalData(' + 'with_seg=False, ' + "decode_backend='numpy', " + 'to_xyz=False, ' + 'backend_args=None)') + + def test_load_depth_annotation(self): + input_results = dict( + img_path='tests/data/pseudo_nyu_dataset/images/' + 'bookstore_0001d_00001.jpg', + depth_map_path='tests/data/pseudo_nyu_dataset/' + 'annotations/bookstore_0001d_00001.png', + category_id=-1, + seg_fields=[]) + transform = LoadDepthAnnotation(depth_rescale_factor=0.001) + results = transform(input_results) + assert 'gt_depth_map' in results + assert results['gt_depth_map'].shape[:2] == mmcv.imread( + input_results['depth_map_path']).shape[:2] + assert results['gt_depth_map'].dtype == np.float32 + assert 'gt_depth_map' in results['seg_fields'] diff --git a/Seg_All_In_One_MMSeg/tests/test_datasets/test_transform.py b/Seg_All_In_One_MMSeg/tests/test_datasets/test_transform.py new file mode 100644 index 0000000..e73e558 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_datasets/test_transform.py @@ -0,0 +1,1273 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import os.path as osp +from unittest import TestCase + +import mmcv +import numpy as np +import pytest +from mmengine.registry import init_default_scope +from PIL import Image + +from mmseg.datasets.transforms import * # noqa +from mmseg.datasets.transforms import (LoadBiomedicalData, + LoadBiomedicalImageFromFile, + PhotoMetricDistortion, RandomCrop, + RandomDepthMix) +from mmseg.registry import TRANSFORMS + +init_default_scope('mmseg') + + +def test_resize(): + # Test `Resize`, `RandomResize` and `RandomChoiceResize` from + # MMCV transform. Noted: `RandomResize` has args `scales` but + # `Resize` and `RandomResize` has args `scale`. + transform = dict(type='Resize', scale=(1333, 800), keep_ratio=True) + resize_module = TRANSFORMS.build(transform) + + results = dict() + # (288, 512, 3) + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + results['img'] = img + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + resized_results = resize_module(results.copy()) + # img_shape = results['img'].shape[:2] in ``MMCV resize`` function + # so right now it is (750, 1333) rather than (750, 1333, 3) + assert resized_results['img_shape'] == (750, 1333) + + # test keep_ratio=False + transform = dict( + type='RandomResize', + scale=(1280, 800), + ratio_range=(1.0, 1.0), + resize_type='Resize', + keep_ratio=False) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert resized_results['img_shape'] == (800, 1280) + + # test `RandomChoiceResize`, which in older mmsegmentation + # `Resize` is multiscale_mode='range' + transform = dict(type='RandomResize', scale=[(1333, 400), (1333, 1200)]) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert max(resized_results['img_shape'][:2]) <= 1333 + assert min(resized_results['img_shape'][:2]) >= 400 + assert min(resized_results['img_shape'][:2]) <= 1200 + + # test RandomChoiceResize, which in older mmsegmentation + # `Resize` is multiscale_mode='value' + transform = dict( + type='RandomChoiceResize', + scales=[(1333, 800), (1333, 400)], + resize_type='Resize', + keep_ratio=False) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert resized_results['img_shape'] in [(800, 1333), (400, 1333)] + + transform = dict(type='Resize', scale_factor=(0.9, 1.1), keep_ratio=True) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert max(resized_results['img_shape'][:2]) <= 1333 * 1.1 + + # test RandomChoiceResize, which `resize_type` is `ResizeShortestEdge` + transform = dict( + type='RandomChoiceResize', + scales=[128, 256, 512], + resize_type='ResizeShortestEdge', + max_size=1333) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert resized_results['img_shape'][0] in [128, 256, 512] + + transform = dict( + type='RandomChoiceResize', + scales=[512], + resize_type='ResizeShortestEdge', + max_size=512) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert resized_results['img_shape'][1] == 512 + + transform = dict( + type='RandomChoiceResize', + scales=[(128, 256), (256, 512), (512, 1024)], + resize_type='ResizeShortestEdge', + max_size=1333) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert resized_results['img_shape'][0] in [128, 256, 512] + + # test scale=None and scale_factor is tuple. + # img shape: (288, 512, 3) + transform = dict( + type='Resize', scale=None, scale_factor=(0.5, 2.0), keep_ratio=True) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert int(288 * 0.5) <= resized_results['img_shape'][0] <= 288 * 2.0 + assert int(512 * 0.5) <= resized_results['img_shape'][1] <= 512 * 2.0 + + # test minimum resized image shape is 640 + transform = dict(type='Resize', scale=(2560, 640), keep_ratio=True) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert resized_results['img_shape'] == (640, 1138) + + # test minimum resized image shape is 640 when img_scale=(512, 640) + # where should define `scale_factor` in MMCV new ``Resize`` function. + min_size_ratio = max(640 / img.shape[0], 640 / img.shape[1]) + transform = dict( + type='Resize', scale_factor=min_size_ratio, keep_ratio=True) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert resized_results['img_shape'] == (640, 1138) + + # test h > w + img = np.random.randn(512, 288, 3) + results['img'] = img + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + min_size_ratio = max(640 / img.shape[0], 640 / img.shape[1]) + transform = dict( + type='Resize', + scale=(2560, 640), + scale_factor=min_size_ratio, + keep_ratio=True) + resize_module = TRANSFORMS.build(transform) + resized_results = resize_module(results.copy()) + assert resized_results['img_shape'] == (1138, 640) + + +def test_flip(): + # test assertion for invalid prob + with pytest.raises(AssertionError): + transform = dict(type='RandomFlip', prob=1.5) + TRANSFORMS.build(transform) + + # test assertion for invalid direction + with pytest.raises(AssertionError): + transform = dict(type='RandomFlip', prob=1.0, direction='horizonta') + TRANSFORMS.build(transform) + + transform = dict(type='RandomFlip', prob=1.0) + flip_module = TRANSFORMS.build(transform) + + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + original_img = copy.deepcopy(img) + seg = np.array( + Image.open(osp.join(osp.dirname(__file__), '../data/seg.png'))) + original_seg = copy.deepcopy(seg) + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = flip_module(results) + + flip_module = TRANSFORMS.build(transform) + results = flip_module(results) + assert np.equal(original_img, results['img']).all() + assert np.equal(original_seg, results['gt_semantic_seg']).all() + + results['gt_depth_map'] = seg + results['seg_fields'] = ['gt_depth_map'] + results = flip_module(results) + flip_module = TRANSFORMS.build(transform) + results = flip_module(results) + assert np.equal(original_img, results['img']).all() + assert np.equal(original_seg, results['gt_depth_map']).all() + + +def test_random_rotate_flip(): + with pytest.raises(AssertionError): + transform = dict(type='RandomRotFlip', flip_prob=1.5) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict(type='RandomRotFlip', rotate_prob=1.5) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict(type='RandomRotFlip', degree=[20, 20, 20]) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict(type='RandomRotFlip', degree=-20) + TRANSFORMS.build(transform) + + transform = dict( + type='RandomRotFlip', flip_prob=1.0, rotate_prob=0, degree=20) + rot_flip_module = TRANSFORMS.build(transform) + + results = dict() + img = mmcv.imread( + osp.join( + osp.dirname(__file__), + '../data/pseudo_synapse_dataset/img_dir/case0005_slice000.jpg'), + 'color') + original_img = copy.deepcopy(img) + seg = np.array( + Image.open( + osp.join( + osp.dirname(__file__), + '../data/pseudo_synapse_dataset/ann_dir/case0005_slice000.png') + )) + original_seg = copy.deepcopy(seg) + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + result_flip = rot_flip_module(results) + assert original_img.shape == result_flip['img'].shape + assert original_seg.shape == result_flip['gt_semantic_seg'].shape + + transform = dict( + type='RandomRotFlip', flip_prob=0, rotate_prob=1.0, degree=20) + rot_flip_module = TRANSFORMS.build(transform) + + result_rotate = rot_flip_module(results) + assert original_img.shape == result_rotate['img'].shape + assert original_seg.shape == result_rotate['gt_semantic_seg'].shape + + assert str(transform) == "{'type': 'RandomRotFlip'," \ + " 'flip_prob': 0," \ + " 'rotate_prob': 1.0," \ + " 'degree': 20}" + + +def test_pad(): + # test assertion if both size_divisor and size is None + with pytest.raises(AssertionError): + transform = dict(type='Pad') + TRANSFORMS.build(transform) + + transform = dict(type='Pad', size_divisor=32) + transform = TRANSFORMS.build(transform) + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + original_img = copy.deepcopy(img) + results['img'] = img + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = transform(results) + # original img already divisible by 32 + assert np.equal(results['img'], original_img).all() + img_shape = results['img'].shape + assert img_shape[0] % 32 == 0 + assert img_shape[1] % 32 == 0 + + +def test_normalize(): + img_norm_cfg = dict( + mean=[123.675, 116.28, 103.53], + std=[58.395, 57.12, 57.375], + to_rgb=True) + transform = dict(type='Normalize', **img_norm_cfg) + transform = TRANSFORMS.build(transform) + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + original_img = copy.deepcopy(img) + results['img'] = img + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = transform(results) + + mean = np.array(img_norm_cfg['mean']) + std = np.array(img_norm_cfg['std']) + converted_img = (original_img[..., ::-1] - mean) / std + assert np.allclose(results['img'], converted_img) + + +def test_random_crop(): + # test assertion for invalid random crop + with pytest.raises(AssertionError): + RandomCrop(crop_size=(-1, 0)) + + results = dict() + img = mmcv.imread(osp.join('tests/data/color.jpg'), 'color') + seg = np.array(Image.open(osp.join('tests/data/seg.png'))) + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + h, w, _ = img.shape + pipeline = RandomCrop(crop_size=(h - 20, w - 20)) + + results = pipeline(results) + assert results['img'].shape[:2] == (h - 20, w - 20) + assert results['img_shape'] == (h - 20, w - 20) + assert results['gt_semantic_seg'].shape[:2] == (h - 20, w - 20) + + +def test_rgb2gray(): + # test assertion out_channels should be greater than 0 + with pytest.raises(AssertionError): + transform = dict(type='RGB2Gray', out_channels=-1) + TRANSFORMS.build(transform) + # test assertion weights should be tuple[float] + with pytest.raises(AssertionError): + transform = dict(type='RGB2Gray', out_channels=1, weights=1.1) + TRANSFORMS.build(transform) + + # test out_channels is None + transform = dict(type='RGB2Gray') + transform = TRANSFORMS.build(transform) + + assert str(transform) == f'RGB2Gray(' \ + f'out_channels={None}, ' \ + f'weights={(0.299, 0.587, 0.114)})' + + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + h, w, c = img.shape + seg = np.array( + Image.open(osp.join(osp.dirname(__file__), '../data/seg.png'))) + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = transform(results) + assert results['img'].shape == (h, w, c) + assert results['img_shape'] == (h, w, c) + assert results['ori_shape'] == (h, w, c) + + # test out_channels = 2 + transform = dict(type='RGB2Gray', out_channels=2) + transform = TRANSFORMS.build(transform) + + assert str(transform) == f'RGB2Gray(' \ + f'out_channels={2}, ' \ + f'weights={(0.299, 0.587, 0.114)})' + + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + h, w, c = img.shape + seg = np.array( + Image.open(osp.join(osp.dirname(__file__), '../data/seg.png'))) + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = transform(results) + assert results['img'].shape == (h, w, 2) + assert results['img_shape'] == (h, w, 2) + + +def test_photo_metric_distortion(): + + results = dict() + img = mmcv.imread(osp.join('tests/data/color.jpg'), 'color') + seg = np.array(Image.open(osp.join('tests/data/seg.png'))) + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + pipeline = PhotoMetricDistortion(saturation_range=(1., 1.)) + results = pipeline(results) + + assert (results['gt_semantic_seg'] == seg).all() + assert results['img_shape'] == img.shape + + +def test_rerange(): + # test assertion if min_value or max_value is illegal + with pytest.raises(AssertionError): + transform = dict(type='Rerange', min_value=[0], max_value=[255]) + TRANSFORMS.build(transform) + + # test assertion if min_value >= max_value + with pytest.raises(AssertionError): + transform = dict(type='Rerange', min_value=1, max_value=1) + TRANSFORMS.build(transform) + + # test assertion if img_min_value == img_max_value + with pytest.raises(AssertionError): + transform = dict(type='Rerange', min_value=0, max_value=1) + transform = TRANSFORMS.build(transform) + results = dict() + results['img'] = np.array([[1, 1], [1, 1]]) + transform(results) + + img_rerange_cfg = dict() + transform = dict(type='Rerange', **img_rerange_cfg) + transform = TRANSFORMS.build(transform) + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + original_img = copy.deepcopy(img) + results['img'] = img + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = transform(results) + + min_value = np.min(original_img) + max_value = np.max(original_img) + converted_img = (original_img - min_value) / (max_value - min_value) * 255 + + assert np.allclose(results['img'], converted_img) + assert str(transform) == f'Rerange(min_value={0}, max_value={255})' + + +def test_CLAHE(): + # test assertion if clip_limit is None + with pytest.raises(AssertionError): + transform = dict(type='CLAHE', clip_limit=None) + TRANSFORMS.build(transform) + + # test assertion if tile_grid_size is illegal + with pytest.raises(AssertionError): + transform = dict(type='CLAHE', tile_grid_size=(8.0, 8.0)) + TRANSFORMS.build(transform) + + # test assertion if tile_grid_size is illegal + with pytest.raises(AssertionError): + transform = dict(type='CLAHE', tile_grid_size=(9, 9, 9)) + TRANSFORMS.build(transform) + + transform = dict(type='CLAHE', clip_limit=2) + transform = TRANSFORMS.build(transform) + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + original_img = copy.deepcopy(img) + results['img'] = img + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = transform(results) + + converted_img = np.empty(original_img.shape) + for i in range(original_img.shape[2]): + converted_img[:, :, i] = mmcv.clahe( + np.array(original_img[:, :, i], dtype=np.uint8), 2, (8, 8)) + + assert np.allclose(results['img'], converted_img) + assert str(transform) == f'CLAHE(clip_limit={2}, tile_grid_size={(8, 8)})' + + +def test_adjust_gamma(): + # test assertion if gamma <= 0 + with pytest.raises(AssertionError): + transform = dict(type='AdjustGamma', gamma=0) + TRANSFORMS.build(transform) + + # test assertion if gamma is list + with pytest.raises(AssertionError): + transform = dict(type='AdjustGamma', gamma=[1.2]) + TRANSFORMS.build(transform) + + # test with gamma = 1.2 + transform = dict(type='AdjustGamma', gamma=1.2) + transform = TRANSFORMS.build(transform) + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + original_img = copy.deepcopy(img) + results['img'] = img + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = transform(results) + + inv_gamma = 1.0 / 1.2 + table = np.array([((i / 255.0)**inv_gamma) * 255 + for i in np.arange(0, 256)]).astype('uint8') + converted_img = mmcv.lut_transform( + np.array(original_img, dtype=np.uint8), table) + assert np.allclose(results['img'], converted_img) + assert str(transform) == f'AdjustGamma(gamma={1.2})' + + +def test_rotate(): + # test assertion degree should be tuple[float] or float + with pytest.raises(AssertionError): + transform = dict(type='RandomRotate', prob=0.5, degree=-10) + TRANSFORMS.build(transform) + # test assertion degree should be tuple[float] or float + with pytest.raises(AssertionError): + transform = dict(type='RandomRotate', prob=0.5, degree=(10., 20., 30.)) + TRANSFORMS.build(transform) + + transform = dict(type='RandomRotate', degree=10., prob=1.) + transform = TRANSFORMS.build(transform) + + assert str(transform) == f'RandomRotate(' \ + f'prob={1.}, ' \ + f'degree=({-10.}, {10.}), ' \ + f'pad_val={0}, ' \ + f'seg_pad_val={255}, ' \ + f'center={None}, ' \ + f'auto_bound={False})' + + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + h, w, _ = img.shape + seg = np.array( + Image.open(osp.join(osp.dirname(__file__), '../data/seg.png'))) + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + results = transform(results) + assert results['img'].shape[:2] == (h, w) + + +def test_seg_rescale(): + results = dict() + seg = np.array( + Image.open(osp.join(osp.dirname(__file__), '../data/seg.png'))) + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + h, w = seg.shape + + transform = dict(type='SegRescale', scale_factor=1. / 2) + rescale_module = TRANSFORMS.build(transform) + rescale_results = rescale_module(results.copy()) + assert rescale_results['gt_semantic_seg'].shape == (h // 2, w // 2) + + transform = dict(type='SegRescale', scale_factor=1) + rescale_module = TRANSFORMS.build(transform) + rescale_results = rescale_module(results.copy()) + assert rescale_results['gt_semantic_seg'].shape == (h, w) + + +def test_mosaic(): + # test prob + with pytest.raises(AssertionError): + transform = dict(type='RandomMosaic', prob=1.5) + TRANSFORMS.build(transform) + # test assertion for invalid img_scale + with pytest.raises(AssertionError): + transform = dict(type='RandomMosaic', prob=1, img_scale=640) + TRANSFORMS.build(transform) + + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + seg = np.array( + Image.open(osp.join(osp.dirname(__file__), '../data/seg.png'))) + + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + + transform = dict(type='RandomMosaic', prob=1, img_scale=(10, 12)) + mosaic_module = TRANSFORMS.build(transform) + assert 'Mosaic' in repr(mosaic_module) + + # test assertion for invalid mix_results + with pytest.raises(AssertionError): + mosaic_module(results) + + results['mix_results'] = [copy.deepcopy(results)] * 3 + results = mosaic_module(results) + assert results['img'].shape[:2] == (20, 24) + + results = dict() + results['img'] = img[:, :, 0] + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + + transform = dict(type='RandomMosaic', prob=0, img_scale=(10, 12)) + mosaic_module = TRANSFORMS.build(transform) + results['mix_results'] = [copy.deepcopy(results)] * 3 + results = mosaic_module(results) + assert results['img'].shape[:2] == img.shape[:2] + + transform = dict(type='RandomMosaic', prob=1, img_scale=(10, 12)) + mosaic_module = TRANSFORMS.build(transform) + results = mosaic_module(results) + assert results['img'].shape[:2] == (20, 24) + + results = dict() + results['img'] = np.concatenate((img, img), axis=2) + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + + transform = dict(type='RandomMosaic', prob=1, img_scale=(10, 12)) + mosaic_module = TRANSFORMS.build(transform) + results['mix_results'] = [copy.deepcopy(results)] * 3 + results = mosaic_module(results) + assert results['img'].shape[2] == 6 + + +def test_cutout(): + # test prob + with pytest.raises(AssertionError): + transform = dict(type='RandomCutOut', prob=1.5, n_holes=1) + TRANSFORMS.build(transform) + # test n_holes + with pytest.raises(AssertionError): + transform = dict( + type='RandomCutOut', prob=0.5, n_holes=(5, 3), cutout_shape=(8, 8)) + TRANSFORMS.build(transform) + with pytest.raises(AssertionError): + transform = dict( + type='RandomCutOut', + prob=0.5, + n_holes=(3, 4, 5), + cutout_shape=(8, 8)) + TRANSFORMS.build(transform) + # test cutout_shape and cutout_ratio + with pytest.raises(AssertionError): + transform = dict( + type='RandomCutOut', prob=0.5, n_holes=1, cutout_shape=8) + TRANSFORMS.build(transform) + with pytest.raises(AssertionError): + transform = dict( + type='RandomCutOut', prob=0.5, n_holes=1, cutout_ratio=0.2) + TRANSFORMS.build(transform) + # either of cutout_shape and cutout_ratio should be given + with pytest.raises(AssertionError): + transform = dict(type='RandomCutOut', prob=0.5, n_holes=1) + TRANSFORMS.build(transform) + with pytest.raises(AssertionError): + transform = dict( + type='RandomCutOut', + prob=0.5, + n_holes=1, + cutout_shape=(2, 2), + cutout_ratio=(0.4, 0.4)) + TRANSFORMS.build(transform) + # test seg_fill_in + with pytest.raises(AssertionError): + transform = dict( + type='RandomCutOut', + prob=0.5, + n_holes=1, + cutout_shape=(8, 8), + seg_fill_in='a') + TRANSFORMS.build(transform) + with pytest.raises(AssertionError): + transform = dict( + type='RandomCutOut', + prob=0.5, + n_holes=1, + cutout_shape=(8, 8), + seg_fill_in=256) + TRANSFORMS.build(transform) + + results = dict() + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + + seg = np.array( + Image.open(osp.join(osp.dirname(__file__), '../data/seg.png'))) + + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['ori_shape'] = img.shape + results['pad_shape'] = img.shape + results['img_fields'] = ['img'] + + transform = dict( + type='RandomCutOut', prob=1, n_holes=1, cutout_shape=(10, 10)) + cutout_module = TRANSFORMS.build(transform) + assert 'cutout_shape' in repr(cutout_module) + cutout_result = cutout_module(copy.deepcopy(results)) + assert cutout_result['img'].sum() < img.sum() + + transform = dict( + type='RandomCutOut', prob=1, n_holes=1, cutout_ratio=(0.8, 0.8)) + cutout_module = TRANSFORMS.build(transform) + assert 'cutout_ratio' in repr(cutout_module) + cutout_result = cutout_module(copy.deepcopy(results)) + assert cutout_result['img'].sum() < img.sum() + + transform = dict( + type='RandomCutOut', prob=0, n_holes=1, cutout_ratio=(0.8, 0.8)) + cutout_module = TRANSFORMS.build(transform) + cutout_result = cutout_module(copy.deepcopy(results)) + assert cutout_result['img'].sum() == img.sum() + assert cutout_result['gt_semantic_seg'].sum() == seg.sum() + + transform = dict( + type='RandomCutOut', + prob=1, + n_holes=(2, 4), + cutout_shape=[(10, 10), (15, 15)], + fill_in=(255, 255, 255), + seg_fill_in=None) + cutout_module = TRANSFORMS.build(transform) + cutout_result = cutout_module(copy.deepcopy(results)) + assert cutout_result['img'].sum() > img.sum() + assert cutout_result['gt_semantic_seg'].sum() == seg.sum() + + transform = dict( + type='RandomCutOut', + prob=1, + n_holes=1, + cutout_ratio=(0.8, 0.8), + fill_in=(255, 255, 255), + seg_fill_in=255) + cutout_module = TRANSFORMS.build(transform) + cutout_result = cutout_module(copy.deepcopy(results)) + assert cutout_result['img'].sum() > img.sum() + assert cutout_result['gt_semantic_seg'].sum() > seg.sum() + + +def test_resize_to_multiple(): + transform = dict(type='ResizeToMultiple', size_divisor=32) + transform = TRANSFORMS.build(transform) + + img = np.random.randn(213, 232, 3) + seg = np.random.randint(0, 19, (213, 232)) + results = dict() + results['img'] = img + results['gt_semantic_seg'] = seg + results['seg_fields'] = ['gt_semantic_seg'] + results['img_shape'] = img.shape + results['pad_shape'] = img.shape + + results = transform(results) + assert results['img'].shape == (224, 256, 3) + assert results['gt_semantic_seg'].shape == (224, 256) + assert results['img_shape'] == (224, 256) + + +def test_generate_edge(): + transform = dict(type='GenerateEdge', edge_width=1) + transform = TRANSFORMS.build(transform) + + seg_map = np.array([ + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 2], + [1, 1, 1, 2, 2], + [1, 1, 2, 2, 2], + [1, 2, 2, 2, 2], + [2, 2, 2, 2, 2], + ]) + results = dict() + results['gt_seg_map'] = seg_map + results['img_shape'] = seg_map.shape + + results = transform(results) + assert np.all(results['gt_edge_map'] == np.array([ + [0, 0, 0, 1, 0], + [0, 0, 1, 1, 1], + [0, 1, 1, 1, 0], + [1, 1, 1, 0, 0], + [1, 1, 0, 0, 0], + [1, 0, 0, 0, 0], + ])) + + +def test_biomedical3d_random_crop(): + # test assertion for invalid random crop + with pytest.raises(AssertionError): + transform = dict(type='BioMedical3DRandomCrop', crop_shape=(-2, -1, 0)) + transform = TRANSFORMS.build(transform) + + from mmseg.datasets.transforms import (LoadBiomedicalAnnotation, + LoadBiomedicalImageFromFile) + results = dict() + results['img_path'] = osp.join( + osp.dirname(__file__), '../data', 'biomedical.nii.gz') + transform = LoadBiomedicalImageFromFile() + results = transform(copy.deepcopy(results)) + + results['seg_map_path'] = osp.join( + osp.dirname(__file__), '../data', 'biomedical_ann.nii.gz') + transform = LoadBiomedicalAnnotation() + results = transform(copy.deepcopy(results)) + + d, h, w = results['img_shape'] + transform = dict( + type='BioMedical3DRandomCrop', + crop_shape=(d - 20, h - 20, w - 20), + keep_foreground=True) + transform = TRANSFORMS.build(transform) + crop_results = transform(results) + assert crop_results['img'].shape[1:] == (d - 20, h - 20, w - 20) + assert crop_results['img_shape'] == (d - 20, h - 20, w - 20) + assert crop_results['gt_seg_map'].shape == (d - 20, h - 20, w - 20) + + transform = dict( + type='BioMedical3DRandomCrop', + crop_shape=(d - 20, h - 20, w - 20), + keep_foreground=False) + transform = TRANSFORMS.build(transform) + crop_results = transform(results) + assert crop_results['img'].shape[1:] == (d - 20, h - 20, w - 20) + assert crop_results['img_shape'] == (d - 20, h - 20, w - 20) + assert crop_results['gt_seg_map'].shape == (d - 20, h - 20, w - 20) + + +def test_biomedical_gaussian_noise(): + # test assertion for invalid prob + with pytest.raises(AssertionError): + transform = dict(type='BioMedicalGaussianNoise', prob=1.5) + TRANSFORMS.build(transform) + + # test assertion for invalid std + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalGaussianNoise', prob=0.2, mean=0.5, std=-0.5) + TRANSFORMS.build(transform) + + transform = dict(type='BioMedicalGaussianNoise', prob=1.0) + noise_module = TRANSFORMS.build(transform) + assert str(noise_module) == 'BioMedicalGaussianNoise'\ + '(prob=1.0, ' \ + 'mean=0.0, ' \ + 'std=0.1)' + + transform = dict(type='BioMedicalGaussianNoise', prob=1.0) + noise_module = TRANSFORMS.build(transform) + results = dict( + img_path=osp.join(osp.dirname(__file__), '../data/biomedical.nii.gz')) + from mmseg.datasets.transforms import LoadBiomedicalImageFromFile + transform = LoadBiomedicalImageFromFile() + results = transform(copy.deepcopy(results)) + original_img = copy.deepcopy(results['img']) + results = noise_module(results) + assert original_img.shape == results['img'].shape + + +def test_biomedical_gaussian_blur(): + # test assertion for invalid prob + with pytest.raises(AssertionError): + transform = dict(type='BioMedicalGaussianBlur', prob=-1.5) + TRANSFORMS.build(transform) + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalGaussianBlur', prob=1.0, sigma_range=0.6) + smooth_module = TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalGaussianBlur', prob=1.0, sigma_range=(0.6)) + smooth_module = TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalGaussianBlur', prob=1.0, sigma_range=(15, 8, 9)) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalGaussianBlur', prob=1.0, sigma_range='0.16') + TRANSFORMS.build(transform) + + transform = dict( + type='BioMedicalGaussianBlur', prob=1.0, sigma_range=(0.7, 0.8)) + smooth_module = TRANSFORMS.build(transform) + assert str( + smooth_module + ) == 'BioMedicalGaussianBlur(prob=1.0, ' \ + 'prob_per_channel=0.5, '\ + 'sigma_range=(0.7, 0.8), ' \ + 'different_sigma_per_channel=True, '\ + 'different_sigma_per_axis=True)' + + transform = dict(type='BioMedicalGaussianBlur', prob=1.0) + smooth_module = TRANSFORMS.build(transform) + assert str( + smooth_module + ) == 'BioMedicalGaussianBlur(prob=1.0, ' \ + 'prob_per_channel=0.5, '\ + 'sigma_range=(0.5, 1.0), ' \ + 'different_sigma_per_channel=True, '\ + 'different_sigma_per_axis=True)' + + results = dict( + img_path=osp.join(osp.dirname(__file__), '../data/biomedical.nii.gz')) + from mmseg.datasets.transforms import LoadBiomedicalImageFromFile + transform = LoadBiomedicalImageFromFile() + results = transform(copy.deepcopy(results)) + original_img = copy.deepcopy(results['img']) + results = smooth_module(results) + assert original_img.shape == results['img'].shape + # the max value in the smoothed image should be less than the original one + assert original_img.max() >= results['img'].max() + assert original_img.min() <= results['img'].min() + + transform = dict( + type='BioMedicalGaussianBlur', + prob=1.0, + different_sigma_per_axis=False) + smooth_module = TRANSFORMS.build(transform) + + results = dict( + img_path=osp.join(osp.dirname(__file__), '../data/biomedical.nii.gz')) + from mmseg.datasets.transforms import LoadBiomedicalImageFromFile + transform = LoadBiomedicalImageFromFile() + results = transform(copy.deepcopy(results)) + original_img = copy.deepcopy(results['img']) + results = smooth_module(results) + assert original_img.shape == results['img'].shape + # the max value in the smoothed image should be less than the original one + assert original_img.max() >= results['img'].max() + assert original_img.min() <= results['img'].min() + + +def test_BioMedicalRandomGamma(): + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalRandomGamma', prob=-1, gamma_range=(0.7, 1.2)) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalRandomGamma', prob=1.2, gamma_range=(0.7, 1.2)) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalRandomGamma', prob=1.0, gamma_range=(0.7)) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalRandomGamma', + prob=1.0, + gamma_range=(0.7, 0.2, 0.3)) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalRandomGamma', + prob=1.0, + gamma_range=(0.7, 2), + invert_image=1) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalRandomGamma', + prob=1.0, + gamma_range=(0.7, 2), + per_channel=1) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict( + type='BioMedicalRandomGamma', + prob=1.0, + gamma_range=(0.7, 2), + retain_stats=1) + TRANSFORMS.build(transform) + + test_img = 'tests/data/biomedical.nii.gz' + results = dict(img_path=test_img) + transform = LoadBiomedicalImageFromFile() + results = transform(copy.deepcopy(results)) + origin_img = results['img'] + transform2 = dict( + type='BioMedicalRandomGamma', + prob=1.0, + gamma_range=(0.7, 2), + ) + transform2 = TRANSFORMS.build(transform2) + results = transform2(results) + transformed_img = results['img'] + assert origin_img.shape == transformed_img.shape + + +def test_BioMedical3DPad(): + # test assertion. + with pytest.raises(AssertionError): + transform = dict(type='BioMedical3DPad', pad_shape=None) + TRANSFORMS.build(transform) + + with pytest.raises(AssertionError): + transform = dict(type='BioMedical3DPad', pad_shape=[256, 256]) + TRANSFORMS.build(transform) + + data_info1 = dict(img=np.random.random((8, 6, 4, 4))) + + transform = dict(type='BioMedical3DPad', pad_shape=(6, 6, 6)) + transform = TRANSFORMS.build(transform) + results = transform(copy.deepcopy(data_info1)) + assert results['img'].shape[1:] == (6, 6, 6) + assert results['pad_shape'] == (6, 6, 6) + + transform = dict(type='BioMedical3DPad', pad_shape=(4, 6, 6)) + transform = TRANSFORMS.build(transform) + results = transform(copy.deepcopy(data_info1)) + assert results['img'].shape[1:] == (6, 6, 6) + assert results['pad_shape'] == (6, 6, 6) + + data_info2 = dict( + img=np.random.random((8, 6, 4, 4)), + gt_seg_map=np.random.randint(0, 2, (6, 4, 4))) + + transform = dict(type='BioMedical3DPad', pad_shape=(6, 6, 6)) + transform = TRANSFORMS.build(transform) + results = transform(copy.deepcopy(data_info2)) + assert results['img'].shape[1:] == (6, 6, 6) + assert results['gt_seg_map'].shape[1:] == (6, 6, 6) + assert results['pad_shape'] == (6, 6, 6) + + transform = dict(type='BioMedical3DPad', pad_shape=(4, 6, 6)) + transform = TRANSFORMS.build(transform) + results = transform(copy.deepcopy(data_info2)) + assert results['img'].shape[1:] == (6, 6, 6) + assert results['gt_seg_map'].shape[1:] == (6, 6, 6) + assert results['pad_shape'] == (6, 6, 6) + + +def test_biomedical_3d_flip(): + # test assertion for invalid prob + with pytest.raises(AssertionError): + transform = dict(type='BioMedical3DRandomFlip', prob=1.5, axes=(0, 1)) + transform = TRANSFORMS.build(transform) + + # test assertion for invalid direction + with pytest.raises(AssertionError): + transform = dict(type='BioMedical3DRandomFlip', prob=1, axes=(0, 1, 3)) + transform = TRANSFORMS.build(transform) + + # test flip axes are (0, 1, 2) + transform = dict(type='BioMedical3DRandomFlip', prob=1, axes=(0, 1, 2)) + transform = TRANSFORMS.build(transform) + + # test with random 3d data + results = dict() + results['img_path'] = 'Null' + results['img_shape'] = (1, 16, 16, 16) + results['img'] = np.random.randn(1, 16, 16, 16) + results['gt_seg_map'] = np.random.randint(0, 4, (16, 16, 16)) + + original_img = results['img'].copy() + original_seg = results['gt_seg_map'].copy() + + # flip first time + results = transform(results) + with pytest.raises(AssertionError): + assert np.equal(original_img, results['img']).all() + with pytest.raises(AssertionError): + assert np.equal(original_seg, results['gt_seg_map']).all() + + # flip second time + results = transform(results) + assert np.equal(original_img, results['img']).all() + assert np.equal(original_seg, results['gt_seg_map']).all() + + # test with actual data and flip axes are (0, 1) + # load biomedical 3d img and seg + data_prefix = osp.join(osp.dirname(__file__), '../data') + input_results = dict(img_path=osp.join(data_prefix, 'biomedical.npy')) + biomedical_loader = LoadBiomedicalData(with_seg=True) + data = biomedical_loader(copy.deepcopy(input_results)) + results = data.copy() + + original_img = data['img'].copy() + original_seg = data['gt_seg_map'].copy() + + # test flip axes are (0, 1) + transform = dict(type='BioMedical3DRandomFlip', prob=1, axes=(0, 1)) + transform = TRANSFORMS.build(transform) + + # flip first time + results = transform(results) + with pytest.raises(AssertionError): + assert np.equal(original_img, results['img']).all() + with pytest.raises(AssertionError): + assert np.equal(original_seg, results['gt_seg_map']).all() + + # flip second time + results = transform(results) + assert np.equal(original_img, results['img']).all() + assert np.equal(original_seg, results['gt_seg_map']).all() + + # test transform with flip axes = (1) + transform = dict(type='BioMedical3DRandomFlip', prob=1, axes=(1, )) + transform = TRANSFORMS.build(transform) + results = data.copy() + results = transform(results) + results = transform(results) + assert np.equal(original_img, results['img']).all() + assert np.equal(original_seg, results['gt_seg_map']).all() + + # test transform with swap_label_pairs + transform = dict( + type='BioMedical3DRandomFlip', + prob=1, + axes=(1, 2), + swap_label_pairs=[(0, 1)]) + transform = TRANSFORMS.build(transform) + results = data.copy() + results = transform(results) + + with pytest.raises(AssertionError): + assert np.equal(original_seg, results['gt_seg_map']).all() + + # swap twice + results = transform(results) + assert np.equal(original_img, results['img']).all() + assert np.equal(original_seg, results['gt_seg_map']).all() + + +def test_albu_transform(): + results = dict( + img_path=osp.join(osp.dirname(__file__), '../data/color.jpg')) + + # Define simple pipeline + load = dict(type='LoadImageFromFile') + load = TRANSFORMS.build(load) + + albu_transform = dict( + type='Albu', transforms=[dict(type='ChannelShuffle', p=1)]) + albu_transform = TRANSFORMS.build(albu_transform) + + normalize = dict(type='Normalize', mean=[0] * 3, std=[0] * 3, to_rgb=True) + normalize = TRANSFORMS.build(normalize) + + # Execute transforms + results = load(results) + results = albu_transform(results) + results = normalize(results) + + assert results['img'].dtype == np.float32 + + +def test_albu_channel_order(): + results = dict( + img_path=osp.join(osp.dirname(__file__), '../data/color.jpg')) + + # Define simple pipeline + load = dict(type='LoadImageFromFile') + load = TRANSFORMS.build(load) + + # Transform is modifying B channel + albu_transform = dict( + type='Albu', + transforms=[ + dict( + type='RGBShift', + r_shift_limit=0, + g_shift_limit=0, + b_shift_limit=200, + p=1) + ]) + albu_transform = TRANSFORMS.build(albu_transform) + + # Execute transforms + results_load = load(results) + results_albu = albu_transform(results_load) + + # assert only Green and Red channel are not modified + np.testing.assert_array_equal(results_albu['img'][..., 1:], + results_load['img'][..., 1:]) + + # assert Blue channel is modified + with pytest.raises(AssertionError): + np.testing.assert_array_equal(results_albu['img'][..., 0], + results_load['img'][..., 0]) + + +class TestRandomDepthMix(TestCase): + + def setUp(self): + self.transform = RandomDepthMix(prob=1.0) + + def test_transform_shape(self): + # Create a dummy result dict + results = { + 'img_shape': (10, 10), + 'img': np.random.rand(10, 10, 3), + 'gt_depth_map': np.random.rand(10, 10) + } + transformed = self.transform.transform(results) + + # Check if the shape remains the same + self.assertEqual(results['img'].shape, transformed['img'].shape) + + def test_transform_values(self): + # Create a dummy result dict + results = { + 'img_shape': (10, 10), + 'img': np.zeros((10, 10, 3)), + 'gt_depth_map': np.ones((10, 10)) + } + transformed = self.transform.transform(results) + + # Assuming the transformation modifies a portion of the image, + # it shouldn't remain all zeros + self.assertFalse(np.all(transformed['img'] == 0)) + + def test_invalid_image_dimension(self): + # Create a dummy result dict with invalid image dimension + results = { + 'img_shape': (10, 10), + 'img': np.random.rand(10, 10, 3, 3), + 'gt_depth_map': np.random.rand(10, 10) + } + + # Check if a ValueError is raised for invalid dimension + with self.assertRaises(ValueError): + self.transform.transform(results) diff --git a/Seg_All_In_One_MMSeg/tests/test_datasets/test_tta.py b/Seg_All_In_One_MMSeg/tests/test_datasets/test_tta.py new file mode 100644 index 0000000..25b1ecd --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_datasets/test_tta.py @@ -0,0 +1,131 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp + +import mmcv +import pytest + +from mmseg.datasets.transforms import * # noqa +from mmseg.registry import TRANSFORMS + + +def test_multi_scale_flip_aug(): + # test exception + with pytest.raises(TypeError): + tta_transform = dict( + type='TestTimeAug', + transforms=[dict(type='Resize', keep_ratio=False)], + ) + TRANSFORMS.build(tta_transform) + + tta_transform = dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale=scale, keep_ratio=False) + for scale in [(256, 256), (512, 512), (1024, 1024)] + ], [dict(type='mmseg.PackSegInputs')]]) + tta_module = TRANSFORMS.build(tta_transform) + + results = dict() + # (288, 512, 3) + img = mmcv.imread( + osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + results['img'] = img + results['ori_shape'] = img.shape + results['ori_height'] = img.shape[0] + results['ori_width'] = img.shape[1] + # Set initial values for default meta_keys + results['pad_shape'] = img.shape + results['scale_factor'] = 1.0 + + tta_results = tta_module(results.copy()) + assert [img.shape for img in tta_results['inputs']] == [(3, 256, 256), + (3, 512, 512), + (3, 1024, 1024)] + + tta_transform = dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale=scale, keep_ratio=False) + for scale in [(256, 256), (512, 512), (1024, 1024)] + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='mmseg.PackSegInputs')] + ]) + tta_module = TRANSFORMS.build(tta_transform) + tta_results: dict = tta_module(results.copy()) + assert [img.shape for img in tta_results['inputs']] == [(3, 256, 256), + (3, 256, 256), + (3, 512, 512), + (3, 512, 512), + (3, 1024, 1024), + (3, 1024, 1024)] + assert [ + data_sample.metainfo['flip'] + for data_sample in tta_results['data_samples'] + ] == [False, True, False, True, False, True] + + tta_transform = dict( + type='TestTimeAug', + transforms=[[dict(type='Resize', scale=(512, 512), keep_ratio=False)], + [dict(type='mmseg.PackSegInputs')]]) + tta_module = TRANSFORMS.build(tta_transform) + tta_results = tta_module(results.copy()) + assert [tta_results['inputs'][0].shape] == [(3, 512, 512)] + + tta_transform = dict( + type='TestTimeAug', + transforms=[ + [dict(type='Resize', scale=(512, 512), keep_ratio=False)], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='mmseg.PackSegInputs')] + ]) + tta_module = TRANSFORMS.build(tta_transform) + tta_results = tta_module(results.copy()) + assert [img.shape for img in tta_results['inputs']] == [(3, 512, 512), + (3, 512, 512)] + assert [ + data_sample.metainfo['flip'] + for data_sample in tta_results['data_samples'] + ] == [False, True] + + tta_transform = dict( + type='TestTimeAug', + transforms=[[ + dict(type='Resize', scale_factor=r, keep_ratio=False) + for r in [0.5, 1.0, 2.0] + ], [dict(type='mmseg.PackSegInputs')]]) + tta_module = TRANSFORMS.build(tta_transform) + tta_results = tta_module(results.copy()) + assert [img.shape for img in tta_results['inputs']] == [(3, 144, 256), + (3, 288, 512), + (3, 576, 1024)] + + tta_transform = dict( + type='TestTimeAug', + transforms=[ + [ + dict(type='Resize', scale_factor=r, keep_ratio=True) + for r in [0.5, 1.0, 2.0] + ], + [ + dict(type='RandomFlip', prob=0., direction='horizontal'), + dict(type='RandomFlip', prob=1., direction='horizontal') + ], [dict(type='mmseg.PackSegInputs')] + ]) + tta_module = TRANSFORMS.build(tta_transform) + tta_results = tta_module(results.copy()) + assert [img.shape for img in tta_results['inputs']] == [(3, 144, 256), + (3, 144, 256), + (3, 288, 512), + (3, 288, 512), + (3, 576, 1024), + (3, 576, 1024)] + assert [ + data_sample.metainfo['flip'] + for data_sample in tta_results['data_samples'] + ] == [False, True, False, True, False, True] diff --git a/Seg_All_In_One_MMSeg/tests/test_digit_version.py b/Seg_All_In_One_MMSeg/tests/test_digit_version.py new file mode 100644 index 0000000..45daf09 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_digit_version.py @@ -0,0 +1,21 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmseg import digit_version + + +def test_digit_version(): + assert digit_version('0.2.16') == (0, 2, 16, 0, 0, 0) + assert digit_version('1.2.3') == (1, 2, 3, 0, 0, 0) + assert digit_version('1.2.3rc0') == (1, 2, 3, 0, -1, 0) + assert digit_version('1.2.3rc1') == (1, 2, 3, 0, -1, 1) + assert digit_version('1.0rc0') == (1, 0, 0, 0, -1, 0) + assert digit_version('1.0') == digit_version('1.0.0') + assert digit_version('1.5.0+cuda90_cudnn7.6.3_lms') == digit_version('1.5') + assert digit_version('1.0.0dev') < digit_version('1.0.0a') + assert digit_version('1.0.0a') < digit_version('1.0.0a1') + assert digit_version('1.0.0a') < digit_version('1.0.0b') + assert digit_version('1.0.0b') < digit_version('1.0.0rc') + assert digit_version('1.0.0rc1') < digit_version('1.0.0') + assert digit_version('1.0.0') < digit_version('1.0.0post') + assert digit_version('1.0.0post') < digit_version('1.0.0post1') + assert digit_version('v1') == (1, 0, 0, 0, 0, 0) + assert digit_version('v1.1.5') == (1, 1, 5, 0, 0, 0) diff --git a/Seg_All_In_One_MMSeg/tests/test_engine/test_layer_decay_optimizer_constructor.py b/Seg_All_In_One_MMSeg/tests/test_engine/test_layer_decay_optimizer_constructor.py new file mode 100644 index 0000000..e7d13db --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_engine/test_layer_decay_optimizer_constructor.py @@ -0,0 +1,300 @@ +# Copyright (c) OpenMMLab. All rights reserved. +# from copyreg import constructor +import pytest +import torch +import torch.nn as nn +from mmcv.cnn import ConvModule +from mmengine.optim.optimizer import build_optim_wrapper +from mmengine.registry import init_default_scope + +from mmseg.engine.optimizers.layer_decay_optimizer_constructor import \ + LearningRateDecayOptimizerConstructor + +init_default_scope('mmseg') + +base_lr = 1 +decay_rate = 2 +base_wd = 0.05 +weight_decay = 0.05 + +expected_stage_wise_lr_wd_convnext = [{ + 'weight_decay': 0.0, + 'lr_scale': 128 +}, { + 'weight_decay': 0.0, + 'lr_scale': 1 +}, { + 'weight_decay': 0.05, + 'lr_scale': 64 +}, { + 'weight_decay': 0.0, + 'lr_scale': 64 +}, { + 'weight_decay': 0.05, + 'lr_scale': 32 +}, { + 'weight_decay': 0.0, + 'lr_scale': 32 +}, { + 'weight_decay': 0.05, + 'lr_scale': 16 +}, { + 'weight_decay': 0.0, + 'lr_scale': 16 +}, { + 'weight_decay': 0.05, + 'lr_scale': 8 +}, { + 'weight_decay': 0.0, + 'lr_scale': 8 +}, { + 'weight_decay': 0.05, + 'lr_scale': 128 +}, { + 'weight_decay': 0.05, + 'lr_scale': 1 +}] + +expected_layer_wise_lr_wd_convnext = [{ + 'weight_decay': 0.0, + 'lr_scale': 128 +}, { + 'weight_decay': 0.0, + 'lr_scale': 1 +}, { + 'weight_decay': 0.05, + 'lr_scale': 64 +}, { + 'weight_decay': 0.0, + 'lr_scale': 64 +}, { + 'weight_decay': 0.05, + 'lr_scale': 32 +}, { + 'weight_decay': 0.0, + 'lr_scale': 32 +}, { + 'weight_decay': 0.05, + 'lr_scale': 16 +}, { + 'weight_decay': 0.0, + 'lr_scale': 16 +}, { + 'weight_decay': 0.05, + 'lr_scale': 2 +}, { + 'weight_decay': 0.0, + 'lr_scale': 2 +}, { + 'weight_decay': 0.05, + 'lr_scale': 128 +}, { + 'weight_decay': 0.05, + 'lr_scale': 1 +}] + +expected_layer_wise_wd_lr_beit = [{ + 'weight_decay': 0.0, + 'lr_scale': 16 +}, { + 'weight_decay': 0.05, + 'lr_scale': 8 +}, { + 'weight_decay': 0.0, + 'lr_scale': 8 +}, { + 'weight_decay': 0.05, + 'lr_scale': 4 +}, { + 'weight_decay': 0.0, + 'lr_scale': 4 +}, { + 'weight_decay': 0.05, + 'lr_scale': 2 +}, { + 'weight_decay': 0.0, + 'lr_scale': 2 +}, { + 'weight_decay': 0.05, + 'lr_scale': 1 +}, { + 'weight_decay': 0.0, + 'lr_scale': 1 +}] + + +class ToyConvNeXt(nn.Module): + + def __init__(self): + super().__init__() + self.stages = nn.ModuleList() + for i in range(4): + stage = nn.Sequential(ConvModule(3, 4, kernel_size=1, bias=True)) + self.stages.append(stage) + self.norm0 = nn.BatchNorm2d(2) + + # add some variables to meet unit test coverate rate + self.cls_token = nn.Parameter(torch.ones(1)) + self.mask_token = nn.Parameter(torch.ones(1)) + self.pos_embed = nn.Parameter(torch.ones(1)) + self.stem_norm = nn.Parameter(torch.ones(1)) + self.downsample_norm0 = nn.BatchNorm2d(2) + self.downsample_norm1 = nn.BatchNorm2d(2) + self.downsample_norm2 = nn.BatchNorm2d(2) + self.lin = nn.Parameter(torch.ones(1)) + self.lin.requires_grad = False + self.downsample_layers = nn.ModuleList() + for _ in range(4): + stage = nn.Sequential(nn.Conv2d(3, 4, kernel_size=1, bias=True)) + self.downsample_layers.append(stage) + + +class ToyBEiT(nn.Module): + + def __init__(self): + super().__init__() + # add some variables to meet unit test coverate rate + self.cls_token = nn.Parameter(torch.ones(1)) + self.patch_embed = nn.Parameter(torch.ones(1)) + self.layers = nn.ModuleList() + for _ in range(3): + layer = nn.Conv2d(3, 3, 1) + self.layers.append(layer) + + +class ToyMAE(nn.Module): + + def __init__(self): + super().__init__() + # add some variables to meet unit test coverate rate + self.cls_token = nn.Parameter(torch.ones(1)) + self.patch_embed = nn.Parameter(torch.ones(1)) + self.layers = nn.ModuleList() + for _ in range(3): + layer = nn.Conv2d(3, 3, 1) + self.layers.append(layer) + + +class ToySegmentor(nn.Module): + + def __init__(self, backbone): + super().__init__() + self.backbone = backbone + self.decode_head = nn.Conv2d(2, 2, kernel_size=1, groups=2) + + +class PseudoDataParallel(nn.Module): + + def __init__(self, model): + super().__init__() + self.module = model + + +class ToyViT(nn.Module): + + def __init__(self): + super().__init__() + + +def check_optimizer_lr_wd(optimizer, gt_lr_wd): + assert isinstance(optimizer, torch.optim.AdamW) + assert optimizer.defaults['lr'] == base_lr + assert optimizer.defaults['weight_decay'] == base_wd + param_groups = optimizer.param_groups + print(param_groups) + assert len(param_groups) == len(gt_lr_wd) + for i, param_dict in enumerate(param_groups): + assert param_dict['weight_decay'] == gt_lr_wd[i]['weight_decay'] + assert param_dict['lr_scale'] == gt_lr_wd[i]['lr_scale'] + assert param_dict['lr_scale'] == param_dict['lr'] + + +def test_learning_rate_decay_optimizer_constructor(): + + # Test lr wd for ConvNeXT + backbone = ToyConvNeXt() + model = PseudoDataParallel(ToySegmentor(backbone)) + # stagewise decay + stagewise_paramwise_cfg = dict( + decay_rate=decay_rate, decay_type='stage_wise', num_layers=6) + optimizer_cfg = dict( + type='AdamW', lr=base_lr, betas=(0.9, 0.999), weight_decay=0.05) + optim_wrapper_cfg = dict( + type='OptimWrapper', + optimizer=optimizer_cfg, + paramwise_cfg=stagewise_paramwise_cfg, + constructor='LearningRateDecayOptimizerConstructor') + optim_wrapper = build_optim_wrapper(model, optim_wrapper_cfg) + check_optimizer_lr_wd(optim_wrapper.optimizer, + expected_stage_wise_lr_wd_convnext) + # layerwise decay + layerwise_paramwise_cfg = dict( + decay_rate=decay_rate, decay_type='layer_wise', num_layers=6) + optim_wrapper_cfg = dict( + type='OptimWrapper', + optimizer=optimizer_cfg, + paramwise_cfg=layerwise_paramwise_cfg, + constructor='LearningRateDecayOptimizerConstructor') + optim_wrapper = build_optim_wrapper(model, optim_wrapper_cfg) + check_optimizer_lr_wd(optim_wrapper.optimizer, + expected_layer_wise_lr_wd_convnext) + + # Test lr wd for BEiT + backbone = ToyBEiT() + model = PseudoDataParallel(ToySegmentor(backbone)) + + layerwise_paramwise_cfg = dict( + decay_rate=decay_rate, decay_type='layer_wise', num_layers=3) + optim_wrapper_cfg = dict( + type='OptimWrapper', + optimizer=optimizer_cfg, + paramwise_cfg=layerwise_paramwise_cfg, + constructor='LearningRateDecayOptimizerConstructor') + optim_wrapper = build_optim_wrapper(model, optim_wrapper_cfg) + check_optimizer_lr_wd(optim_wrapper.optimizer, + expected_layer_wise_wd_lr_beit) + + # Test invalidation of lr wd for Vit + backbone = ToyViT() + model = PseudoDataParallel(ToySegmentor(backbone)) + with pytest.raises(NotImplementedError): + optim_constructor = LearningRateDecayOptimizerConstructor( + optim_wrapper_cfg, layerwise_paramwise_cfg) + optim_constructor(model) + with pytest.raises(NotImplementedError): + optim_constructor = LearningRateDecayOptimizerConstructor( + optim_wrapper_cfg, stagewise_paramwise_cfg) + optim_constructor(model) + + # Test lr wd for MAE + backbone = ToyMAE() + model = PseudoDataParallel(ToySegmentor(backbone)) + + layerwise_paramwise_cfg = dict( + decay_rate=decay_rate, decay_type='layer_wise', num_layers=3) + optim_wrapper_cfg = dict( + type='OptimWrapper', + optimizer=optimizer_cfg, + paramwise_cfg=layerwise_paramwise_cfg, + constructor='LearningRateDecayOptimizerConstructor') + optim_wrapper = build_optim_wrapper(model, optim_wrapper_cfg) + check_optimizer_lr_wd(optim_wrapper.optimizer, + expected_layer_wise_wd_lr_beit) + + +def test_beit_layer_decay_optimizer_constructor(): + + # paramwise_cfg with BEiTExampleModel + backbone = ToyBEiT() + model = PseudoDataParallel(ToySegmentor(backbone)) + paramwise_cfg = dict(layer_decay_rate=2, num_layers=3) + optim_wrapper_cfg = dict( + type='OptimWrapper', + constructor='LayerDecayOptimizerConstructor', + paramwise_cfg=paramwise_cfg, + optimizer=dict( + type='AdamW', lr=1, betas=(0.9, 0.999), weight_decay=0.05)) + optim_wrapper = build_optim_wrapper(model, optim_wrapper_cfg) + # optimizer = optim_wrapper_builder(model) + check_optimizer_lr_wd(optim_wrapper.optimizer, + expected_layer_wise_wd_lr_beit) diff --git a/Seg_All_In_One_MMSeg/tests/test_engine/test_optimizer.py b/Seg_All_In_One_MMSeg/tests/test_engine/test_optimizer.py new file mode 100644 index 0000000..af69f5f --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_engine/test_optimizer.py @@ -0,0 +1,33 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn as nn +from mmengine.optim import build_optim_wrapper + + +class ExampleModel(nn.Module): + + def __init__(self): + super().__init__() + self.param1 = nn.Parameter(torch.ones(1)) + self.conv1 = nn.Conv2d(3, 4, kernel_size=1, bias=False) + self.conv2 = nn.Conv2d(4, 2, kernel_size=1) + self.bn = nn.BatchNorm2d(2) + + def forward(self, x): + return x + + +base_lr = 0.01 +base_wd = 0.0001 +momentum = 0.9 + + +def test_build_optimizer(): + model = ExampleModel() + optim_wrapper_cfg = dict( + type='OptimWrapper', + optimizer=dict( + type='SGD', lr=base_lr, weight_decay=base_wd, momentum=momentum)) + optim_wrapper = build_optim_wrapper(model, optim_wrapper_cfg) + # test whether optimizer is successfully built from parent. + assert isinstance(optim_wrapper.optimizer, torch.optim.SGD) diff --git a/Seg_All_In_One_MMSeg/tests/test_engine/test_visualization_hook.py b/Seg_All_In_One_MMSeg/tests/test_engine/test_visualization_hook.py new file mode 100644 index 0000000..022e27c --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_engine/test_visualization_hook.py @@ -0,0 +1,64 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from unittest import TestCase +from unittest.mock import Mock + +import torch +from mmengine.structures import PixelData + +from mmseg.engine.hooks import SegVisualizationHook +from mmseg.structures import SegDataSample +from mmseg.visualization import SegLocalVisualizer + + +class TestVisualizationHook(TestCase): + + def setUp(self) -> None: + + h = 288 + w = 512 + num_class = 2 + + SegLocalVisualizer.get_instance('visualizer') + SegLocalVisualizer.dataset_meta = dict( + classes=('background', 'foreground'), + palette=[[120, 120, 120], [6, 230, 230]]) + + data_sample = SegDataSample() + data_sample.set_metainfo({'img_path': 'tests/data/color.jpg'}) + self.data_batch = [{'data_sample': data_sample}] * 2 + + pred_sem_seg_data = dict(data=torch.randint(0, num_class, (1, h, w))) + pred_sem_seg = PixelData(**pred_sem_seg_data) + pred_seg_data_sample = SegDataSample() + pred_seg_data_sample.set_metainfo({'img_path': 'tests/data/color.jpg'}) + pred_seg_data_sample.pred_sem_seg = pred_sem_seg + self.outputs = [pred_seg_data_sample] * 2 + + def test_after_iter(self): + runner = Mock() + runner.iter = 1 + hook = SegVisualizationHook(draw=True, interval=1) + hook._after_iter( + runner, 1, self.data_batch, self.outputs, mode='train') + hook._after_iter(runner, 1, self.data_batch, self.outputs, mode='val') + hook._after_iter(runner, 1, self.data_batch, self.outputs, mode='test') + + def test_after_val_iter(self): + runner = Mock() + runner.iter = 2 + hook = SegVisualizationHook(interval=1) + hook.after_val_iter(runner, 1, self.data_batch, self.outputs) + + hook = SegVisualizationHook(draw=True, interval=1) + hook.after_val_iter(runner, 1, self.data_batch, self.outputs) + + hook = SegVisualizationHook( + draw=True, interval=1, show=True, wait_time=1) + hook.after_val_iter(runner, 1, self.data_batch, self.outputs) + + def test_after_test_iter(self): + runner = Mock() + hook = SegVisualizationHook(draw=True, interval=1) + assert hook._test_index == 0 + hook.after_test_iter(runner, 1, self.data_batch, self.outputs) + assert hook._test_index == len(self.outputs) diff --git a/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_citys_metric.py b/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_citys_metric.py new file mode 100644 index 0000000..06f956f --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_citys_metric.py @@ -0,0 +1,119 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import shutil +from unittest import TestCase + +import numpy as np +import pytest +import torch +from mmengine.structures import PixelData + +from mmseg.evaluation import CityscapesMetric +from mmseg.structures import SegDataSample + + +class TestCityscapesMetric(TestCase): + + def _demo_mm_inputs(self, + batch_size=1, + image_shapes=(3, 128, 256), + num_classes=5): + """Create a superset of inputs needed to run test or train batches. + + Args: + batch_size (int): batch size. Default to 2. + image_shapes (List[tuple], Optional): image shape. + Default to (3, 64, 64) + num_classes (int): number of different classes. + Default to 5. + """ + if isinstance(image_shapes, list): + assert len(image_shapes) == batch_size + else: + image_shapes = [image_shapes] * batch_size + + packed_inputs = [] + for idx in range(batch_size): + image_shape = image_shapes[idx] + _, h, w = image_shape + + data_sample = SegDataSample() + gt_semantic_seg = np.random.randint( + 0, num_classes, (1, h, w), dtype=np.uint8) + gt_semantic_seg = torch.LongTensor(gt_semantic_seg) + gt_sem_seg_data = dict(data=gt_semantic_seg) + data_sample.gt_sem_seg = PixelData(**gt_sem_seg_data) + data_sample = data_sample.to_dict() + data_sample[ + 'seg_map_path'] = 'tests/data/pseudo_cityscapes_dataset/gtFine/val/frankfurt/frankfurt_000000_000294_gtFine_labelTrainIds.png' # noqa + packed_inputs.append(data_sample) + + return packed_inputs + + def _demo_mm_model_output(self, + batch_size=1, + image_shapes=(3, 128, 256), + num_classes=5): + """Create a superset of inputs needed to run test or train batches. + + Args: + batch_size (int): batch size. Default to 2. + image_shapes (List[tuple], Optional): image shape. + Default to (3, 64, 64) + num_classes (int): number of different classes. + Default to 5. + """ + results_dict = dict() + _, h, w = image_shapes + seg_logit = torch.randn(batch_size, num_classes, h, w) + results_dict['seg_logits'] = seg_logit + seg_pred = np.random.randint( + 0, num_classes, (batch_size, h, w), dtype=np.uint8) + seg_pred = torch.LongTensor(seg_pred) + results_dict['pred_sem_seg'] = seg_pred + + batch_datasampes = [ + SegDataSample() + for _ in range(results_dict['pred_sem_seg'].shape[0]) + ] + for key, value in results_dict.items(): + for i in range(value.shape[0]): + setattr(batch_datasampes[i], key, PixelData(data=value[i])) + + _predictions = [] + for pred in batch_datasampes: + test_data = pred.to_dict() + test_data[ + 'img_path'] = 'tests/data/pseudo_cityscapes_dataset/leftImg8bit/val/frankfurt/frankfurt_000000_000294_leftImg8bit.png' # noqa + _predictions.append(test_data) + + return _predictions + + def test_evaluate(self): + """Test using the metric in the same way as Evalutor.""" + + data_batch = self._demo_mm_inputs(2) + predictions = self._demo_mm_model_output(2) + data_samples = [ + dict(**data, **result) + for data, result in zip(data_batch, predictions) + ] + # test keep_results should be True when format_only is True + with pytest.raises(AssertionError): + CityscapesMetric( + output_dir='tmp', format_only=True, keep_results=False) + + # test evaluate with cityscape metric + metric = CityscapesMetric(output_dir='tmp') + metric.process(data_batch, data_samples) + res = metric.evaluate(2) + self.assertIsInstance(res, dict) + + # test format_only + metric = CityscapesMetric( + output_dir='tmp', format_only=True, keep_results=True) + metric.process(data_batch, data_samples) + metric.evaluate(2) + assert osp.exists('tmp') + assert osp.isfile('tmp/frankfurt_000000_000294_leftImg8bit.png') + shutil.rmtree('tmp') diff --git a/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_depth_metric.py b/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_depth_metric.py new file mode 100644 index 0000000..a172db8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_depth_metric.py @@ -0,0 +1,85 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import shutil +from unittest import TestCase + +import torch +from mmengine.structures import PixelData + +from mmseg.evaluation import DepthMetric +from mmseg.structures import SegDataSample + + +class TestDepthMetric(TestCase): + + def _demo_mm_inputs(self, + batch_size=2, + image_shapes=(3, 64, 64), + num_classes=5): + """Create a superset of inputs needed to run test or train batches. + + Args: + batch_size (int): batch size. Default to 2. + image_shapes (List[tuple], Optional): image shape. + Default to (3, 64, 64) + num_classes (int): number of different classes. + Default to 5. + """ + if isinstance(image_shapes, list): + assert len(image_shapes) == batch_size + else: + image_shapes = [image_shapes] * batch_size + + data_samples = [] + for idx in range(batch_size): + image_shape = image_shapes[idx] + _, h, w = image_shape + + data_sample = SegDataSample() + gt_depth_map = torch.rand((1, h, w)) * 10 + data_sample.gt_depth_map = PixelData(data=gt_depth_map) + + data_samples.append(data_sample.to_dict()) + + return data_samples + + def _demo_mm_model_output(self, + data_samples, + batch_size=2, + image_shapes=(3, 64, 64), + num_classes=5): + + _, h, w = image_shapes + + for data_sample in data_samples: + data_sample['pred_depth_map'] = dict(data=torch.randn(1, h, w)) + + data_sample[ + 'img_path'] = 'tests/data/pseudo_dataset/imgs/00000_img.jpg' + return data_samples + + def test_evaluate(self): + """Test using the metric in the same way as Evalutor.""" + + data_samples = self._demo_mm_inputs() + data_samples = self._demo_mm_model_output(data_samples) + + depth_metric = DepthMetric() + depth_metric.process([0] * len(data_samples), data_samples) + res = depth_metric.compute_metrics(depth_metric.results) + self.assertIsInstance(res, dict) + + # test save depth map file in output_dir + depth_metric = DepthMetric(output_dir='tmp') + depth_metric.process([0] * len(data_samples), data_samples) + assert osp.exists('tmp') + assert osp.isfile('tmp/00000_img.png') + shutil.rmtree('tmp') + + # test format_only + depth_metric = DepthMetric(output_dir='tmp', format_only=True) + depth_metric.process([0] * len(data_samples), data_samples) + assert depth_metric.results == [] + assert osp.exists('tmp') + assert osp.isfile('tmp/00000_img.png') + shutil.rmtree('tmp') diff --git a/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_iou_metric.py b/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_iou_metric.py new file mode 100644 index 0000000..7a0e9d5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_evaluation/test_metrics/test_iou_metric.py @@ -0,0 +1,104 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp +import shutil +from unittest import TestCase + +import numpy as np +import torch +from mmengine.structures import PixelData + +from mmseg.evaluation import IoUMetric +from mmseg.structures import SegDataSample + + +class TestIoUMetric(TestCase): + + def _demo_mm_inputs(self, + batch_size=2, + image_shapes=(3, 64, 64), + num_classes=5): + """Create a superset of inputs needed to run test or train batches. + + Args: + batch_size (int): batch size. Default to 2. + image_shapes (List[tuple], Optional): image shape. + Default to (3, 64, 64) + num_classes (int): number of different classes. + Default to 5. + """ + if isinstance(image_shapes, list): + assert len(image_shapes) == batch_size + else: + image_shapes = [image_shapes] * batch_size + + data_samples = [] + for idx in range(batch_size): + image_shape = image_shapes[idx] + _, h, w = image_shape + + data_sample = SegDataSample() + gt_semantic_seg = np.random.randint( + 0, num_classes, (1, h, w), dtype=np.uint8) + gt_semantic_seg = torch.LongTensor(gt_semantic_seg) + gt_sem_seg_data = dict(data=gt_semantic_seg) + data_sample.gt_sem_seg = PixelData(**gt_sem_seg_data) + + data_samples.append(data_sample.to_dict()) + + return data_samples + + def _demo_mm_model_output(self, + data_samples, + batch_size=2, + image_shapes=(3, 64, 64), + num_classes=5): + + _, h, w = image_shapes + + for data_sample in data_samples: + data_sample['seg_logits'] = dict( + data=torch.randn(num_classes, h, w)) + data_sample['pred_sem_seg'] = dict( + data=torch.randint(0, num_classes, (1, h, w))) + data_sample[ + 'img_path'] = 'tests/data/pseudo_dataset/imgs/00000_img.jpg' + return data_samples + + def test_evaluate(self): + """Test using the metric in the same way as Evalutor.""" + + data_samples = self._demo_mm_inputs() + data_samples = self._demo_mm_model_output(data_samples) + + iou_metric = IoUMetric(iou_metrics=['mIoU']) + iou_metric.dataset_meta = dict( + classes=['wall', 'building', 'sky', 'floor', 'tree'], + label_map=dict(), + reduce_zero_label=False) + iou_metric.process([0] * len(data_samples), data_samples) + res = iou_metric.evaluate(2) + self.assertIsInstance(res, dict) + + # test save segment file in output_dir + iou_metric = IoUMetric(iou_metrics=['mIoU'], output_dir='tmp') + iou_metric.dataset_meta = dict( + classes=['wall', 'building', 'sky', 'floor', 'tree'], + label_map=dict(), + reduce_zero_label=False) + iou_metric.process([0] * len(data_samples), data_samples) + assert osp.exists('tmp') + assert osp.isfile('tmp/00000_img.png') + shutil.rmtree('tmp') + + # test format_only + iou_metric = IoUMetric( + iou_metrics=['mIoU'], output_dir='tmp', format_only=True) + iou_metric.dataset_meta = dict( + classes=['wall', 'building', 'sky', 'floor', 'tree'], + label_map=dict(), + reduce_zero_label=False) + iou_metric.process([0] * len(data_samples), data_samples) + assert iou_metric.results == [] + assert osp.exists('tmp') + assert osp.isfile('tmp/00000_img.png') + shutil.rmtree('tmp') diff --git a/Seg_All_In_One_MMSeg/tests/test_models/__init__.py b/Seg_All_In_One_MMSeg/tests/test_models/__init__.py new file mode 100644 index 0000000..ef101fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/__init__.py @@ -0,0 +1 @@ +# Copyright (c) OpenMMLab. All rights reserved. diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_assigners/test_hungarian_assigner.py b/Seg_All_In_One_MMSeg/tests/test_models/test_assigners/test_hungarian_assigner.py new file mode 100644 index 0000000..2cdb1de --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_assigners/test_hungarian_assigner.py @@ -0,0 +1,77 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from unittest import TestCase + +import torch +from mmengine.structures import InstanceData + +from mmseg.models.assigners import HungarianAssigner + + +class TestHungarianAssigner(TestCase): + + def test_init(self): + with self.assertRaises(AssertionError): + HungarianAssigner([]) + + def test_hungarian_match_assigner(self): + assigner = HungarianAssigner([ + dict(type='ClassificationCost', weight=2.0), + dict(type='CrossEntropyLossCost', weight=5.0, use_sigmoid=True), + dict(type='DiceCost', weight=5.0, pred_act=True, eps=1.0) + ]) + num_classes = 3 + num_masks = 10 + num_points = 20 + gt_instances = InstanceData() + gt_instances.labels = torch.randint(0, num_classes, (num_classes, )) + gt_instances.masks = torch.randint(0, 2, (num_classes, num_points)) + pred_instances = InstanceData() + pred_instances.scores = torch.rand((num_masks, num_classes)) + pred_instances.masks = torch.rand((num_masks, num_points)) + + matched_quiery_inds, matched_label_inds = \ + assigner.assign(pred_instances, gt_instances) + unique_quiery_inds = torch.unique(matched_quiery_inds) + unique_label_inds = torch.unique(matched_label_inds) + self.assertTrue(len(unique_quiery_inds) == len(matched_quiery_inds)) + self.assertTrue( + torch.equal(unique_label_inds, torch.arange(0, num_classes))) + + def test_cls_match_cost(self): + num_classes = 3 + num_masks = 10 + gt_instances = InstanceData() + gt_instances.labels = torch.randint(0, num_classes, (num_classes, )) + pred_instances = InstanceData() + pred_instances.scores = torch.rand((num_masks, num_classes)) + + # test ClassificationCost + assigner = HungarianAssigner(dict(type='ClassificationCost')) + matched_quiery_inds, matched_label_inds = \ + assigner.assign(pred_instances, gt_instances) + unique_quiery_inds = torch.unique(matched_quiery_inds) + unique_label_inds = torch.unique(matched_label_inds) + self.assertTrue(len(unique_quiery_inds) == len(matched_quiery_inds)) + self.assertTrue( + torch.equal(unique_label_inds, torch.arange(0, num_classes))) + + def test_mask_match_cost(self): + num_classes = 3 + num_masks = 10 + num_points = 20 + gt_instances = InstanceData() + gt_instances.masks = torch.randint(0, 2, (num_classes, num_points)) + pred_instances = InstanceData() + pred_instances.masks = torch.rand((num_masks, num_points)) + + # test DiceCost + assigner = HungarianAssigner( + dict(type='DiceCost', pred_act=True, eps=1.0)) + assign_result = assigner.assign(pred_instances, gt_instances) + self.assertTrue(len(assign_result[0]) == len(assign_result[1])) + + # test CrossEntropyLossCost + assigner = HungarianAssigner( + dict(type='CrossEntropyLossCost', use_sigmoid=True)) + assign_result = assigner.assign(pred_instances, gt_instances) + self.assertTrue(len(assign_result[0]) == len(assign_result[1])) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/__init__.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/__init__.py new file mode 100644 index 0000000..ef101fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/__init__.py @@ -0,0 +1 @@ +# Copyright (c) OpenMMLab. All rights reserved. diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_beit.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_beit.py new file mode 100644 index 0000000..59a12c5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_beit.py @@ -0,0 +1,185 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones.beit import BEiT +from .utils import check_norm_state + + +def test_beit_backbone(): + with pytest.raises(TypeError): + # pretrained must be a string path + model = BEiT() + model.init_weights(pretrained=0) + + with pytest.raises(TypeError): + # img_size must be int or tuple + model = BEiT(img_size=512.0) + + with pytest.raises(TypeError): + # out_indices must be int ,list or tuple + model = BEiT(out_indices=1.) + + with pytest.raises(AssertionError): + # The length of img_size tuple must be lower than 3. + BEiT(img_size=(224, 224, 224)) + + with pytest.raises(TypeError): + # Pretrained must be None or Str. + BEiT(pretrained=123) + + # Test img_size isinstance tuple + imgs = torch.randn(1, 3, 224, 224) + model = BEiT(img_size=(224, )) + model.init_weights() + model(imgs) + + # Test img_size isinstance tuple + imgs = torch.randn(1, 3, 224, 224) + model = BEiT(img_size=(224, 224)) + model(imgs) + + # Test norm_eval = True + model = BEiT(norm_eval=True) + model.train() + + # Test BEiT backbone with input size of 224 and patch size of 16 + model = BEiT() + model.init_weights() + model.train() + + # Test qv_bias + model = BEiT(qv_bias=False) + model.train() + + # Test out_indices = list + model = BEiT(out_indices=[2, 4, 8, 12]) + model.train() + + assert check_norm_state(model.modules(), True) + + # Test image size = (224, 224) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test BEiT backbone with input size of 256 and patch size of 16 + model = BEiT(img_size=(256, 256)) + model.init_weights() + model.train() + imgs = torch.randn(1, 3, 256, 256) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 16, 16) + + # Test BEiT backbone with input size of 32 and patch size of 16 + model = BEiT(img_size=(32, 32)) + model.init_weights() + model.train() + imgs = torch.randn(1, 3, 32, 32) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 2, 2) + + # Test unbalanced size input image + model = BEiT(img_size=(112, 224)) + model.init_weights() + model.train() + imgs = torch.randn(1, 3, 112, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 7, 14) + + # Test irregular input image + model = BEiT(img_size=(234, 345)) + model.init_weights() + model.train() + imgs = torch.randn(1, 3, 234, 345) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 21) + + # Test init_values=0 + model = BEiT(init_values=0) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test final norm + model = BEiT(final_norm=True) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test patch norm + model = BEiT(patch_norm=True) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + +def test_beit_init(): + path = 'PATH_THAT_DO_NOT_EXIST' + # Test all combinations of pretrained and init_cfg + # pretrained=None, init_cfg=None + model = BEiT(pretrained=None, init_cfg=None) + assert model.init_cfg is None + model.init_weights() + + # pretrained=None + # init_cfg loads pretrain from an non-existent file + model = BEiT( + pretrained=None, init_cfg=dict(type='Pretrained', checkpoint=path)) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # test resize_rel_pos_embed + value = torch.randn(732, 16) + ckpt = { + 'state_dict': { + 'layers.0.attn.relative_position_index': 0, + 'layers.0.attn.relative_position_bias_table': value + } + } + model = BEiT(img_size=(512, 512)) + # If scipy is installed, this AttributeError would not be raised. + from mmengine.utils import is_installed + if not is_installed('scipy'): + with pytest.raises(AttributeError): + model.resize_rel_pos_embed(ckpt) + + # pretrained=None + # init_cfg=123, whose type is unsupported + model = BEiT(pretrained=None, init_cfg=123) + with pytest.raises(TypeError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg=None + model = BEiT(pretrained=path, init_cfg=None) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = BEiT( + pretrained=path, init_cfg=dict(type='Pretrained', checkpoint=path)) + with pytest.raises(AssertionError): + model = BEiT(pretrained=path, init_cfg=123) + + # pretrain=123, whose type is unsupported + # init_cfg=None + with pytest.raises(TypeError): + model = BEiT(pretrained=123, init_cfg=None) + + # pretrain=123, whose type is unsupported + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = BEiT( + pretrained=123, init_cfg=dict(type='Pretrained', checkpoint=path)) + + # pretrain=123, whose type is unsupported + # init_cfg=123, whose type is unsupported + with pytest.raises(AssertionError): + model = BEiT(pretrained=123, init_cfg=123) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_bisenetv1.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_bisenetv1.py new file mode 100644 index 0000000..c067749 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_bisenetv1.py @@ -0,0 +1,109 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import BiSeNetV1 +from mmseg.models.backbones.bisenetv1 import (AttentionRefinementModule, + ContextPath, FeatureFusionModule, + SpatialPath) + + +def test_bisenetv1_backbone(): + # Test BiSeNetV1 Standard Forward + backbone_cfg = dict( + type='ResNet', + in_channels=3, + depth=18, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 1, 1), + strides=(1, 2, 2, 2), + norm_eval=False, + style='pytorch', + contract_dilation=True) + model = BiSeNetV1(in_channels=3, backbone_cfg=backbone_cfg) + model.init_weights() + model.train() + batch_size = 2 + imgs = torch.randn(batch_size, 3, 64, 128) + feat = model(imgs) + + assert len(feat) == 3 + # output for segment Head + assert feat[0].shape == torch.Size([batch_size, 256, 8, 16]) + # for auxiliary head 1 + assert feat[1].shape == torch.Size([batch_size, 128, 8, 16]) + # for auxiliary head 2 + assert feat[2].shape == torch.Size([batch_size, 128, 4, 8]) + + # Test input with rare shape + batch_size = 2 + imgs = torch.randn(batch_size, 3, 95, 27) + feat = model(imgs) + assert len(feat) == 3 + + with pytest.raises(AssertionError): + # BiSeNetV1 spatial path channel constraints. + BiSeNetV1( + backbone_cfg=backbone_cfg, + in_channels=3, + spatial_channels=(16, 16, 16)) + + with pytest.raises(AssertionError): + # BiSeNetV1 context path constraints. + BiSeNetV1( + backbone_cfg=backbone_cfg, + in_channels=3, + context_channels=(16, 32, 64, 128)) + + +def test_bisenetv1_spatial_path(): + with pytest.raises(AssertionError): + # BiSeNetV1 spatial path channel constraints. + SpatialPath(num_channels=(16, 16, 16), in_channels=3) + + +def test_bisenetv1_context_path(): + backbone_cfg = dict( + type='ResNet', + in_channels=3, + depth=50, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 1, 1), + strides=(1, 2, 2, 2), + norm_eval=False, + style='pytorch', + contract_dilation=True) + + with pytest.raises(AssertionError): + # BiSeNetV1 context path constraints. + ContextPath( + backbone_cfg=backbone_cfg, context_channels=(16, 32, 64, 128)) + + +def test_bisenetv1_attention_refinement_module(): + x_arm = AttentionRefinementModule(32, 8) + assert x_arm.conv_layer.in_channels == 32 + assert x_arm.conv_layer.out_channels == 8 + assert x_arm.conv_layer.kernel_size == (3, 3) + x = torch.randn(2, 32, 8, 16) + x_out = x_arm(x) + assert x_out.shape == torch.Size([2, 8, 8, 16]) + + +def test_bisenetv1_feature_fusion_module(): + ffm = FeatureFusionModule(16, 32) + assert ffm.conv1.in_channels == 16 + assert ffm.conv1.out_channels == 32 + assert ffm.conv1.kernel_size == (1, 1) + assert ffm.gap.output_size == (1, 1) + assert ffm.conv_atten[0].in_channels == 32 + assert ffm.conv_atten[0].out_channels == 32 + assert ffm.conv_atten[0].kernel_size == (1, 1) + + ffm = FeatureFusionModule(16, 16) + x1 = torch.randn(2, 8, 8, 16) + x2 = torch.randn(2, 8, 8, 16) + x_out = ffm(x1, x2) + assert x_out.shape == torch.Size([2, 16, 8, 16]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_bisenetv2.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_bisenetv2.py new file mode 100644 index 0000000..cf2dfb3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_bisenetv2.py @@ -0,0 +1,57 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmcv.cnn import ConvModule + +from mmseg.models.backbones import BiSeNetV2 +from mmseg.models.backbones.bisenetv2 import (BGALayer, DetailBranch, + SemanticBranch) + + +def test_bisenetv2_backbone(): + # Test BiSeNetV2 Standard Forward + model = BiSeNetV2() + model.init_weights() + model.train() + batch_size = 2 + imgs = torch.randn(batch_size, 3, 128, 256) + feat = model(imgs) + + assert len(feat) == 5 + # output for segment Head + assert feat[0].shape == torch.Size([batch_size, 128, 16, 32]) + # for auxiliary head 1 + assert feat[1].shape == torch.Size([batch_size, 16, 32, 64]) + # for auxiliary head 2 + assert feat[2].shape == torch.Size([batch_size, 32, 16, 32]) + # for auxiliary head 3 + assert feat[3].shape == torch.Size([batch_size, 64, 8, 16]) + # for auxiliary head 4 + assert feat[4].shape == torch.Size([batch_size, 128, 4, 8]) + + # Test input with rare shape + batch_size = 2 + imgs = torch.randn(batch_size, 3, 95, 27) + feat = model(imgs) + assert len(feat) == 5 + + +def test_bisenetv2_DetailBranch(): + x = torch.randn(1, 3, 32, 64) + detail_branch = DetailBranch(detail_channels=(64, 16, 32)) + assert isinstance(detail_branch.detail_branch[0][0], ConvModule) + x_out = detail_branch(x) + assert x_out.shape == torch.Size([1, 32, 4, 8]) + + +def test_bisenetv2_SemanticBranch(): + semantic_branch = SemanticBranch(semantic_channels=(16, 32, 64, 128)) + assert semantic_branch.stage1.pool.stride == 2 + + +def test_bisenetv2_BGALayer(): + x_a = torch.randn(1, 8, 8, 16) + x_b = torch.randn(1, 8, 2, 4) + bga = BGALayer(out_channels=8) + assert isinstance(bga.conv, ConvModule) + x_out = bga(x_a, x_b) + assert x_out.shape == torch.Size([1, 8, 8, 16]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_blocks.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_blocks.py new file mode 100644 index 0000000..7a65d27 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_blocks.py @@ -0,0 +1,187 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import mmcv +import pytest +import torch +from mmengine.utils import digit_version +from mmengine.utils.dl_utils import TORCH_VERSION + +from mmseg.models.utils import (InvertedResidual, InvertedResidualV3, SELayer, + make_divisible) + + +def test_make_divisible(): + # test with min_value = None + assert make_divisible(10, 4) == 12 + assert make_divisible(9, 4) == 12 + assert make_divisible(1, 4) == 4 + + # test with min_value = 8 + assert make_divisible(10, 4, 8) == 12 + assert make_divisible(9, 4, 8) == 12 + assert make_divisible(1, 4, 8) == 8 + + +def test_inv_residual(): + with pytest.raises(AssertionError): + # test stride assertion. + InvertedResidual(32, 32, 3, 4) + + # test default config with res connection. + # set expand_ratio = 4, stride = 1 and inp=oup. + inv_module = InvertedResidual(32, 32, 1, 4) + assert inv_module.use_res_connect + assert inv_module.conv[0].kernel_size == (1, 1) + assert inv_module.conv[0].padding == 0 + assert inv_module.conv[1].kernel_size == (3, 3) + assert inv_module.conv[1].padding == 1 + assert inv_module.conv[0].with_norm + assert inv_module.conv[1].with_norm + x = torch.rand(1, 32, 64, 64) + output = inv_module(x) + assert output.shape == (1, 32, 64, 64) + + # test inv_residual module without res connection. + # set expand_ratio = 4, stride = 2. + inv_module = InvertedResidual(32, 32, 2, 4) + assert not inv_module.use_res_connect + assert inv_module.conv[0].kernel_size == (1, 1) + x = torch.rand(1, 32, 64, 64) + output = inv_module(x) + assert output.shape == (1, 32, 32, 32) + + # test expand_ratio == 1 + inv_module = InvertedResidual(32, 32, 1, 1) + assert inv_module.conv[0].kernel_size == (3, 3) + x = torch.rand(1, 32, 64, 64) + output = inv_module(x) + assert output.shape == (1, 32, 64, 64) + + # test with checkpoint forward + inv_module = InvertedResidual(32, 32, 1, 1, with_cp=True) + assert inv_module.with_cp + x = torch.rand(1, 32, 64, 64, requires_grad=True) + output = inv_module(x) + assert output.shape == (1, 32, 64, 64) + + +def test_inv_residualv3(): + with pytest.raises(AssertionError): + # test stride assertion. + InvertedResidualV3(32, 32, 16, stride=3) + + with pytest.raises(AssertionError): + # test assertion. + InvertedResidualV3(32, 32, 16, with_expand_conv=False) + + # test with se_cfg=None, with_expand_conv=False + inv_module = InvertedResidualV3(32, 32, 32, with_expand_conv=False) + + assert inv_module.with_res_shortcut is True + assert inv_module.with_se is False + assert inv_module.with_expand_conv is False + assert not hasattr(inv_module, 'expand_conv') + assert isinstance(inv_module.depthwise_conv.conv, torch.nn.Conv2d) + assert inv_module.depthwise_conv.conv.kernel_size == (3, 3) + assert inv_module.depthwise_conv.conv.stride == (1, 1) + assert inv_module.depthwise_conv.conv.padding == (1, 1) + assert isinstance(inv_module.depthwise_conv.bn, torch.nn.BatchNorm2d) + assert isinstance(inv_module.depthwise_conv.activate, torch.nn.ReLU) + assert inv_module.linear_conv.conv.kernel_size == (1, 1) + assert inv_module.linear_conv.conv.stride == (1, 1) + assert inv_module.linear_conv.conv.padding == (0, 0) + assert isinstance(inv_module.linear_conv.bn, torch.nn.BatchNorm2d) + + x = torch.rand(1, 32, 64, 64) + output = inv_module(x) + assert output.shape == (1, 32, 64, 64) + + # test with se_cfg and with_expand_conv + se_cfg = dict( + channels=16, + ratio=4, + act_cfg=(dict(type='ReLU'), + dict(type='HSigmoid', bias=3.0, divisor=6.0))) + act_cfg = dict(type='HSwish') + inv_module = InvertedResidualV3( + 32, 40, 16, 3, 2, se_cfg=se_cfg, act_cfg=act_cfg) + assert inv_module.with_res_shortcut is False + assert inv_module.with_se is True + assert inv_module.with_expand_conv is True + assert inv_module.expand_conv.conv.kernel_size == (1, 1) + assert inv_module.expand_conv.conv.stride == (1, 1) + assert inv_module.expand_conv.conv.padding == (0, 0) + + assert isinstance(inv_module.depthwise_conv.conv, + mmcv.cnn.bricks.Conv2dAdaptivePadding) + assert inv_module.depthwise_conv.conv.kernel_size == (3, 3) + assert inv_module.depthwise_conv.conv.stride == (2, 2) + assert inv_module.depthwise_conv.conv.padding == (0, 0) + assert isinstance(inv_module.depthwise_conv.bn, torch.nn.BatchNorm2d) + + assert inv_module.linear_conv.conv.kernel_size == (1, 1) + assert inv_module.linear_conv.conv.stride == (1, 1) + assert inv_module.linear_conv.conv.padding == (0, 0) + assert isinstance(inv_module.linear_conv.bn, torch.nn.BatchNorm2d) + + if (TORCH_VERSION == 'parrots' + or digit_version(TORCH_VERSION) < digit_version('1.7')): + # Note: Use PyTorch official HSwish + # when torch>=1.7 after MMCV >= 1.4.5. + # Hardswish is not supported when PyTorch version < 1.6. + # And Hardswish in PyTorch 1.6 does not support inplace. + # More details could be found from: + # https://github.com/open-mmlab/mmcv/pull/1709 + assert isinstance(inv_module.expand_conv.activate, mmcv.cnn.HSwish) + assert isinstance(inv_module.depthwise_conv.activate, mmcv.cnn.HSwish) + else: + assert isinstance(inv_module.expand_conv.activate, torch.nn.Hardswish) + assert isinstance(inv_module.depthwise_conv.activate, + torch.nn.Hardswish) + + x = torch.rand(1, 32, 64, 64) + output = inv_module(x) + assert output.shape == (1, 40, 32, 32) + + # test with checkpoint forward + inv_module = InvertedResidualV3( + 32, 40, 16, 3, 2, se_cfg=se_cfg, act_cfg=act_cfg, with_cp=True) + assert inv_module.with_cp + x = torch.randn(2, 32, 64, 64, requires_grad=True) + output = inv_module(x) + assert output.shape == (2, 40, 32, 32) + + +def test_se_layer(): + with pytest.raises(AssertionError): + # test act_cfg assertion. + SELayer(32, act_cfg=(dict(type='ReLU'), )) + + # test config with channels = 16. + se_layer = SELayer(16) + assert se_layer.conv1.conv.kernel_size == (1, 1) + assert se_layer.conv1.conv.stride == (1, 1) + assert se_layer.conv1.conv.padding == (0, 0) + assert isinstance(se_layer.conv1.activate, torch.nn.ReLU) + assert se_layer.conv2.conv.kernel_size == (1, 1) + assert se_layer.conv2.conv.stride == (1, 1) + assert se_layer.conv2.conv.padding == (0, 0) + assert isinstance(se_layer.conv2.activate, mmcv.cnn.HSigmoid) + + x = torch.rand(1, 16, 64, 64) + output = se_layer(x) + assert output.shape == (1, 16, 64, 64) + + # test config with channels = 16, act_cfg = dict(type='ReLU'). + se_layer = SELayer(16, act_cfg=dict(type='ReLU')) + assert se_layer.conv1.conv.kernel_size == (1, 1) + assert se_layer.conv1.conv.stride == (1, 1) + assert se_layer.conv1.conv.padding == (0, 0) + assert isinstance(se_layer.conv1.activate, torch.nn.ReLU) + assert se_layer.conv2.conv.kernel_size == (1, 1) + assert se_layer.conv2.conv.stride == (1, 1) + assert se_layer.conv2.conv.padding == (0, 0) + assert isinstance(se_layer.conv2.activate, torch.nn.ReLU) + + x = torch.rand(1, 16, 64, 64) + output = se_layer(x) + assert output.shape == (1, 16, 64, 64) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_cgnet.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_cgnet.py new file mode 100644 index 0000000..f938525 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_cgnet.py @@ -0,0 +1,151 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import CGNet +from mmseg.models.backbones.cgnet import (ContextGuidedBlock, + GlobalContextExtractor) + + +def test_cgnet_GlobalContextExtractor(): + block = GlobalContextExtractor(16, 16, with_cp=True) + x = torch.randn(2, 16, 64, 64, requires_grad=True) + x_out = block(x) + assert x_out.shape == torch.Size([2, 16, 64, 64]) + + +def test_cgnet_context_guided_block(): + with pytest.raises(AssertionError): + # cgnet ContextGuidedBlock GlobalContextExtractor channel and reduction + # constraints. + ContextGuidedBlock(8, 8) + + # test cgnet ContextGuidedBlock with checkpoint forward + block = ContextGuidedBlock( + 16, 16, act_cfg=dict(type='PReLU'), with_cp=True) + assert block.with_cp + x = torch.randn(2, 16, 64, 64, requires_grad=True) + x_out = block(x) + assert x_out.shape == torch.Size([2, 16, 64, 64]) + + # test cgnet ContextGuidedBlock without checkpoint forward + block = ContextGuidedBlock(32, 32) + assert not block.with_cp + x = torch.randn(3, 32, 32, 32) + x_out = block(x) + assert x_out.shape == torch.Size([3, 32, 32, 32]) + + # test cgnet ContextGuidedBlock with down sampling + block = ContextGuidedBlock(32, 32, downsample=True) + assert block.conv1x1.conv.in_channels == 32 + assert block.conv1x1.conv.out_channels == 32 + assert block.conv1x1.conv.kernel_size == (3, 3) + assert block.conv1x1.conv.stride == (2, 2) + assert block.conv1x1.conv.padding == (1, 1) + + assert block.f_loc.in_channels == 32 + assert block.f_loc.out_channels == 32 + assert block.f_loc.kernel_size == (3, 3) + assert block.f_loc.stride == (1, 1) + assert block.f_loc.padding == (1, 1) + assert block.f_loc.groups == 32 + assert block.f_loc.dilation == (1, 1) + assert block.f_loc.bias is None + + assert block.f_sur.in_channels == 32 + assert block.f_sur.out_channels == 32 + assert block.f_sur.kernel_size == (3, 3) + assert block.f_sur.stride == (1, 1) + assert block.f_sur.padding == (2, 2) + assert block.f_sur.groups == 32 + assert block.f_sur.dilation == (2, 2) + assert block.f_sur.bias is None + + assert block.bottleneck.in_channels == 64 + assert block.bottleneck.out_channels == 32 + assert block.bottleneck.kernel_size == (1, 1) + assert block.bottleneck.stride == (1, 1) + assert block.bottleneck.bias is None + + x = torch.randn(1, 32, 32, 32) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 16, 16]) + + # test cgnet ContextGuidedBlock without down sampling + block = ContextGuidedBlock(32, 32, downsample=False) + assert block.conv1x1.conv.in_channels == 32 + assert block.conv1x1.conv.out_channels == 16 + assert block.conv1x1.conv.kernel_size == (1, 1) + assert block.conv1x1.conv.stride == (1, 1) + assert block.conv1x1.conv.padding == (0, 0) + + assert block.f_loc.in_channels == 16 + assert block.f_loc.out_channels == 16 + assert block.f_loc.kernel_size == (3, 3) + assert block.f_loc.stride == (1, 1) + assert block.f_loc.padding == (1, 1) + assert block.f_loc.groups == 16 + assert block.f_loc.dilation == (1, 1) + assert block.f_loc.bias is None + + assert block.f_sur.in_channels == 16 + assert block.f_sur.out_channels == 16 + assert block.f_sur.kernel_size == (3, 3) + assert block.f_sur.stride == (1, 1) + assert block.f_sur.padding == (2, 2) + assert block.f_sur.groups == 16 + assert block.f_sur.dilation == (2, 2) + assert block.f_sur.bias is None + + x = torch.randn(1, 32, 32, 32) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 32, 32]) + + +def test_cgnet_backbone(): + with pytest.raises(AssertionError): + # check invalid num_channels + CGNet(num_channels=(32, 64, 128, 256)) + + with pytest.raises(AssertionError): + # check invalid num_blocks + CGNet(num_blocks=(3, 21, 3)) + + with pytest.raises(AssertionError): + # check invalid dilation + CGNet(num_blocks=2) + + with pytest.raises(AssertionError): + # check invalid reduction + CGNet(reductions=16) + + with pytest.raises(AssertionError): + # check invalid num_channels and reduction + CGNet(num_channels=(32, 64, 128), reductions=(64, 129)) + + # Test CGNet with default settings + model = CGNet() + model.init_weights() + model.train() + + imgs = torch.randn(2, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 3 + assert feat[0].shape == torch.Size([2, 35, 112, 112]) + assert feat[1].shape == torch.Size([2, 131, 56, 56]) + assert feat[2].shape == torch.Size([2, 256, 28, 28]) + + # Test CGNet with norm_eval True and with_cp True + model = CGNet(norm_eval=True, with_cp=True) + with pytest.raises(TypeError): + # check invalid pretrained + model.init_weights(pretrained=8) + model.init_weights() + model.train() + + imgs = torch.randn(2, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 3 + assert feat[0].shape == torch.Size([2, 35, 112, 112]) + assert feat[1].shape == torch.Size([2, 131, 56, 56]) + assert feat[2].shape == torch.Size([2, 256, 28, 28]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_clip_text_encoder.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_clip_text_encoder.py new file mode 100644 index 0000000..ea06c5b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_clip_text_encoder.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine import Config +from mmengine.registry import init_default_scope + +from mmseg.models.text_encoder import CLIPTextEncoder +from mmseg.utils import get_classes + + +def test_clip_text_encoder(): + init_default_scope('mmseg') + # test vocabulary + output_dims = 8 + embed_dims = 32 + vocabulary = ['cat', 'dog', 'bird', 'car', 'bike'] + cfg = dict( + vocabulary=vocabulary, + templates=['a photo of a {}.'], + embed_dims=embed_dims, + output_dims=output_dims) + cfg = Config(cfg) + + text_encoder = CLIPTextEncoder(**cfg) + if torch.cuda.is_available(): + text_encoder = text_encoder.cuda() + + with torch.no_grad(): + class_embeds = text_encoder() + assert class_embeds.shape == (len(vocabulary) + 1, output_dims) + + # test dataset name + cfg = dict( + dataset_name='vaihingen', + templates=['a photo of a {}.'], + embed_dims=embed_dims, + output_dims=output_dims) + cfg = Config(cfg) + + text_encoder = CLIPTextEncoder(**cfg) + with torch.no_grad(): + class_embeds = text_encoder() + class_nums = len(get_classes('vaihingen')) + assert class_embeds.shape == (class_nums + 1, output_dims) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_erfnet.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_erfnet.py new file mode 100644 index 0000000..6ae7345 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_erfnet.py @@ -0,0 +1,146 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import ERFNet +from mmseg.models.backbones.erfnet import (DownsamplerBlock, NonBottleneck1d, + UpsamplerBlock) + + +def test_erfnet_backbone(): + # Test ERFNet Standard Forward. + model = ERFNet( + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128), + dec_upsample_channels=(64, 16), + dec_stages_non_bottleneck=(2, 2), + dec_non_bottleneck_channels=(64, 16), + dropout_ratio=0.1, + ) + model.init_weights() + model.train() + batch_size = 2 + imgs = torch.randn(batch_size, 3, 256, 512) + output = model(imgs) + + # output for segment Head + assert output[0].shape == torch.Size([batch_size, 16, 128, 256]) + + # Test input with rare shape + batch_size = 2 + imgs = torch.randn(batch_size, 3, 527, 279) + output = model(imgs) + assert len(output[0]) == batch_size + + with pytest.raises(AssertionError): + # Number of encoder downsample block and decoder upsample block. + ERFNet( + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128), + dec_upsample_channels=(128, 64, 16), + dec_stages_non_bottleneck=(2, 2), + dec_non_bottleneck_channels=(64, 16), + dropout_ratio=0.1, + ) + with pytest.raises(AssertionError): + # Number of encoder downsample block and encoder Non-bottleneck block. + ERFNet( + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8, 10), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128), + dec_upsample_channels=(64, 16), + dec_stages_non_bottleneck=(2, 2), + dec_non_bottleneck_channels=(64, 16), + dropout_ratio=0.1, + ) + with pytest.raises(AssertionError): + # Number of encoder downsample block and + # channels of encoder Non-bottleneck block. + ERFNet( + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128, 256), + dec_upsample_channels=(64, 16), + dec_stages_non_bottleneck=(2, 2), + dec_non_bottleneck_channels=(64, 16), + dropout_ratio=0.1, + ) + + with pytest.raises(AssertionError): + # Number of encoder Non-bottleneck block and number of its channels. + ERFNet( + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8, 3), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128), + dec_upsample_channels=(64, 16), + dec_stages_non_bottleneck=(2, 2), + dec_non_bottleneck_channels=(64, 16), + dropout_ratio=0.1, + ) + with pytest.raises(AssertionError): + # Number of decoder upsample block and decoder Non-bottleneck block. + ERFNet( + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128), + dec_upsample_channels=(64, 16), + dec_stages_non_bottleneck=(2, 2, 3), + dec_non_bottleneck_channels=(64, 16), + dropout_ratio=0.1, + ) + with pytest.raises(AssertionError): + # Number of decoder Non-bottleneck block and number of its channels. + ERFNet( + in_channels=3, + enc_downsample_channels=(16, 64, 128), + enc_stage_non_bottlenecks=(5, 8), + enc_non_bottleneck_dilations=(2, 4, 8, 16), + enc_non_bottleneck_channels=(64, 128), + dec_upsample_channels=(64, 16), + dec_stages_non_bottleneck=(2, 2), + dec_non_bottleneck_channels=(64, 16, 8), + dropout_ratio=0.1, + ) + + +def test_erfnet_downsampler_block(): + x_db = DownsamplerBlock(16, 64) + assert x_db.conv.in_channels == 16 + assert x_db.conv.out_channels == 48 + assert len(x_db.bn.weight) == 64 + assert x_db.pool.kernel_size == 2 + assert x_db.pool.stride == 2 + + +def test_erfnet_non_bottleneck_1d(): + x_nb1d = NonBottleneck1d(16, 0, 1) + assert x_nb1d.convs_layers[0].in_channels == 16 + assert x_nb1d.convs_layers[0].out_channels == 16 + assert x_nb1d.convs_layers[2].in_channels == 16 + assert x_nb1d.convs_layers[2].out_channels == 16 + assert x_nb1d.convs_layers[5].in_channels == 16 + assert x_nb1d.convs_layers[5].out_channels == 16 + assert x_nb1d.convs_layers[7].in_channels == 16 + assert x_nb1d.convs_layers[7].out_channels == 16 + assert x_nb1d.convs_layers[9].p == 0 + + +def test_erfnet_upsampler_block(): + x_ub = UpsamplerBlock(64, 16) + assert x_ub.conv.in_channels == 64 + assert x_ub.conv.out_channels == 16 + assert len(x_ub.bn.weight) == 16 diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_fast_scnn.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_fast_scnn.py new file mode 100644 index 0000000..7ee638b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_fast_scnn.py @@ -0,0 +1,42 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import FastSCNN + + +def test_fastscnn_backbone(): + with pytest.raises(AssertionError): + # Fast-SCNN channel constraints. + FastSCNN( + 3, (32, 48), + 64, (64, 96, 128), (2, 2, 1), + global_out_channels=127, + higher_in_channels=64, + lower_in_channels=128) + + # Test FastSCNN Standard Forward + model = FastSCNN( + in_channels=3, + downsample_dw_channels=(4, 6), + global_in_channels=8, + global_block_channels=(8, 12, 16), + global_block_strides=(2, 2, 1), + global_out_channels=16, + higher_in_channels=8, + lower_in_channels=16, + fusion_out_channels=16, + ) + model.init_weights() + model.train() + batch_size = 4 + imgs = torch.randn(batch_size, 3, 64, 128) + feat = model(imgs) + + assert len(feat) == 3 + # higher-res + assert feat[0].shape == torch.Size([batch_size, 8, 8, 16]) + # lower-res + assert feat[1].shape == torch.Size([batch_size, 16, 2, 4]) + # FFM output + assert feat[2].shape == torch.Size([batch_size, 16, 8, 16]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_hrnet.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_hrnet.py new file mode 100644 index 0000000..3e35515 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_hrnet.py @@ -0,0 +1,144 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch +from mmengine.utils.dl_utils.parrots_wrapper import _BatchNorm + +from mmseg.models.backbones.hrnet import HRModule, HRNet +from mmseg.models.backbones.resnet import BasicBlock, Bottleneck + + +@pytest.mark.parametrize('block', [BasicBlock, Bottleneck]) +def test_hrmodule(block): + # Test multiscale forward + num_channles = (32, 64) + in_channels = [c * block.expansion for c in num_channles] + hrmodule = HRModule( + num_branches=2, + blocks=block, + in_channels=in_channels, + num_blocks=(4, 4), + num_channels=num_channles, + ) + + feats = [ + torch.randn(1, in_channels[0], 64, 64), + torch.randn(1, in_channels[1], 32, 32) + ] + feats = hrmodule(feats) + + assert len(feats) == 2 + assert feats[0].shape == torch.Size([1, in_channels[0], 64, 64]) + assert feats[1].shape == torch.Size([1, in_channels[1], 32, 32]) + + # Test single scale forward + num_channles = (32, 64) + in_channels = [c * block.expansion for c in num_channles] + hrmodule = HRModule( + num_branches=2, + blocks=block, + in_channels=in_channels, + num_blocks=(4, 4), + num_channels=num_channles, + multiscale_output=False, + ) + + feats = [ + torch.randn(1, in_channels[0], 64, 64), + torch.randn(1, in_channels[1], 32, 32) + ] + feats = hrmodule(feats) + + assert len(feats) == 1 + assert feats[0].shape == torch.Size([1, in_channels[0], 64, 64]) + + +def test_hrnet_backbone(): + # only have 3 stages + extra = dict( + stage1=dict( + num_modules=1, + num_branches=1, + block='BOTTLENECK', + num_blocks=(4, ), + num_channels=(64, )), + stage2=dict( + num_modules=1, + num_branches=2, + block='BASIC', + num_blocks=(4, 4), + num_channels=(32, 64)), + stage3=dict( + num_modules=4, + num_branches=3, + block='BASIC', + num_blocks=(4, 4, 4), + num_channels=(32, 64, 128))) + + with pytest.raises(AssertionError): + # HRNet now only support 4 stages + HRNet(extra=extra) + extra['stage4'] = dict( + num_modules=3, + num_branches=3, # should be 4 + block='BASIC', + num_blocks=(4, 4, 4, 4), + num_channels=(32, 64, 128, 256)) + + with pytest.raises(AssertionError): + # len(num_blocks) should equal num_branches + HRNet(extra=extra) + + extra['stage4']['num_branches'] = 4 + + # Test hrnetv2p_w32 + model = HRNet(extra=extra) + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 64, 64) + feats = model(imgs) + assert len(feats) == 4 + assert feats[0].shape == torch.Size([1, 32, 16, 16]) + assert feats[3].shape == torch.Size([1, 256, 2, 2]) + + # Test single scale output + model = HRNet(extra=extra, multiscale_output=False) + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 64, 64) + feats = model(imgs) + assert len(feats) == 1 + assert feats[0].shape == torch.Size([1, 32, 16, 16]) + + # Test HRNET with two stage frozen + frozen_stages = 2 + model = HRNet(extra, frozen_stages=frozen_stages) + model.init_weights() + model.train() + assert model.norm1.training is False + + for layer in [model.conv1, model.norm1]: + for param in layer.parameters(): + assert param.requires_grad is False + for i in range(1, frozen_stages + 1): + if i == 1: + layer = getattr(model, f'layer{i}') + transition = getattr(model, f'transition{i}') + elif i == 4: + layer = getattr(model, f'stage{i}') + else: + layer = getattr(model, f'stage{i}') + transition = getattr(model, f'transition{i}') + + for mod in layer.modules(): + if isinstance(mod, _BatchNorm): + assert mod.training is False + for param in layer.parameters(): + assert param.requires_grad is False + + for mod in transition.modules(): + if isinstance(mod, _BatchNorm): + assert mod.training is False + for param in transition.parameters(): + assert param.requires_grad is False diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_icnet.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_icnet.py new file mode 100644 index 0000000..a96d8d8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_icnet.py @@ -0,0 +1,50 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import ICNet + + +def test_icnet_backbone(): + with pytest.raises(TypeError): + # Must give backbone dict in config file. + ICNet( + in_channels=3, + layer_channels=(128, 512), + light_branch_middle_channels=8, + psp_out_channels=128, + out_channels=(16, 128, 128), + backbone_cfg=None) + + # Test ICNet Standard Forward + model = ICNet( + layer_channels=(128, 512), + backbone_cfg=dict( + type='ResNetV1c', + in_channels=3, + depth=18, + num_stages=4, + out_indices=(0, 1, 2, 3), + dilations=(1, 1, 2, 4), + strides=(1, 2, 1, 1), + norm_cfg=dict(type='BN', requires_grad=True), + norm_eval=False, + style='pytorch', + contract_dilation=True), + ) + assert hasattr(model.backbone, + 'maxpool') and model.backbone.maxpool.ceil_mode is True + model.init_weights() + model.train() + batch_size = 2 + imgs = torch.randn(batch_size, 3, 32, 64) + feat = model(imgs) + + assert model.psp_modules[0][0].output_size == 1 + assert model.psp_modules[1][0].output_size == 2 + assert model.psp_modules[2][0].output_size == 3 + assert model.psp_bottleneck.padding == 1 + assert model.conv_sub1[0].padding == 1 + + assert len(feat) == 3 + assert feat[0].shape == torch.Size([batch_size, 64, 4, 8]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mae.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mae.py new file mode 100644 index 0000000..16f52b5 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mae.py @@ -0,0 +1,186 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones.mae import MAE +from .utils import check_norm_state + + +def test_mae_backbone(): + with pytest.raises(TypeError): + # pretrained must be a string path + model = MAE() + model.init_weights(pretrained=0) + + with pytest.raises(TypeError): + # img_size must be int or tuple + model = MAE(img_size=512.0) + + with pytest.raises(TypeError): + # out_indices must be int ,list or tuple + model = MAE(out_indices=1.) + + with pytest.raises(AssertionError): + # The length of img_size tuple must be lower than 3. + MAE(img_size=(224, 224, 224)) + + with pytest.raises(TypeError): + # Pretrained must be None or Str. + MAE(pretrained=123) + + # Test img_size isinstance tuple + imgs = torch.randn(1, 3, 224, 224) + model = MAE(img_size=(224, )) + model.init_weights() + model(imgs) + + # Test img_size isinstance tuple + imgs = torch.randn(1, 3, 224, 224) + model = MAE(img_size=(224, 224)) + model(imgs) + + # Test norm_eval = True + model = MAE(norm_eval=True) + model.train() + + # Test BEiT backbone with input size of 224 and patch size of 16 + model = MAE() + model.init_weights() + model.train() + + # Test out_indices = list + model = MAE(out_indices=[2, 4, 8, 12]) + model.train() + + assert check_norm_state(model.modules(), True) + + # Test image size = (224, 224) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test MAE backbone with input size of 256 and patch size of 16 + model = MAE(img_size=(256, 256)) + model.init_weights() + model.train() + imgs = torch.randn(1, 3, 256, 256) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 16, 16) + + # Test MAE backbone with input size of 32 and patch size of 16 + model = MAE(img_size=(32, 32)) + model.init_weights() + model.train() + imgs = torch.randn(1, 3, 32, 32) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 2, 2) + + # Test unbalanced size input image + model = MAE(img_size=(112, 224)) + model.init_weights() + model.train() + imgs = torch.randn(1, 3, 112, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 7, 14) + + # Test irregular input image + model = MAE(img_size=(234, 345)) + model.init_weights() + model.train() + imgs = torch.randn(1, 3, 234, 345) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 21) + + # Test init_values=0 + model = MAE(init_values=0) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test final norm + model = MAE(final_norm=True) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test patch norm + model = MAE(patch_norm=True) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + +def test_mae_init(): + path = 'PATH_THAT_DO_NOT_EXIST' + # Test all combinations of pretrained and init_cfg + # pretrained=None, init_cfg=None + model = MAE(pretrained=None, init_cfg=None) + assert model.init_cfg is None + model.init_weights() + + # pretrained=None + # init_cfg loads pretrain from an non-existent file + model = MAE( + pretrained=None, init_cfg=dict(type='Pretrained', checkpoint=path)) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # test resize_rel_pos_embed + value = torch.randn(732, 16) + abs_pos_embed_value = torch.rand(1, 17, 768) + ckpt = { + 'state_dict': { + 'layers.0.attn.relative_position_index': 0, + 'layers.0.attn.relative_position_bias_table': value, + 'pos_embed': abs_pos_embed_value + } + } + model = MAE(img_size=(512, 512)) + # If scipy is installed, this AttributeError would not be raised. + from mmengine.utils import is_installed + if not is_installed('scipy'): + with pytest.raises(AttributeError): + model.resize_rel_pos_embed(ckpt) + + # test resize abs pos embed + ckpt = model.resize_abs_pos_embed(ckpt['state_dict']) + + # pretrained=None + # init_cfg=123, whose type is unsupported + model = MAE(pretrained=None, init_cfg=123) + with pytest.raises(TypeError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg=None + model = MAE(pretrained=path, init_cfg=None) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = MAE( + pretrained=path, init_cfg=dict(type='Pretrained', checkpoint=path)) + with pytest.raises(AssertionError): + model = MAE(pretrained=path, init_cfg=123) + + # pretrain=123, whose type is unsupported + # init_cfg=None + with pytest.raises(TypeError): + model = MAE(pretrained=123, init_cfg=None) + + # pretrain=123, whose type is unsupported + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = MAE( + pretrained=123, init_cfg=dict(type='Pretrained', checkpoint=path)) + + # pretrain=123, whose type is unsupported + # init_cfg=123, whose type is unsupported + with pytest.raises(AssertionError): + model = MAE(pretrained=123, init_cfg=123) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mit.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mit.py new file mode 100644 index 0000000..72f74fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mit.py @@ -0,0 +1,122 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import MixVisionTransformer +from mmseg.models.backbones.mit import (EfficientMultiheadAttention, MixFFN, + TransformerEncoderLayer) + + +def test_mit(): + with pytest.raises(TypeError): + # Pretrained represents pretrain url and must be str or None. + MixVisionTransformer(pretrained=123) + + # Test normal input + H, W = (224, 224) + temp = torch.randn((1, 3, H, W)) + model = MixVisionTransformer( + embed_dims=32, num_heads=[1, 2, 5, 8], out_indices=(0, 1, 2, 3)) + model.init_weights() + outs = model(temp) + assert outs[0].shape == (1, 32, H // 4, W // 4) + assert outs[1].shape == (1, 64, H // 8, W // 8) + assert outs[2].shape == (1, 160, H // 16, W // 16) + assert outs[3].shape == (1, 256, H // 32, W // 32) + + # Test non-squared input + H, W = (224, 256) + temp = torch.randn((1, 3, H, W)) + outs = model(temp) + assert outs[0].shape == (1, 32, H // 4, W // 4) + assert outs[1].shape == (1, 64, H // 8, W // 8) + assert outs[2].shape == (1, 160, H // 16, W // 16) + assert outs[3].shape == (1, 256, H // 32, W // 32) + + # Test MixFFN + FFN = MixFFN(64, 128) + hw_shape = (32, 32) + token_len = 32 * 32 + temp = torch.randn((1, token_len, 64)) + # Self identity + out = FFN(temp, hw_shape) + assert out.shape == (1, token_len, 64) + # Out identity + outs = FFN(temp, hw_shape, temp) + assert out.shape == (1, token_len, 64) + + # Test EfficientMHA + MHA = EfficientMultiheadAttention(64, 2) + hw_shape = (32, 32) + token_len = 32 * 32 + temp = torch.randn((1, token_len, 64)) + # Self identity + out = MHA(temp, hw_shape) + assert out.shape == (1, token_len, 64) + # Out identity + outs = MHA(temp, hw_shape, temp) + assert out.shape == (1, token_len, 64) + + # Test TransformerEncoderLayer with checkpoint forward + block = TransformerEncoderLayer( + embed_dims=64, num_heads=4, feedforward_channels=256, with_cp=True) + assert block.with_cp + x = torch.randn(1, 56 * 56, 64) + x_out = block(x, (56, 56)) + assert x_out.shape == torch.Size([1, 56 * 56, 64]) + + +def test_mit_init(): + path = 'PATH_THAT_DO_NOT_EXIST' + # Test all combinations of pretrained and init_cfg + # pretrained=None, init_cfg=None + model = MixVisionTransformer(pretrained=None, init_cfg=None) + assert model.init_cfg is None + model.init_weights() + + # pretrained=None + # init_cfg loads pretrain from an non-existent file + model = MixVisionTransformer( + pretrained=None, init_cfg=dict(type='Pretrained', checkpoint=path)) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained=None + # init_cfg=123, whose type is unsupported + model = MixVisionTransformer(pretrained=None, init_cfg=123) + with pytest.raises(TypeError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg=None + model = MixVisionTransformer(pretrained=path, init_cfg=None) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + MixVisionTransformer( + pretrained=path, init_cfg=dict(type='Pretrained', checkpoint=path)) + with pytest.raises(AssertionError): + MixVisionTransformer(pretrained=path, init_cfg=123) + + # pretrain=123, whose type is unsupported + # init_cfg=None + with pytest.raises(TypeError): + MixVisionTransformer(pretrained=123, init_cfg=None) + + # pretrain=123, whose type is unsupported + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + MixVisionTransformer( + pretrained=123, init_cfg=dict(type='Pretrained', checkpoint=path)) + + # pretrain=123, whose type is unsupported + # init_cfg=123, whose type is unsupported + with pytest.raises(AssertionError): + MixVisionTransformer(pretrained=123, init_cfg=123) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mobilenet_v3.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mobilenet_v3.py new file mode 100644 index 0000000..769ee14 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mobilenet_v3.py @@ -0,0 +1,67 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import MobileNetV3 + + +def test_mobilenet_v3(): + with pytest.raises(AssertionError): + # check invalid arch + MobileNetV3('big') + + with pytest.raises(AssertionError): + # check invalid reduction_factor + MobileNetV3(reduction_factor=0) + + with pytest.raises(ValueError): + # check invalid out_indices + MobileNetV3(out_indices=(0, 1, 15)) + + with pytest.raises(ValueError): + # check invalid frozen_stages + MobileNetV3(frozen_stages=15) + + with pytest.raises(TypeError): + # check invalid pretrained + model = MobileNetV3() + model.init_weights(pretrained=8) + + # Test MobileNetV3 with default settings + model = MobileNetV3() + model.init_weights() + model.train() + + imgs = torch.randn(2, 3, 56, 56) + feat = model(imgs) + assert len(feat) == 3 + assert feat[0].shape == (2, 16, 28, 28) + assert feat[1].shape == (2, 16, 14, 14) + assert feat[2].shape == (2, 576, 7, 7) + + # Test MobileNetV3 with arch = 'large' + model = MobileNetV3(arch='large', out_indices=(1, 3, 16)) + model.init_weights() + model.train() + + imgs = torch.randn(2, 3, 56, 56) + feat = model(imgs) + assert len(feat) == 3 + assert feat[0].shape == (2, 16, 28, 28) + assert feat[1].shape == (2, 24, 14, 14) + assert feat[2].shape == (2, 960, 7, 7) + + # Test MobileNetV3 with norm_eval True, with_cp True and frozen_stages=5 + model = MobileNetV3(norm_eval=True, with_cp=True, frozen_stages=5) + with pytest.raises(TypeError): + # check invalid pretrained + model.init_weights(pretrained=8) + model.init_weights() + model.train() + + imgs = torch.randn(2, 3, 56, 56) + feat = model(imgs) + assert len(feat) == 3 + assert feat[0].shape == (2, 16, 28, 28) + assert feat[1].shape == (2, 16, 14, 14) + assert feat[2].shape == (2, 576, 7, 7) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mscan.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mscan.py new file mode 100644 index 0000000..84dfb8e --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_mscan.py @@ -0,0 +1,69 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.backbones import MSCAN +from mmseg.models.backbones.mscan import (MSCAAttention, MSCASpatialAttention, + OverlapPatchEmbed, StemConv) + + +def test_mscan_backbone(): + # Test MSCAN Standard Forward + model = MSCAN( + embed_dims=[8, 16, 32, 64], + norm_cfg=dict(type='BN', requires_grad=True)) + model.init_weights() + model.train() + batch_size = 2 + imgs = torch.randn(batch_size, 3, 64, 128) + feat = model(imgs) + + assert len(feat) == 4 + # output for segment Head + assert feat[0].shape == torch.Size([batch_size, 8, 16, 32]) + assert feat[1].shape == torch.Size([batch_size, 16, 8, 16]) + assert feat[2].shape == torch.Size([batch_size, 32, 4, 8]) + assert feat[3].shape == torch.Size([batch_size, 64, 2, 4]) + + # Test input with rare shape + batch_size = 2 + imgs = torch.randn(batch_size, 3, 95, 27) + feat = model(imgs) + assert len(feat) == 4 + + +def test_mscan_overlap_patch_embed_module(): + x_overlap_patch_embed = OverlapPatchEmbed( + norm_cfg=dict(type='BN', requires_grad=True)) + assert x_overlap_patch_embed.proj.in_channels == 3 + assert x_overlap_patch_embed.norm.weight.shape == torch.Size([768]) + x = torch.randn(2, 3, 16, 32) + x_out, H, W = x_overlap_patch_embed(x) + assert x_out.shape == torch.Size([2, 32, 768]) + + +def test_mscan_spatial_attention_module(): + x_spatial_attention = MSCASpatialAttention(8) + assert x_spatial_attention.proj_1.kernel_size == (1, 1) + assert x_spatial_attention.proj_2.stride == (1, 1) + x = torch.randn(2, 8, 16, 32) + x_out = x_spatial_attention(x) + assert x_out.shape == torch.Size([2, 8, 16, 32]) + + +def test_mscan_attention_module(): + x_attention = MSCAAttention(8) + assert x_attention.conv0.weight.shape[0] == 8 + assert x_attention.conv3.kernel_size == (1, 1) + x = torch.randn(2, 8, 16, 32) + x_out = x_attention(x) + assert x_out.shape == torch.Size([2, 8, 16, 32]) + + +def test_mscan_stem_module(): + x_stem = StemConv(8, 8, norm_cfg=dict(type='BN', requires_grad=True)) + assert x_stem.proj[0].weight.shape[0] == 4 + assert x_stem.proj[-1].weight.shape[0] == 8 + x = torch.randn(2, 8, 16, 32) + x_out, H, W = x_stem(x) + assert x_out.shape == torch.Size([2, 32, 8]) + assert (H, W) == (4, 8) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_pidnet.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_pidnet.py new file mode 100644 index 0000000..208dfc7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_pidnet.py @@ -0,0 +1,87 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import tempfile + +import torch +from mmengine.registry import init_default_scope + +from mmseg.registry import MODELS + +init_default_scope('mmseg') + + +def test_pidnet_backbone(): + # Test PIDNet Standard Forward + norm_cfg = dict(type='BN', requires_grad=True) + backbone_cfg = dict( + type='PIDNet', + in_channels=3, + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + align_corners=False, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU', inplace=True)) + model = MODELS.build(backbone_cfg) + model.init_weights() + + # Test init weights + temp_file = tempfile.NamedTemporaryFile() + temp_file.close() + torch.save(model.state_dict(), temp_file.name) + backbone_cfg.update( + init_cfg=dict(type='Pretrained', checkpoint=temp_file.name)) + model = MODELS.build(backbone_cfg) + model.init_weights() + os.remove(temp_file.name) + + # Test eval mode + model.eval() + batch_size = 1 + imgs = torch.randn(batch_size, 3, 64, 128) + feats = model(imgs) + + assert type(feats) == torch.Tensor + assert feats.shape == torch.Size([batch_size, 128, 8, 16]) + + # Test train mode + model.train() + batch_size = 2 + imgs = torch.randn(batch_size, 3, 64, 128) + feats = model(imgs) + + assert len(feats) == 3 + # test output for P branch + assert feats[0].shape == torch.Size([batch_size, 64, 8, 16]) + # test output for I branch + assert feats[1].shape == torch.Size([batch_size, 128, 8, 16]) + # test output for D branch + assert feats[2].shape == torch.Size([batch_size, 64, 8, 16]) + + # Test pidnet-m + backbone_cfg.update(channels=64) + model = MODELS.build(backbone_cfg) + feats = model(imgs) + + assert len(feats) == 3 + # test output for P branch + assert feats[0].shape == torch.Size([batch_size, 128, 8, 16]) + # test output for I branch + assert feats[1].shape == torch.Size([batch_size, 256, 8, 16]) + # test output for D branch + assert feats[2].shape == torch.Size([batch_size, 128, 8, 16]) + + # Test pidnet-l + backbone_cfg.update( + channels=64, ppm_channesl=112, num_stem_blocks=3, num_branch_blocks=4) + model = MODELS.build(backbone_cfg) + feats = model(imgs) + + assert len(feats) == 3 + # test output for P branch + assert feats[0].shape == torch.Size([batch_size, 128, 8, 16]) + # test output for I branch + assert feats[1].shape == torch.Size([batch_size, 256, 8, 16]) + # test output for D branch + assert feats[2].shape == torch.Size([batch_size, 128, 8, 16]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnest.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnest.py new file mode 100644 index 0000000..3013f34 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnest.py @@ -0,0 +1,44 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import ResNeSt +from mmseg.models.backbones.resnest import Bottleneck as BottleneckS + + +def test_resnest_bottleneck(): + with pytest.raises(AssertionError): + # Style must be in ['pytorch', 'caffe'] + BottleneckS(64, 64, radix=2, reduction_factor=4, style='tensorflow') + + # Test ResNeSt Bottleneck structure + block = BottleneckS( + 64, 256, radix=2, reduction_factor=4, stride=2, style='pytorch') + assert block.avd_layer.stride == 2 + assert block.conv2.channels == 256 + + # Test ResNeSt Bottleneck forward + block = BottleneckS(64, 16, radix=2, reduction_factor=4) + x = torch.randn(2, 64, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size([2, 64, 56, 56]) + + +def test_resnest_backbone(): + with pytest.raises(KeyError): + # ResNeSt depth should be in [50, 101, 152, 200] + ResNeSt(depth=18) + + # Test ResNeSt with radix 2, reduction_factor 4 + model = ResNeSt( + depth=50, radix=2, reduction_factor=4, out_indices=(0, 1, 2, 3)) + model.init_weights() + model.train() + + imgs = torch.randn(2, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([2, 256, 56, 56]) + assert feat[1].shape == torch.Size([2, 512, 28, 28]) + assert feat[2].shape == torch.Size([2, 1024, 14, 14]) + assert feat[3].shape == torch.Size([2, 2048, 7, 7]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnet.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnet.py new file mode 100644 index 0000000..f2f24ba --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnet.py @@ -0,0 +1,575 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch +from mmcv.ops import DeformConv2dPack +from mmengine.utils.dl_utils.parrots_wrapper import _BatchNorm +from torch.nn.modules import AvgPool2d, GroupNorm + +from mmseg.models.backbones import ResNet, ResNetV1d +from mmseg.models.backbones.resnet import BasicBlock, Bottleneck +from mmseg.models.utils import ResLayer +from .utils import all_zeros, check_norm_state, is_block, is_norm + + +def test_resnet_basic_block(): + with pytest.raises(AssertionError): + # Not implemented yet. + dcn = dict(type='DCN', deform_groups=1, fallback_on_stride=False) + BasicBlock(64, 64, dcn=dcn) + + with pytest.raises(AssertionError): + # Not implemented yet. + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv3') + ] + BasicBlock(64, 64, plugins=plugins) + + with pytest.raises(AssertionError): + # Not implemented yet + plugins = [ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + position='after_conv2') + ] + BasicBlock(64, 64, plugins=plugins) + + # Test BasicBlock with checkpoint forward + block = BasicBlock(16, 16, with_cp=True) + assert block.with_cp + x = torch.randn(1, 16, 28, 28) + x_out = block(x) + assert x_out.shape == torch.Size([1, 16, 28, 28]) + + # test BasicBlock structure and forward + block = BasicBlock(32, 32) + assert block.conv1.in_channels == 32 + assert block.conv1.out_channels == 32 + assert block.conv1.kernel_size == (3, 3) + assert block.conv2.in_channels == 32 + assert block.conv2.out_channels == 32 + assert block.conv2.kernel_size == (3, 3) + x = torch.randn(1, 32, 28, 28) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 28, 28]) + + +def test_resnet_bottleneck(): + with pytest.raises(AssertionError): + # Style must be in ['pytorch', 'caffe'] + Bottleneck(64, 64, style='tensorflow') + + with pytest.raises(AssertionError): + # Allowed positions are 'after_conv1', 'after_conv2', 'after_conv3' + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv4') + ] + Bottleneck(64, 16, plugins=plugins) + + with pytest.raises(AssertionError): + # Need to specify different postfix to avoid duplicate plugin name + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv3'), + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv3') + ] + Bottleneck(64, 16, plugins=plugins) + + with pytest.raises(KeyError): + # Plugin type is not supported + plugins = [dict(cfg=dict(type='WrongPlugin'), position='after_conv3')] + Bottleneck(64, 16, plugins=plugins) + + # Test Bottleneck with checkpoint forward + block = Bottleneck(64, 16, with_cp=True) + assert block.with_cp + x = torch.randn(1, 64, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + # Test Bottleneck style + block = Bottleneck(64, 64, stride=2, style='pytorch') + assert block.conv1.stride == (1, 1) + assert block.conv2.stride == (2, 2) + block = Bottleneck(64, 64, stride=2, style='caffe') + assert block.conv1.stride == (2, 2) + assert block.conv2.stride == (1, 1) + + # Test Bottleneck DCN + dcn = dict(type='DCN', deform_groups=1, fallback_on_stride=False) + with pytest.raises(AssertionError): + Bottleneck(64, 64, dcn=dcn, conv_cfg=dict(type='Conv')) + block = Bottleneck(64, 64, dcn=dcn) + assert isinstance(block.conv2, DeformConv2dPack) + + # Test Bottleneck forward + block = Bottleneck(64, 16) + x = torch.randn(1, 64, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + # Test Bottleneck with 1 ContextBlock after conv3 + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv3') + ] + block = Bottleneck(64, 16, plugins=plugins) + assert block.context_block.in_channels == 64 + x = torch.randn(1, 64, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + # Test Bottleneck with 1 GeneralizedAttention after conv2 + plugins = [ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + position='after_conv2') + ] + block = Bottleneck(64, 16, plugins=plugins) + assert block.gen_attention_block.in_channels == 16 + x = torch.randn(1, 64, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + # Test Bottleneck with 1 GeneralizedAttention after conv2, 1 NonLocal2d + # after conv2, 1 ContextBlock after conv3 + plugins = [ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + position='after_conv2'), + dict(cfg=dict(type='NonLocal2d'), position='after_conv2'), + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv3') + ] + block = Bottleneck(64, 16, plugins=plugins) + assert block.gen_attention_block.in_channels == 16 + assert block.nonlocal_block.in_channels == 16 + assert block.context_block.in_channels == 64 + x = torch.randn(1, 64, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + # Test Bottleneck with 1 ContextBlock after conv2, 2 ContextBlock after + # conv3 + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=1), + position='after_conv2'), + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=2), + position='after_conv3'), + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=3), + position='after_conv3') + ] + block = Bottleneck(64, 16, plugins=plugins) + assert block.context_block1.in_channels == 16 + assert block.context_block2.in_channels == 64 + assert block.context_block3.in_channels == 64 + x = torch.randn(1, 64, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + +def test_resnet_res_layer(): + # Test ResLayer of 3 Bottleneck w\o downsample + layer = ResLayer(Bottleneck, 64, 16, 3) + assert len(layer) == 3 + assert layer[0].conv1.in_channels == 64 + assert layer[0].conv1.out_channels == 16 + for i in range(1, len(layer)): + assert layer[i].conv1.in_channels == 64 + assert layer[i].conv1.out_channels == 16 + for i in range(len(layer)): + assert layer[i].downsample is None + x = torch.randn(1, 64, 56, 56) + x_out = layer(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + # Test ResLayer of 3 Bottleneck with downsample + layer = ResLayer(Bottleneck, 64, 64, 3) + assert layer[0].downsample[0].out_channels == 256 + for i in range(1, len(layer)): + assert layer[i].downsample is None + x = torch.randn(1, 64, 56, 56) + x_out = layer(x) + assert x_out.shape == torch.Size([1, 256, 56, 56]) + + # Test ResLayer of 3 Bottleneck with stride=2 + layer = ResLayer(Bottleneck, 64, 64, 3, stride=2) + assert layer[0].downsample[0].out_channels == 256 + assert layer[0].downsample[0].stride == (2, 2) + for i in range(1, len(layer)): + assert layer[i].downsample is None + x = torch.randn(1, 64, 56, 56) + x_out = layer(x) + assert x_out.shape == torch.Size([1, 256, 28, 28]) + + # Test ResLayer of 3 Bottleneck with stride=2 and average downsample + layer = ResLayer(Bottleneck, 64, 64, 3, stride=2, avg_down=True) + assert isinstance(layer[0].downsample[0], AvgPool2d) + assert layer[0].downsample[1].out_channels == 256 + assert layer[0].downsample[1].stride == (1, 1) + for i in range(1, len(layer)): + assert layer[i].downsample is None + x = torch.randn(1, 64, 56, 56) + x_out = layer(x) + assert x_out.shape == torch.Size([1, 256, 28, 28]) + + # Test ResLayer of 3 Bottleneck with dilation=2 + layer = ResLayer(Bottleneck, 64, 16, 3, dilation=2) + for i in range(len(layer)): + assert layer[i].conv2.dilation == (2, 2) + x = torch.randn(1, 64, 56, 56) + x_out = layer(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + # Test ResLayer of 3 Bottleneck with dilation=2, contract_dilation=True + layer = ResLayer(Bottleneck, 64, 16, 3, dilation=2, contract_dilation=True) + assert layer[0].conv2.dilation == (1, 1) + for i in range(1, len(layer)): + assert layer[i].conv2.dilation == (2, 2) + x = torch.randn(1, 64, 56, 56) + x_out = layer(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + # Test ResLayer of 3 Bottleneck with dilation=2, multi_grid + layer = ResLayer(Bottleneck, 64, 16, 3, dilation=2, multi_grid=(1, 2, 4)) + assert layer[0].conv2.dilation == (1, 1) + assert layer[1].conv2.dilation == (2, 2) + assert layer[2].conv2.dilation == (4, 4) + x = torch.randn(1, 64, 56, 56) + x_out = layer(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + +def test_resnet_backbone(): + """Test resnet backbone.""" + with pytest.raises(KeyError): + # ResNet depth should be in [18, 34, 50, 101, 152] + ResNet(20) + + with pytest.raises(AssertionError): + # In ResNet: 1 <= num_stages <= 4 + ResNet(50, num_stages=0) + + with pytest.raises(AssertionError): + # len(stage_with_dcn) == num_stages + dcn = dict(type='DCN', deform_groups=1, fallback_on_stride=False) + ResNet(50, dcn=dcn, stage_with_dcn=(True, )) + + with pytest.raises(AssertionError): + # len(stage_with_plugin) == num_stages + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True), + position='after_conv3') + ] + ResNet(50, plugins=plugins) + + with pytest.raises(AssertionError): + # In ResNet: 1 <= num_stages <= 4 + ResNet(18, num_stages=5) + + with pytest.raises(AssertionError): + # len(strides) == len(dilations) == num_stages + ResNet(18, strides=(1, ), dilations=(1, 1), num_stages=3) + + with pytest.raises(TypeError): + # pretrained must be a string path + model = ResNet(18, pretrained=0) + model.init_weights() + + with pytest.raises(AssertionError): + # Style must be in ['pytorch', 'caffe'] + ResNet(50, style='tensorflow') + + # Test ResNet18 norm_eval=True + model = ResNet(18, norm_eval=True) + model.init_weights() + model.train() + assert check_norm_state(model.modules(), False) + + # Test ResNet18 with torchvision pretrained weight + model = ResNet( + depth=18, norm_eval=True, pretrained='torchvision://resnet18') + model.init_weights() + model.train() + assert check_norm_state(model.modules(), False) + + # Test ResNet18 with first stage frozen + frozen_stages = 1 + model = ResNet(18, frozen_stages=frozen_stages) + model.init_weights() + model.train() + assert model.norm1.training is False + for layer in [model.conv1, model.norm1]: + for param in layer.parameters(): + assert param.requires_grad is False + for i in range(1, frozen_stages + 1): + layer = getattr(model, f'layer{i}') + for mod in layer.modules(): + if isinstance(mod, _BatchNorm): + assert mod.training is False + for param in layer.parameters(): + assert param.requires_grad is False + + # Test ResNet18V1d with first stage frozen + model = ResNetV1d(depth=18, frozen_stages=frozen_stages) + assert len(model.stem) == 9 + model.init_weights() + model.train() + check_norm_state(model.stem, False) + for param in model.stem.parameters(): + assert param.requires_grad is False + for i in range(1, frozen_stages + 1): + layer = getattr(model, f'layer{i}') + for mod in layer.modules(): + if isinstance(mod, _BatchNorm): + assert mod.training is False + for param in layer.parameters(): + assert param.requires_grad is False + + # Test ResNet18 forward + model = ResNet(18) + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 64, 56, 56]) + assert feat[1].shape == torch.Size([1, 128, 28, 28]) + assert feat[2].shape == torch.Size([1, 256, 14, 14]) + assert feat[3].shape == torch.Size([1, 512, 7, 7]) + + # Test ResNet18 with BatchNorm forward + model = ResNet(18) + for m in model.modules(): + if is_norm(m): + assert isinstance(m, _BatchNorm) + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 64, 56, 56]) + assert feat[1].shape == torch.Size([1, 128, 28, 28]) + assert feat[2].shape == torch.Size([1, 256, 14, 14]) + assert feat[3].shape == torch.Size([1, 512, 7, 7]) + + # Test ResNet18 with layers 1, 2, 3 out forward + model = ResNet(18, out_indices=(0, 1, 2)) + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 112, 112) + feat = model(imgs) + assert len(feat) == 3 + assert feat[0].shape == torch.Size([1, 64, 28, 28]) + assert feat[1].shape == torch.Size([1, 128, 14, 14]) + assert feat[2].shape == torch.Size([1, 256, 7, 7]) + + # Test ResNet18 with checkpoint forward + model = ResNet(18, with_cp=True) + for m in model.modules(): + if is_block(m): + assert m.with_cp + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 64, 56, 56]) + assert feat[1].shape == torch.Size([1, 128, 28, 28]) + assert feat[2].shape == torch.Size([1, 256, 14, 14]) + assert feat[3].shape == torch.Size([1, 512, 7, 7]) + + # Test ResNet18 with checkpoint forward + model = ResNet(18, with_cp=True) + for m in model.modules(): + if is_block(m): + assert m.with_cp + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 64, 56, 56]) + assert feat[1].shape == torch.Size([1, 128, 28, 28]) + assert feat[2].shape == torch.Size([1, 256, 14, 14]) + assert feat[3].shape == torch.Size([1, 512, 7, 7]) + + # Test ResNet18 with GroupNorm forward + model = ResNet( + 18, norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)) + for m in model.modules(): + if is_norm(m): + assert isinstance(m, GroupNorm) + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 64, 56, 56]) + assert feat[1].shape == torch.Size([1, 128, 28, 28]) + assert feat[2].shape == torch.Size([1, 256, 14, 14]) + assert feat[3].shape == torch.Size([1, 512, 7, 7]) + + # Test ResNet50 with 1 GeneralizedAttention after conv2, 1 NonLocal2d + # after conv2, 1 ContextBlock after conv3 in layers 2, 3, 4 + plugins = [ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + stages=(False, True, True, True), + position='after_conv2'), + dict(cfg=dict(type='NonLocal2d'), position='after_conv2'), + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + stages=(False, True, True, False), + position='after_conv3') + ] + model = ResNet(50, plugins=plugins) + for m in model.layer1.modules(): + if is_block(m): + assert not hasattr(m, 'context_block') + assert not hasattr(m, 'gen_attention_block') + assert m.nonlocal_block.in_channels == 64 + for m in model.layer2.modules(): + if is_block(m): + assert m.nonlocal_block.in_channels == 128 + assert m.gen_attention_block.in_channels == 128 + assert m.context_block.in_channels == 512 + + for m in model.layer3.modules(): + if is_block(m): + assert m.nonlocal_block.in_channels == 256 + assert m.gen_attention_block.in_channels == 256 + assert m.context_block.in_channels == 1024 + + for m in model.layer4.modules(): + if is_block(m): + assert m.nonlocal_block.in_channels == 512 + assert m.gen_attention_block.in_channels == 512 + assert not hasattr(m, 'context_block') + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 256, 56, 56]) + assert feat[1].shape == torch.Size([1, 512, 28, 28]) + assert feat[2].shape == torch.Size([1, 1024, 14, 14]) + assert feat[3].shape == torch.Size([1, 2048, 7, 7]) + + # Test ResNet50 with 1 ContextBlock after conv2, 1 ContextBlock after + # conv3 in layers 2, 3, 4 + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=1), + stages=(False, True, True, False), + position='after_conv3'), + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16, postfix=2), + stages=(False, True, True, False), + position='after_conv3') + ] + + model = ResNet(50, plugins=plugins) + for m in model.layer1.modules(): + if is_block(m): + assert not hasattr(m, 'context_block') + assert not hasattr(m, 'context_block1') + assert not hasattr(m, 'context_block2') + for m in model.layer2.modules(): + if is_block(m): + assert not hasattr(m, 'context_block') + assert m.context_block1.in_channels == 512 + assert m.context_block2.in_channels == 512 + + for m in model.layer3.modules(): + if is_block(m): + assert not hasattr(m, 'context_block') + assert m.context_block1.in_channels == 1024 + assert m.context_block2.in_channels == 1024 + + for m in model.layer4.modules(): + if is_block(m): + assert not hasattr(m, 'context_block') + assert not hasattr(m, 'context_block1') + assert not hasattr(m, 'context_block2') + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 256, 56, 56]) + assert feat[1].shape == torch.Size([1, 512, 28, 28]) + assert feat[2].shape == torch.Size([1, 1024, 14, 14]) + assert feat[3].shape == torch.Size([1, 2048, 7, 7]) + + # Test ResNet18 zero initialization of residual + model = ResNet(18, zero_init_residual=True) + model.init_weights() + for m in model.modules(): + if isinstance(m, Bottleneck): + assert all_zeros(m.norm3) + elif isinstance(m, BasicBlock): + assert all_zeros(m.norm2) + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 64, 56, 56]) + assert feat[1].shape == torch.Size([1, 128, 28, 28]) + assert feat[2].shape == torch.Size([1, 256, 14, 14]) + assert feat[3].shape == torch.Size([1, 512, 7, 7]) + + # Test ResNetV1d forward + model = ResNetV1d(depth=18) + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 64, 56, 56]) + assert feat[1].shape == torch.Size([1, 128, 28, 28]) + assert feat[2].shape == torch.Size([1, 256, 14, 14]) + assert feat[3].shape == torch.Size([1, 512, 7, 7]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnext.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnext.py new file mode 100644 index 0000000..2aecaf0 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_resnext.py @@ -0,0 +1,62 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import ResNeXt +from mmseg.models.backbones.resnext import Bottleneck as BottleneckX +from .utils import is_block + + +def test_renext_bottleneck(): + with pytest.raises(AssertionError): + # Style must be in ['pytorch', 'caffe'] + BottleneckX(64, 64, groups=32, base_width=4, style='tensorflow') + + # Test ResNeXt Bottleneck structure + block = BottleneckX( + 64, 64, groups=32, base_width=4, stride=2, style='pytorch') + assert block.conv2.stride == (2, 2) + assert block.conv2.groups == 32 + assert block.conv2.out_channels == 128 + + # Test ResNeXt Bottleneck with DCN + dcn = dict(type='DCN', deform_groups=1, fallback_on_stride=False) + with pytest.raises(AssertionError): + # conv_cfg must be None if dcn is not None + BottleneckX( + 64, + 64, + groups=32, + base_width=4, + dcn=dcn, + conv_cfg=dict(type='Conv')) + BottleneckX(64, 64, dcn=dcn) + + # Test ResNeXt Bottleneck forward + block = BottleneckX(64, 16, groups=32, base_width=4) + x = torch.randn(1, 64, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size([1, 64, 56, 56]) + + +def test_resnext_backbone(): + with pytest.raises(KeyError): + # ResNeXt depth should be in [50, 101, 152] + ResNeXt(depth=18) + + # Test ResNeXt with group 32, base_width 4 + model = ResNeXt(depth=50, groups=32, base_width=4) + print(model) + for m in model.modules(): + if is_block(m): + assert m.conv2.groups == 32 + model.init_weights() + model.train() + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert len(feat) == 4 + assert feat[0].shape == torch.Size([1, 256, 56, 56]) + assert feat[1].shape == torch.Size([1, 512, 28, 28]) + assert feat[2].shape == torch.Size([1, 1024, 14, 14]) + assert feat[3].shape == torch.Size([1, 2048, 7, 7]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_stdc.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_stdc.py new file mode 100644 index 0000000..1e3862b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_stdc.py @@ -0,0 +1,131 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import STDCContextPathNet +from mmseg.models.backbones.stdc import (AttentionRefinementModule, + FeatureFusionModule, STDCModule, + STDCNet) + + +def test_stdc_context_path_net(): + # Test STDCContextPathNet Standard Forward + model = STDCContextPathNet( + backbone_cfg=dict( + type='STDCNet', + stdc_type='STDCNet1', + in_channels=3, + channels=(32, 64, 256, 512, 1024), + bottleneck_type='cat', + num_convs=4, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='ReLU'), + with_final_conv=True), + last_in_channels=(1024, 512), + out_channels=128, + ffm_cfg=dict(in_channels=384, out_channels=256, scale_factor=4)) + model.init_weights() + model.train() + batch_size = 2 + imgs = torch.randn(batch_size, 3, 256, 512) + feat = model(imgs) + + assert len(feat) == 4 + # output for segment Head + assert feat[0].shape == torch.Size([batch_size, 256, 32, 64]) + # for auxiliary head 1 + assert feat[1].shape == torch.Size([batch_size, 128, 16, 32]) + # for auxiliary head 2 + assert feat[2].shape == torch.Size([batch_size, 128, 32, 64]) + # for auxiliary head 3 + assert feat[3].shape == torch.Size([batch_size, 256, 32, 64]) + + # Test input with rare shape + batch_size = 2 + imgs = torch.randn(batch_size, 3, 527, 279) + model = STDCContextPathNet( + backbone_cfg=dict( + type='STDCNet', + stdc_type='STDCNet1', + in_channels=3, + channels=(32, 64, 256, 512, 1024), + bottleneck_type='add', + num_convs=4, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='ReLU'), + with_final_conv=False), + last_in_channels=(1024, 512), + out_channels=128, + ffm_cfg=dict(in_channels=384, out_channels=256, scale_factor=4)) + model.init_weights() + model.train() + feat = model(imgs) + assert len(feat) == 4 + + +def test_stdcnet(): + with pytest.raises(AssertionError): + # STDC backbone constraints. + STDCNet( + stdc_type='STDCNet3', + in_channels=3, + channels=(32, 64, 256, 512, 1024), + bottleneck_type='cat', + num_convs=4, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='ReLU'), + with_final_conv=False) + + with pytest.raises(AssertionError): + # STDC bottleneck type constraints. + STDCNet( + stdc_type='STDCNet1', + in_channels=3, + channels=(32, 64, 256, 512, 1024), + bottleneck_type='dog', + num_convs=4, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='ReLU'), + with_final_conv=False) + + with pytest.raises(AssertionError): + # STDC channels length constraints. + STDCNet( + stdc_type='STDCNet1', + in_channels=3, + channels=(16, 32, 64, 256, 512, 1024), + bottleneck_type='cat', + num_convs=4, + norm_cfg=dict(type='BN', requires_grad=True), + act_cfg=dict(type='ReLU'), + with_final_conv=False) + + +def test_feature_fusion_module(): + x_ffm = FeatureFusionModule(in_channels=64, out_channels=32) + assert x_ffm.conv0.in_channels == 64 + assert x_ffm.attention[1].in_channels == 32 + assert x_ffm.attention[2].in_channels == 8 + assert x_ffm.attention[2].out_channels == 32 + x1 = torch.randn(2, 32, 32, 64) + x2 = torch.randn(2, 32, 32, 64) + x_out = x_ffm(x1, x2) + assert x_out.shape == torch.Size([2, 32, 32, 64]) + + +def test_attention_refinement_module(): + x_arm = AttentionRefinementModule(128, 32) + assert x_arm.conv_layer.in_channels == 128 + assert x_arm.atten_conv_layer[1].conv.out_channels == 32 + x = torch.randn(2, 128, 32, 64) + x_out = x_arm(x) + assert x_out.shape == torch.Size([2, 32, 32, 64]) + + +def test_stdc_module(): + x_stdc = STDCModule(in_channels=32, out_channels=32, stride=4) + assert x_stdc.layers[0].conv.in_channels == 32 + assert x_stdc.layers[3].conv.out_channels == 4 + x = torch.randn(2, 32, 32, 64) + x_out = x_stdc(x) + assert x_out.shape == torch.Size([2, 32, 32, 64]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_swin.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_swin.py new file mode 100644 index 0000000..8d14d47 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_swin.py @@ -0,0 +1,100 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones.swin import SwinBlock, SwinTransformer + + +def test_swin_block(): + # test SwinBlock structure and forward + block = SwinBlock(embed_dims=32, num_heads=4, feedforward_channels=128) + assert block.ffn.embed_dims == 32 + assert block.attn.w_msa.num_heads == 4 + assert block.ffn.feedforward_channels == 128 + x = torch.randn(1, 56 * 56, 32) + x_out = block(x, (56, 56)) + assert x_out.shape == torch.Size([1, 56 * 56, 32]) + + # Test BasicBlock with checkpoint forward + block = SwinBlock( + embed_dims=64, num_heads=4, feedforward_channels=256, with_cp=True) + assert block.with_cp + x = torch.randn(1, 56 * 56, 64) + x_out = block(x, (56, 56)) + assert x_out.shape == torch.Size([1, 56 * 56, 64]) + + +def test_swin_transformer(): + """Test Swin Transformer backbone.""" + + with pytest.raises(TypeError): + # Pretrained arg must be str or None. + SwinTransformer(pretrained=123) + + with pytest.raises(AssertionError): + # Because swin uses non-overlapping patch embed, so the stride of patch + # embed must be equal to patch size. + SwinTransformer(strides=(2, 2, 2, 2), patch_size=4) + + # test pretrained image size + with pytest.raises(AssertionError): + SwinTransformer(pretrain_img_size=(112, 112, 112)) + + # Test absolute position embedding + temp = torch.randn((1, 3, 112, 112)) + model = SwinTransformer(pretrain_img_size=112, use_abs_pos_embed=True) + model.init_weights() + model(temp) + + # Test patch norm + model = SwinTransformer(patch_norm=False) + model(temp) + + # Test normal inference + temp = torch.randn((1, 3, 256, 256)) + model = SwinTransformer() + outs = model(temp) + assert outs[0].shape == (1, 96, 64, 64) + assert outs[1].shape == (1, 192, 32, 32) + assert outs[2].shape == (1, 384, 16, 16) + assert outs[3].shape == (1, 768, 8, 8) + + # Test abnormal inference size + temp = torch.randn((1, 3, 255, 255)) + model = SwinTransformer() + outs = model(temp) + assert outs[0].shape == (1, 96, 64, 64) + assert outs[1].shape == (1, 192, 32, 32) + assert outs[2].shape == (1, 384, 16, 16) + assert outs[3].shape == (1, 768, 8, 8) + + # Test abnormal inference size + temp = torch.randn((1, 3, 112, 137)) + model = SwinTransformer() + outs = model(temp) + assert outs[0].shape == (1, 96, 28, 35) + assert outs[1].shape == (1, 192, 14, 18) + assert outs[2].shape == (1, 384, 7, 9) + assert outs[3].shape == (1, 768, 4, 5) + + # Test frozen + model = SwinTransformer(frozen_stages=4) + model.train() + for p in model.parameters(): + assert not p.requires_grad + + # Test absolute position embedding frozen + model = SwinTransformer(frozen_stages=4, use_abs_pos_embed=True) + model.train() + for p in model.parameters(): + assert not p.requires_grad + + # Test Swin with checkpoint forward + temp = torch.randn((1, 3, 56, 56)) + model = SwinTransformer(with_cp=True) + for m in model.modules(): + if isinstance(m, SwinBlock): + assert m.with_cp + model.init_weights() + model.train() + model(temp) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_timm_backbone.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_timm_backbone.py new file mode 100644 index 0000000..d9a50cf --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_timm_backbone.py @@ -0,0 +1,133 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones import TIMMBackbone +from .utils import check_norm_state + + +def test_timm_backbone(): + with pytest.raises(TypeError): + # pretrained must be a string path + model = TIMMBackbone() + model.init_weights(pretrained=0) + + # Test different norm_layer, can be: 'SyncBN', 'BN2d', 'GN', 'LN', 'IN' + # Test resnet18 from timm, norm_layer='BN2d' + model = TIMMBackbone( + model_name='resnet18', + features_only=True, + pretrained=False, + output_stride=32, + norm_layer='BN2d') + + # Test resnet18 from timm, norm_layer='SyncBN' + model = TIMMBackbone( + model_name='resnet18', + features_only=True, + pretrained=False, + output_stride=32, + norm_layer='SyncBN2d') + + # Test resnet18 from timm, features_only=True, output_stride=32 + model = TIMMBackbone( + model_name='resnet18', + features_only=True, + pretrained=False, + output_stride=32) + model.init_weights() + model.train() + assert check_norm_state(model.modules(), True) + + imgs = torch.randn(1, 3, 224, 224) + feats = model(imgs) + feats = [feat.shape for feat in feats] + assert len(feats) == 5 + assert feats[0] == torch.Size((1, 64, 112, 112)) + assert feats[1] == torch.Size((1, 64, 56, 56)) + assert feats[2] == torch.Size((1, 128, 28, 28)) + assert feats[3] == torch.Size((1, 256, 14, 14)) + assert feats[4] == torch.Size((1, 512, 7, 7)) + + # Test resnet18 from timm, features_only=True, output_stride=16 + model = TIMMBackbone( + model_name='resnet18', + features_only=True, + pretrained=False, + output_stride=16) + imgs = torch.randn(1, 3, 224, 224) + feats = model(imgs) + feats = [feat.shape for feat in feats] + assert len(feats) == 5 + assert feats[0] == torch.Size((1, 64, 112, 112)) + assert feats[1] == torch.Size((1, 64, 56, 56)) + assert feats[2] == torch.Size((1, 128, 28, 28)) + assert feats[3] == torch.Size((1, 256, 14, 14)) + assert feats[4] == torch.Size((1, 512, 14, 14)) + + # Test resnet18 from timm, features_only=True, output_stride=8 + model = TIMMBackbone( + model_name='resnet18', + features_only=True, + pretrained=False, + output_stride=8) + imgs = torch.randn(1, 3, 224, 224) + feats = model(imgs) + feats = [feat.shape for feat in feats] + assert len(feats) == 5 + assert feats[0] == torch.Size((1, 64, 112, 112)) + assert feats[1] == torch.Size((1, 64, 56, 56)) + assert feats[2] == torch.Size((1, 128, 28, 28)) + assert feats[3] == torch.Size((1, 256, 28, 28)) + assert feats[4] == torch.Size((1, 512, 28, 28)) + + # Test efficientnet_b1 with pretrained weights + model = TIMMBackbone(model_name='efficientnet_b1', pretrained=True) + + # Test resnetv2_50x1_bitm from timm, features_only=True, output_stride=8 + model = TIMMBackbone( + model_name='resnetv2_50x1_bitm', + features_only=True, + pretrained=False, + output_stride=8) + imgs = torch.randn(1, 3, 8, 8) + feats = model(imgs) + feats = [feat.shape for feat in feats] + assert len(feats) == 5 + assert feats[0] == torch.Size((1, 64, 4, 4)) + assert feats[1] == torch.Size((1, 256, 2, 2)) + assert feats[2] == torch.Size((1, 512, 1, 1)) + assert feats[3] == torch.Size((1, 1024, 1, 1)) + assert feats[4] == torch.Size((1, 2048, 1, 1)) + + # Test resnetv2_50x3_bitm from timm, features_only=True, output_stride=8 + model = TIMMBackbone( + model_name='resnetv2_50x3_bitm', + features_only=True, + pretrained=False, + output_stride=8) + imgs = torch.randn(1, 3, 8, 8) + feats = model(imgs) + feats = [feat.shape for feat in feats] + assert len(feats) == 5 + assert feats[0] == torch.Size((1, 192, 4, 4)) + assert feats[1] == torch.Size((1, 768, 2, 2)) + assert feats[2] == torch.Size((1, 1536, 1, 1)) + assert feats[3] == torch.Size((1, 3072, 1, 1)) + assert feats[4] == torch.Size((1, 6144, 1, 1)) + + # Test resnetv2_101x1_bitm from timm, features_only=True, output_stride=8 + model = TIMMBackbone( + model_name='resnetv2_101x1_bitm', + features_only=True, + pretrained=False, + output_stride=8) + imgs = torch.randn(1, 3, 8, 8) + feats = model(imgs) + feats = [feat.shape for feat in feats] + assert len(feats) == 5 + assert feats[0] == torch.Size((1, 64, 4, 4)) + assert feats[1] == torch.Size((1, 256, 2, 2)) + assert feats[2] == torch.Size((1, 512, 1, 1)) + assert feats[3] == torch.Size((1, 1024, 1, 1)) + assert feats[4] == torch.Size((1, 2048, 1, 1)) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_twins.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_twins.py new file mode 100644 index 0000000..aa3eaf9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_twins.py @@ -0,0 +1,171 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones.twins import (PCPVT, SVT, + ConditionalPositionEncoding, + LocallyGroupedSelfAttention) + + +def test_pcpvt(): + # Test normal input + H, W = (224, 224) + temp = torch.randn((1, 3, H, W)) + model = PCPVT( + embed_dims=[32, 64, 160, 256], + num_heads=[1, 2, 5, 8], + mlp_ratios=[8, 8, 4, 4], + qkv_bias=True, + depths=[3, 4, 6, 3], + sr_ratios=[8, 4, 2, 1], + norm_after_stage=False) + model.init_weights() + outs = model(temp) + assert outs[0].shape == (1, 32, H // 4, W // 4) + assert outs[1].shape == (1, 64, H // 8, W // 8) + assert outs[2].shape == (1, 160, H // 16, W // 16) + assert outs[3].shape == (1, 256, H // 32, W // 32) + + +def test_svt(): + # Test normal input + H, W = (224, 224) + temp = torch.randn((1, 3, H, W)) + model = SVT( + embed_dims=[32, 64, 128], + num_heads=[1, 2, 4], + mlp_ratios=[4, 4, 4], + qkv_bias=False, + depths=[4, 4, 4], + windiow_sizes=[7, 7, 7], + norm_after_stage=True) + + model.init_weights() + outs = model(temp) + assert outs[0].shape == (1, 32, H // 4, W // 4) + assert outs[1].shape == (1, 64, H // 8, W // 8) + assert outs[2].shape == (1, 128, H // 16, W // 16) + + +def test_svt_init(): + path = 'PATH_THAT_DO_NOT_EXIST' + # Test all combinations of pretrained and init_cfg + # pretrained=None, init_cfg=None + model = SVT(pretrained=None, init_cfg=None) + assert model.init_cfg is None + model.init_weights() + + # pretrained=None + # init_cfg loads pretrain from an non-existent file + model = SVT( + pretrained=None, init_cfg=dict(type='Pretrained', checkpoint=path)) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained=None + # init_cfg=123, whose type is unsupported + model = SVT(pretrained=None, init_cfg=123) + with pytest.raises(TypeError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg=None + model = SVT(pretrained=path, init_cfg=None) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = SVT( + pretrained=path, init_cfg=dict(type='Pretrained', checkpoint=path)) + with pytest.raises(AssertionError): + model = SVT(pretrained=path, init_cfg=123) + + # pretrain=123, whose type is unsupported + # init_cfg=None + with pytest.raises(TypeError): + model = SVT(pretrained=123, init_cfg=None) + + # pretrain=123, whose type is unsupported + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = SVT( + pretrained=123, init_cfg=dict(type='Pretrained', checkpoint=path)) + + # pretrain=123, whose type is unsupported + # init_cfg=123, whose type is unsupported + with pytest.raises(AssertionError): + model = SVT(pretrained=123, init_cfg=123) + + +def test_pcpvt_init(): + path = 'PATH_THAT_DO_NOT_EXIST' + # Test all combinations of pretrained and init_cfg + # pretrained=None, init_cfg=None + model = PCPVT(pretrained=None, init_cfg=None) + assert model.init_cfg is None + model.init_weights() + + # pretrained=None + # init_cfg loads pretrain from an non-existent file + model = PCPVT( + pretrained=None, init_cfg=dict(type='Pretrained', checkpoint=path)) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained=None + # init_cfg=123, whose type is unsupported + model = PCPVT(pretrained=None, init_cfg=123) + with pytest.raises(TypeError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg=None + model = PCPVT(pretrained=path, init_cfg=None) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = PCPVT( + pretrained=path, init_cfg=dict(type='Pretrained', checkpoint=path)) + with pytest.raises(AssertionError): + model = PCPVT(pretrained=path, init_cfg=123) + + # pretrain=123, whose type is unsupported + # init_cfg=None + with pytest.raises(TypeError): + model = PCPVT(pretrained=123, init_cfg=None) + + # pretrain=123, whose type is unsupported + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = PCPVT( + pretrained=123, init_cfg=dict(type='Pretrained', checkpoint=path)) + + # pretrain=123, whose type is unsupported + # init_cfg=123, whose type is unsupported + with pytest.raises(AssertionError): + model = PCPVT(pretrained=123, init_cfg=123) + + +def test_locallygrouped_self_attention_module(): + LSA = LocallyGroupedSelfAttention(embed_dims=32, window_size=3) + outs = LSA(torch.randn(1, 3136, 32), (56, 56)) + assert outs.shape == torch.Size([1, 3136, 32]) + + +def test_conditional_position_encoding_module(): + CPE = ConditionalPositionEncoding(in_channels=32, embed_dims=32, stride=2) + outs = CPE(torch.randn(1, 3136, 32), (56, 56)) + assert outs.shape == torch.Size([1, 784, 32]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_unet.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_unet.py new file mode 100644 index 0000000..4d3faf6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_unet.py @@ -0,0 +1,825 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch +from mmcv.cnn import ConvModule +from mmengine.registry import init_default_scope + +from mmseg.models.backbones.unet import (BasicConvBlock, DeconvModule, + InterpConv, UNet, UpConvBlock) +from mmseg.models.utils import Upsample +from .utils import check_norm_state + +init_default_scope('mmseg') + + +def test_unet_basic_conv_block(): + with pytest.raises(AssertionError): + # Not implemented yet. + dcn = dict(type='DCN', deform_groups=1, fallback_on_stride=False) + BasicConvBlock(64, 64, dcn=dcn) + + with pytest.raises(AssertionError): + # Not implemented yet. + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv3') + ] + BasicConvBlock(64, 64, plugins=plugins) + + with pytest.raises(AssertionError): + # Not implemented yet + plugins = [ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + position='after_conv2') + ] + BasicConvBlock(64, 64, plugins=plugins) + + # test BasicConvBlock with checkpoint forward + block = BasicConvBlock(16, 16, with_cp=True) + assert block.with_cp + x = torch.randn(1, 16, 64, 64, requires_grad=True) + x_out = block(x) + assert x_out.shape == torch.Size([1, 16, 64, 64]) + + block = BasicConvBlock(16, 16, with_cp=False) + assert not block.with_cp + x = torch.randn(1, 16, 64, 64) + x_out = block(x) + assert x_out.shape == torch.Size([1, 16, 64, 64]) + + # test BasicConvBlock with stride convolution to downsample + block = BasicConvBlock(16, 16, stride=2) + x = torch.randn(1, 16, 64, 64) + x_out = block(x) + assert x_out.shape == torch.Size([1, 16, 32, 32]) + + # test BasicConvBlock structure and forward + block = BasicConvBlock(16, 64, num_convs=3, dilation=3) + assert block.convs[0].conv.in_channels == 16 + assert block.convs[0].conv.out_channels == 64 + assert block.convs[0].conv.kernel_size == (3, 3) + assert block.convs[0].conv.dilation == (1, 1) + assert block.convs[0].conv.padding == (1, 1) + + assert block.convs[1].conv.in_channels == 64 + assert block.convs[1].conv.out_channels == 64 + assert block.convs[1].conv.kernel_size == (3, 3) + assert block.convs[1].conv.dilation == (3, 3) + assert block.convs[1].conv.padding == (3, 3) + + assert block.convs[2].conv.in_channels == 64 + assert block.convs[2].conv.out_channels == 64 + assert block.convs[2].conv.kernel_size == (3, 3) + assert block.convs[2].conv.dilation == (3, 3) + assert block.convs[2].conv.padding == (3, 3) + + +def test_deconv_module(): + with pytest.raises(AssertionError): + # kernel_size should be greater than or equal to scale_factor and + # (kernel_size - scale_factor) should be even numbers + DeconvModule(64, 32, kernel_size=1, scale_factor=2) + + with pytest.raises(AssertionError): + # kernel_size should be greater than or equal to scale_factor and + # (kernel_size - scale_factor) should be even numbers + DeconvModule(64, 32, kernel_size=3, scale_factor=2) + + with pytest.raises(AssertionError): + # kernel_size should be greater than or equal to scale_factor and + # (kernel_size - scale_factor) should be even numbers + DeconvModule(64, 32, kernel_size=5, scale_factor=4) + + # test DeconvModule with checkpoint forward and upsample 2X. + block = DeconvModule(64, 32, with_cp=True) + assert block.with_cp + x = torch.randn(1, 64, 128, 128, requires_grad=True) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + block = DeconvModule(64, 32, with_cp=False) + assert not block.with_cp + x = torch.randn(1, 64, 128, 128) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test DeconvModule with different kernel size for upsample 2X. + x = torch.randn(1, 64, 64, 64) + block = DeconvModule(64, 32, kernel_size=2, scale_factor=2) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 128, 128]) + + block = DeconvModule(64, 32, kernel_size=6, scale_factor=2) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 128, 128]) + + # test DeconvModule with different kernel size for upsample 4X. + x = torch.randn(1, 64, 64, 64) + block = DeconvModule(64, 32, kernel_size=4, scale_factor=4) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + block = DeconvModule(64, 32, kernel_size=6, scale_factor=4) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + +def test_interp_conv(): + # test InterpConv with checkpoint forward and upsample 2X. + block = InterpConv(64, 32, with_cp=True) + assert block.with_cp + x = torch.randn(1, 64, 128, 128, requires_grad=True) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + block = InterpConv(64, 32, with_cp=False) + assert not block.with_cp + x = torch.randn(1, 64, 128, 128) + x_out = block(x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test InterpConv with conv_first=False for upsample 2X. + block = InterpConv(64, 32, conv_first=False) + x = torch.randn(1, 64, 128, 128) + x_out = block(x) + assert isinstance(block.interp_upsample[0], Upsample) + assert isinstance(block.interp_upsample[1], ConvModule) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test InterpConv with conv_first=True for upsample 2X. + block = InterpConv(64, 32, conv_first=True) + x = torch.randn(1, 64, 128, 128) + x_out = block(x) + assert isinstance(block.interp_upsample[0], ConvModule) + assert isinstance(block.interp_upsample[1], Upsample) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test InterpConv with bilinear upsample for upsample 2X. + block = InterpConv( + 64, + 32, + conv_first=False, + upsample_cfg=dict( + scale_factor=2, mode='bilinear', align_corners=False)) + x = torch.randn(1, 64, 128, 128) + x_out = block(x) + assert isinstance(block.interp_upsample[0], Upsample) + assert isinstance(block.interp_upsample[1], ConvModule) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + assert block.interp_upsample[0].mode == 'bilinear' + + # test InterpConv with nearest upsample for upsample 2X. + block = InterpConv( + 64, + 32, + conv_first=False, + upsample_cfg=dict(scale_factor=2, mode='nearest')) + x = torch.randn(1, 64, 128, 128) + x_out = block(x) + assert isinstance(block.interp_upsample[0], Upsample) + assert isinstance(block.interp_upsample[1], ConvModule) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + assert block.interp_upsample[0].mode == 'nearest' + + +def test_up_conv_block(): + with pytest.raises(AssertionError): + # Not implemented yet. + dcn = dict(type='DCN', deform_groups=1, fallback_on_stride=False) + UpConvBlock(BasicConvBlock, 64, 32, 32, dcn=dcn) + + with pytest.raises(AssertionError): + # Not implemented yet. + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv3') + ] + UpConvBlock(BasicConvBlock, 64, 32, 32, plugins=plugins) + + with pytest.raises(AssertionError): + # Not implemented yet + plugins = [ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + position='after_conv2') + ] + UpConvBlock(BasicConvBlock, 64, 32, 32, plugins=plugins) + + # test UpConvBlock with checkpoint forward and upsample 2X. + block = UpConvBlock(BasicConvBlock, 64, 32, 32, with_cp=True) + skip_x = torch.randn(1, 32, 256, 256, requires_grad=True) + x = torch.randn(1, 64, 128, 128, requires_grad=True) + x_out = block(skip_x, x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test UpConvBlock with upsample=True for upsample 2X. The spatial size of + # skip_x is 2X larger than x. + block = UpConvBlock( + BasicConvBlock, 64, 32, 32, upsample_cfg=dict(type='InterpConv')) + skip_x = torch.randn(1, 32, 256, 256) + x = torch.randn(1, 64, 128, 128) + x_out = block(skip_x, x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test UpConvBlock with upsample=False for upsample 2X. The spatial size of + # skip_x is the same as that of x. + block = UpConvBlock(BasicConvBlock, 64, 32, 32, upsample_cfg=None) + skip_x = torch.randn(1, 32, 256, 256) + x = torch.randn(1, 64, 256, 256) + x_out = block(skip_x, x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test UpConvBlock with different upsample method for upsample 2X. + # The upsample method is interpolation upsample (bilinear or nearest). + block = UpConvBlock( + BasicConvBlock, + 64, + 32, + 32, + upsample_cfg=dict( + type='InterpConv', + upsample_cfg=dict( + scale_factor=2, mode='bilinear', align_corners=False))) + skip_x = torch.randn(1, 32, 256, 256) + x = torch.randn(1, 64, 128, 128) + x_out = block(skip_x, x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test UpConvBlock with different upsample method for upsample 2X. + # The upsample method is deconvolution upsample. + block = UpConvBlock( + BasicConvBlock, + 64, + 32, + 32, + upsample_cfg=dict(type='DeconvModule', kernel_size=4, scale_factor=2)) + skip_x = torch.randn(1, 32, 256, 256) + x = torch.randn(1, 64, 128, 128) + x_out = block(skip_x, x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + # test BasicConvBlock structure and forward + block = UpConvBlock( + conv_block=BasicConvBlock, + in_channels=64, + skip_channels=32, + out_channels=32, + num_convs=3, + dilation=3, + upsample_cfg=dict( + type='InterpConv', + upsample_cfg=dict( + scale_factor=2, mode='bilinear', align_corners=False))) + skip_x = torch.randn(1, 32, 256, 256) + x = torch.randn(1, 64, 128, 128) + x_out = block(skip_x, x) + assert x_out.shape == torch.Size([1, 32, 256, 256]) + + assert block.conv_block.convs[0].conv.in_channels == 64 + assert block.conv_block.convs[0].conv.out_channels == 32 + assert block.conv_block.convs[0].conv.kernel_size == (3, 3) + assert block.conv_block.convs[0].conv.dilation == (1, 1) + assert block.conv_block.convs[0].conv.padding == (1, 1) + + assert block.conv_block.convs[1].conv.in_channels == 32 + assert block.conv_block.convs[1].conv.out_channels == 32 + assert block.conv_block.convs[1].conv.kernel_size == (3, 3) + assert block.conv_block.convs[1].conv.dilation == (3, 3) + assert block.conv_block.convs[1].conv.padding == (3, 3) + + assert block.conv_block.convs[2].conv.in_channels == 32 + assert block.conv_block.convs[2].conv.out_channels == 32 + assert block.conv_block.convs[2].conv.kernel_size == (3, 3) + assert block.conv_block.convs[2].conv.dilation == (3, 3) + assert block.conv_block.convs[2].conv.padding == (3, 3) + + assert block.upsample.interp_upsample[1].conv.in_channels == 64 + assert block.upsample.interp_upsample[1].conv.out_channels == 32 + assert block.upsample.interp_upsample[1].conv.kernel_size == (1, 1) + assert block.upsample.interp_upsample[1].conv.dilation == (1, 1) + assert block.upsample.interp_upsample[1].conv.padding == (0, 0) + + +def test_unet(): + with pytest.raises(AssertionError): + # Not implemented yet. + dcn = dict(type='DCN', deform_groups=1, fallback_on_stride=False) + UNet(3, 64, 5, dcn=dcn) + + with pytest.raises(AssertionError): + # Not implemented yet. + plugins = [ + dict( + cfg=dict(type='ContextBlock', ratio=1. / 16), + position='after_conv3') + ] + UNet(3, 64, 5, plugins=plugins) + + with pytest.raises(AssertionError): + # Not implemented yet + plugins = [ + dict( + cfg=dict( + type='GeneralizedAttention', + spatial_range=-1, + num_heads=8, + attention_type='0010', + kv_stride=2), + position='after_conv2') + ] + UNet(3, 64, 5, plugins=plugins) + + with pytest.raises(AssertionError): + # Check whether the input image size can be divisible by the whole + # downsample rate of the encoder. The whole downsample rate of this + # case is 8. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=4, + strides=(1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2), + dec_num_convs=(2, 2, 2), + downsamples=(True, True, True), + enc_dilations=(1, 1, 1, 1), + dec_dilations=(1, 1, 1)) + x = torch.randn(2, 3, 65, 65) + unet(x) + + with pytest.raises(AssertionError): + # Check whether the input image size can be divisible by the whole + # downsample rate of the encoder. The whole downsample rate of this + # case is 16. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 65, 65) + unet(x) + + with pytest.raises(AssertionError): + # Check whether the input image size can be divisible by the whole + # downsample rate of the encoder. The whole downsample rate of this + # case is 8. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 65, 65) + unet(x) + + with pytest.raises(AssertionError): + # Check whether the input image size can be divisible by the whole + # downsample rate of the encoder. The whole downsample rate of this + # case is 8. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 2, 2, 2, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 65, 65) + unet(x) + + with pytest.raises(AssertionError): + # Check whether the input image size can be divisible by the whole + # downsample rate of the encoder. The whole downsample rate of this + # case is 32. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=6, + strides=(1, 1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2, 2), + downsamples=(True, True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1, 1)) + x = torch.randn(2, 3, 65, 65) + unet(x) + + with pytest.raises(AssertionError): + # Check if num_stages matches strides, len(strides)=num_stages + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 64, 64) + unet(x) + + with pytest.raises(AssertionError): + # Check if num_stages matches strides, len(enc_num_convs)=num_stages + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 64, 64) + unet(x) + + with pytest.raises(AssertionError): + # Check if num_stages matches strides, len(dec_num_convs)=num_stages-1 + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 64, 64) + unet(x) + + with pytest.raises(AssertionError): + # Check if num_stages matches strides, len(downsamples)=num_stages-1 + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 64, 64) + unet(x) + + with pytest.raises(AssertionError): + # Check if num_stages matches strides, len(enc_dilations)=num_stages + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 64, 64) + unet(x) + + with pytest.raises(AssertionError): + # Check if num_stages matches strides, len(dec_dilations)=num_stages-1 + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1, 1)) + x = torch.randn(2, 3, 64, 64) + unet(x) + + # test UNet norm_eval=True + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1), + norm_eval=True) + unet.train() + assert check_norm_state(unet.modules(), False) + + # test UNet norm_eval=False + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1), + norm_eval=False) + unet.train() + assert check_norm_state(unet.modules(), True) + + # test UNet forward and outputs. The whole downsample rate is 16. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 8, 8]) + assert x_outs[1].shape == torch.Size([2, 32, 16, 16]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 8. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 16, 16]) + assert x_outs[1].shape == torch.Size([2, 32, 16, 16]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 8. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 2, 2, 2, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 16, 16]) + assert x_outs[1].shape == torch.Size([2, 32, 16, 16]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 4. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, False, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 32, 32]) + assert x_outs[1].shape == torch.Size([2, 32, 32, 32]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 4. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 2, 2, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, False, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 32, 32]) + assert x_outs[1].shape == torch.Size([2, 32, 32, 32]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 8. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 16, 16]) + assert x_outs[1].shape == torch.Size([2, 32, 16, 16]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 4. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, False, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 32, 32]) + assert x_outs[1].shape == torch.Size([2, 32, 32, 32]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 2. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, False, False, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 64, 64]) + assert x_outs[1].shape == torch.Size([2, 32, 64, 64]) + assert x_outs[2].shape == torch.Size([2, 16, 64, 64]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 1. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 1, 1, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(False, False, False, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 128, 128]) + assert x_outs[1].shape == torch.Size([2, 32, 128, 128]) + assert x_outs[2].shape == torch.Size([2, 16, 128, 128]) + assert x_outs[3].shape == torch.Size([2, 8, 128, 128]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 16. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 2, 2, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, True), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 8, 8]) + assert x_outs[1].shape == torch.Size([2, 32, 16, 16]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 8. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 2, 2, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 16, 16]) + assert x_outs[1].shape == torch.Size([2, 32, 16, 16]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 8. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 2, 2, 2, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, True, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 16, 16]) + assert x_outs[1].shape == torch.Size([2, 32, 16, 16]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet forward and outputs. The whole downsample rate is 4. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 2, 2, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, False, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1)) + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 32, 32]) + assert x_outs[1].shape == torch.Size([2, 32, 32, 32]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) + + # test UNet init_weights method. + unet = UNet( + in_channels=3, + base_channels=4, + num_stages=5, + strides=(1, 2, 2, 1, 1), + enc_num_convs=(2, 2, 2, 2, 2), + dec_num_convs=(2, 2, 2, 2), + downsamples=(True, True, False, False), + enc_dilations=(1, 1, 1, 1, 1), + dec_dilations=(1, 1, 1, 1), + pretrained=None) + unet.init_weights() + x = torch.randn(2, 3, 128, 128) + x_outs = unet(x) + assert x_outs[0].shape == torch.Size([2, 64, 32, 32]) + assert x_outs[1].shape == torch.Size([2, 32, 32, 32]) + assert x_outs[2].shape == torch.Size([2, 16, 32, 32]) + assert x_outs[3].shape == torch.Size([2, 8, 64, 64]) + assert x_outs[4].shape == torch.Size([2, 4, 128, 128]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_vit.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_vit.py new file mode 100644 index 0000000..0d1ba70 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_vit.py @@ -0,0 +1,185 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.backbones.vit import (TransformerEncoderLayer, + VisionTransformer) +from .utils import check_norm_state + + +def test_vit_backbone(): + with pytest.raises(TypeError): + # pretrained must be a string path + model = VisionTransformer() + model.init_weights(pretrained=0) + + with pytest.raises(TypeError): + # img_size must be int or tuple + model = VisionTransformer(img_size=512.0) + + with pytest.raises(TypeError): + # out_indices must be int ,list or tuple + model = VisionTransformer(out_indices=1.) + + with pytest.raises(TypeError): + # test upsample_pos_embed function + x = torch.randn(1, 196) + VisionTransformer.resize_pos_embed(x, 512, 512, 224, 224, 'bilinear') + + with pytest.raises(AssertionError): + # The length of img_size tuple must be lower than 3. + VisionTransformer(img_size=(224, 224, 224)) + + with pytest.raises(TypeError): + # Pretrained must be None or Str. + VisionTransformer(pretrained=123) + + with pytest.raises(AssertionError): + # with_cls_token must be True when output_cls_token == True + VisionTransformer(with_cls_token=False, output_cls_token=True) + + # Test img_size isinstance tuple + imgs = torch.randn(1, 3, 224, 224) + model = VisionTransformer(img_size=(224, )) + model.init_weights() + model(imgs) + + # Test img_size isinstance tuple + imgs = torch.randn(1, 3, 224, 224) + model = VisionTransformer(img_size=(224, 224)) + model(imgs) + + # Test norm_eval = True + model = VisionTransformer(norm_eval=True) + model.train() + + # Test ViT backbone with input size of 224 and patch size of 16 + model = VisionTransformer() + model.init_weights() + model.train() + + assert check_norm_state(model.modules(), True) + + # Test normal size input image + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test large size input image + imgs = torch.randn(1, 3, 256, 256) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 16, 16) + + # Test small size input image + imgs = torch.randn(1, 3, 32, 32) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 2, 2) + + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test unbalanced size input image + imgs = torch.randn(1, 3, 112, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 7, 14) + + # Test irregular input image + imgs = torch.randn(1, 3, 234, 345) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 15, 22) + + # Test with_cp=True + model = VisionTransformer(with_cp=True) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test with_cls_token=False + model = VisionTransformer(with_cls_token=False) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test final norm + model = VisionTransformer(final_norm=True) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test patch norm + model = VisionTransformer(patch_norm=True) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[-1].shape == (1, 768, 14, 14) + + # Test output_cls_token + model = VisionTransformer(with_cls_token=True, output_cls_token=True) + imgs = torch.randn(1, 3, 224, 224) + feat = model(imgs) + assert feat[0][0].shape == (1, 768, 14, 14) + assert feat[0][1].shape == (1, 768) + + # Test TransformerEncoderLayer with checkpoint forward + block = TransformerEncoderLayer( + embed_dims=64, num_heads=4, feedforward_channels=256, with_cp=True) + assert block.with_cp + x = torch.randn(1, 56 * 56, 64) + x_out = block(x) + assert x_out.shape == torch.Size([1, 56 * 56, 64]) + + +def test_vit_init(): + path = 'PATH_THAT_DO_NOT_EXIST' + # Test all combinations of pretrained and init_cfg + # pretrained=None, init_cfg=None + model = VisionTransformer(pretrained=None, init_cfg=None) + assert model.init_cfg is None + model.init_weights() + + # pretrained=None + # init_cfg loads pretrain from an non-existent file + model = VisionTransformer( + pretrained=None, init_cfg=dict(type='Pretrained', checkpoint=path)) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained=None + # init_cfg=123, whose type is unsupported + model = VisionTransformer(pretrained=None, init_cfg=123) + with pytest.raises(TypeError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg=None + model = VisionTransformer(pretrained=path, init_cfg=None) + assert model.init_cfg == dict(type='Pretrained', checkpoint=path) + # Test loading a checkpoint from an non-existent file + with pytest.raises(OSError): + model.init_weights() + + # pretrained loads pretrain from an non-existent file + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = VisionTransformer( + pretrained=path, init_cfg=dict(type='Pretrained', checkpoint=path)) + with pytest.raises(AssertionError): + model = VisionTransformer(pretrained=path, init_cfg=123) + + # pretrain=123, whose type is unsupported + # init_cfg=None + with pytest.raises(TypeError): + model = VisionTransformer(pretrained=123, init_cfg=None) + + # pretrain=123, whose type is unsupported + # init_cfg loads pretrain from an non-existent file + with pytest.raises(AssertionError): + model = VisionTransformer( + pretrained=123, init_cfg=dict(type='Pretrained', checkpoint=path)) + + # pretrain=123, whose type is unsupported + # init_cfg=123, whose type is unsupported + with pytest.raises(AssertionError): + model = VisionTransformer(pretrained=123, init_cfg=123) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_vpd.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_vpd.py new file mode 100644 index 0000000..a268159 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/test_vpd.py @@ -0,0 +1,51 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from os.path import dirname, join +from unittest import TestCase + +import torch +from mmengine import Config + +import mmseg +from mmseg.models.backbones import VPD + + +class TestVPD(TestCase): + + def setUp(self) -> None: + + repo_dpath = dirname(dirname(mmseg.__file__)) + config_dpath = join(repo_dpath, 'configs/_base_/models/vpd_sd.py') + vpd_cfg = Config.fromfile(config_dpath).stable_diffusion_cfg + vpd_cfg.pop('checkpoint') + + self.vpd_model = VPD( + diffusion_cfg=vpd_cfg, + class_embed_path='https://download.openmmlab.com/mmsegmentation/' + 'v0.5/vpd/nyu_class_embeddings.pth', + class_embed_select=True, + pad_shape=64, + unet_cfg=dict(use_attn=False), + ) + + def test_forward(self): + # test forward without class_id + x = torch.randn(1, 3, 60, 60) + with torch.no_grad(): + out = self.vpd_model(x) + + self.assertEqual(len(out), 4) + self.assertListEqual(list(out[0].shape), [1, 320, 8, 8]) + self.assertListEqual(list(out[1].shape), [1, 640, 4, 4]) + self.assertListEqual(list(out[2].shape), [1, 1280, 2, 2]) + self.assertListEqual(list(out[3].shape), [1, 1280, 1, 1]) + + # test forward with class_id + x = torch.randn(1, 3, 60, 60) + with torch.no_grad(): + out = self.vpd_model((x, torch.tensor([2]))) + + self.assertEqual(len(out), 4) + self.assertListEqual(list(out[0].shape), [1, 320, 8, 8]) + self.assertListEqual(list(out[1].shape), [1, 640, 4, 4]) + self.assertListEqual(list(out[2].shape), [1, 1280, 2, 2]) + self.assertListEqual(list(out[3].shape), [1, 1280, 1, 1]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/utils.py b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/utils.py new file mode 100644 index 0000000..54b6404 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_backbones/utils.py @@ -0,0 +1,43 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from torch.nn.modules import GroupNorm +from torch.nn.modules.batchnorm import _BatchNorm + +from mmseg.models.backbones.resnet import BasicBlock, Bottleneck +from mmseg.models.backbones.resnext import Bottleneck as BottleneckX + + +def is_block(modules): + """Check if is ResNet building block.""" + if isinstance(modules, (BasicBlock, Bottleneck, BottleneckX)): + return True + return False + + +def is_norm(modules): + """Check if is one of the norms.""" + if isinstance(modules, (GroupNorm, _BatchNorm)): + return True + return False + + +def all_zeros(modules): + """Check if the weight(and bias) is all zero.""" + weight_zero = torch.allclose(modules.weight.data, + torch.zeros_like(modules.weight.data)) + if hasattr(modules, 'bias'): + bias_zero = torch.allclose(modules.bias.data, + torch.zeros_like(modules.bias.data)) + else: + bias_zero = True + + return weight_zero and bias_zero + + +def check_norm_state(modules, train_state): + """Check if norm layer is in correct train state.""" + for mod in modules: + if isinstance(mod, _BatchNorm): + if mod.training != train_state: + return False + return True diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_data_preprocessor.py b/Seg_All_In_One_MMSeg/tests/test_models/test_data_preprocessor.py new file mode 100644 index 0000000..d05eef1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_data_preprocessor.py @@ -0,0 +1,64 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from unittest import TestCase + +import torch +from mmengine.structures import PixelData + +from mmseg.models import SegDataPreProcessor +from mmseg.structures import SegDataSample + + +class TestSegDataPreProcessor(TestCase): + + def test_init(self): + # test mean is None + processor = SegDataPreProcessor() + self.assertTrue(not hasattr(processor, 'mean')) + self.assertTrue(processor._enable_normalize is False) + + # test mean is not None + processor = SegDataPreProcessor(mean=[0, 0, 0], std=[1, 1, 1]) + self.assertTrue(hasattr(processor, 'mean')) + self.assertTrue(hasattr(processor, 'std')) + self.assertTrue(processor._enable_normalize) + + # please specify both mean and std + with self.assertRaises(AssertionError): + SegDataPreProcessor(mean=[0, 0, 0]) + + # bgr2rgb and rgb2bgr cannot be set to True at the same time + with self.assertRaises(AssertionError): + SegDataPreProcessor(bgr_to_rgb=True, rgb_to_bgr=True) + + def test_forward(self): + data_sample = SegDataSample() + data_sample.gt_sem_seg = PixelData( + **{'data': torch.randint(0, 10, (1, 11, 10))}) + processor = SegDataPreProcessor( + mean=[0, 0, 0], std=[1, 1, 1], size=(20, 20)) + data = { + 'inputs': [ + torch.randint(0, 256, (3, 11, 10)), + torch.randint(0, 256, (3, 11, 10)) + ], + 'data_samples': [data_sample, data_sample] + } + out = processor(data, training=True) + self.assertEqual(out['inputs'].shape, (2, 3, 20, 20)) + self.assertEqual(len(out['data_samples']), 2) + + # test predict with padding + processor = SegDataPreProcessor( + mean=[0, 0, 0], + std=[1, 1, 1], + size=(20, 20), + test_cfg=dict(size_divisor=15)) + data = { + 'inputs': [ + torch.randint(0, 256, (3, 11, 10)), + ], + 'data_samples': [data_sample] + } + out = processor(data, training=False) + self.assertEqual(out['inputs'].shape[2] % 15, 0) + self.assertEqual(out['inputs'].shape[3] % 15, 0) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_forward.py b/Seg_All_In_One_MMSeg/tests/test_models/test_forward.py new file mode 100644 index 0000000..bb3967f --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_forward.py @@ -0,0 +1,229 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""pytest tests/test_forward.py.""" +import copy +from os.path import dirname, exists, join +from unittest.mock import patch + +import numpy as np +import pytest +import torch +import torch.nn as nn +from mmengine.model.utils import revert_sync_batchnorm +from mmengine.registry import init_default_scope +from mmengine.structures import PixelData +from mmengine.utils import is_list_of, is_tuple_of +from torch import Tensor + +from mmseg.structures import SegDataSample + +init_default_scope('mmseg') + + +def _demo_mm_inputs(batch_size=2, image_shapes=(3, 32, 32), num_classes=5): + """Create a superset of inputs needed to run test or train batches. + + Args: + batch_size (int): batch size. Default to 2. + image_shapes (List[tuple], Optional): image shape. + Default to (3, 128, 128) + num_classes (int): number of different labels a + box might have. Default to 10. + """ + if isinstance(image_shapes, list): + assert len(image_shapes) == batch_size + else: + image_shapes = [image_shapes] * batch_size + + inputs = [] + data_samples = [] + for idx in range(batch_size): + image_shape = image_shapes[idx] + c, h, w = image_shape + image = np.random.randint(0, 255, size=image_shape, dtype=np.uint8) + + mm_input = torch.from_numpy(image) + + img_meta = { + 'img_id': idx, + 'img_shape': image_shape[1:], + 'ori_shape': image_shape[1:], + 'pad_shape': image_shape[1:], + 'filename': '.png', + 'scale_factor': 1.0, + 'flip': False, + 'flip_direction': None, + } + + data_sample = SegDataSample() + data_sample.set_metainfo(img_meta) + + gt_semantic_seg = np.random.randint( + 0, num_classes, (1, h, w), dtype=np.uint8) + gt_semantic_seg = torch.LongTensor(gt_semantic_seg) + gt_sem_seg_data = dict(data=gt_semantic_seg) + data_sample.gt_sem_seg = PixelData(**gt_sem_seg_data) + inputs.append(mm_input) + data_samples.append(data_sample) + return dict(inputs=inputs, data_samples=data_samples) + + +def _get_config_directory(): + """Find the predefined segmentor config directory.""" + try: + # Assume we are running in the source mmsegmentation repo + repo_dpath = dirname(dirname(dirname(__file__))) + except NameError: + # For IPython development when this __file__ is not defined + import mmseg + repo_dpath = dirname(dirname(dirname(mmseg.__file__))) + config_dpath = join(repo_dpath, 'configs') + if not exists(config_dpath): + raise Exception('Cannot find config path') + return config_dpath + + +def _get_config_module(fname): + """Load a configuration as a python module.""" + from mmengine import Config + config_dpath = _get_config_directory() + config_fpath = join(config_dpath, fname) + config_mod = Config.fromfile(config_fpath) + return config_mod + + +def _get_segmentor_cfg(fname): + """Grab configs necessary to create a segmentor. + + These are deep copied to allow for safe modification of parameters without + influencing other tests. + """ + config = _get_config_module(fname) + model = copy.deepcopy(config.model) + return model + + +def test_pspnet_forward(): + _test_encoder_decoder_forward( + 'pspnet/pspnet_r18-d8_4xb2-80k_cityscapes-512x1024.py') + + +def test_fcn_forward(): + _test_encoder_decoder_forward( + 'fcn/fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py') + + +def test_deeplabv3_forward(): + _test_encoder_decoder_forward( + 'deeplabv3/deeplabv3_r18-d8_4xb2-80k_cityscapes-512x1024.py') + + +def test_deeplabv3plus_forward(): + _test_encoder_decoder_forward( + 'deeplabv3plus/deeplabv3plus_r18-d8_4xb2-80k_cityscapes-512x1024.py') + + +def test_gcnet_forward(): + _test_encoder_decoder_forward( + 'gcnet/gcnet_r50-d8_4xb2-40k_cityscapes-512x1024.py') + + +def test_ccnet_forward(): + if not torch.cuda.is_available(): + pytest.skip('CCNet requires CUDA') + _test_encoder_decoder_forward( + 'ccnet/ccnet_r50-d8_4xb2-40k_cityscapes-512x1024.py') + + +def test_upernet_forward(): + _test_encoder_decoder_forward( + 'upernet/upernet_r50_4xb2-40k_cityscapes-512x1024.py') + + +def test_hrnet_forward(): + _test_encoder_decoder_forward( + 'hrnet/fcn_hr18s_4xb2-40k_cityscapes-512x1024.py') + + +def test_ocrnet_forward(): + _test_encoder_decoder_forward( + 'ocrnet/ocrnet_hr18s_4xb2-40k_cityscapes-512x1024.py') + + +def test_sem_fpn_forward(): + _test_encoder_decoder_forward( + 'sem_fpn/fpn_r50_4xb2-80k_cityscapes-512x1024.py') + + +def test_mobilenet_v2_forward(): + _test_encoder_decoder_forward( + 'mobilenet_v2/mobilenet-v2-d8_pspnet_4xb2-80k_cityscapes-512x1024.py') + + +def get_world_size(process_group): + + return 1 + + +def _check_input_dim(self, inputs): + pass + + +@patch('torch.nn.modules.batchnorm._BatchNorm._check_input_dim', + _check_input_dim) +@patch('torch.distributed.get_world_size', get_world_size) +def _test_encoder_decoder_forward(cfg_file): + model = _get_segmentor_cfg(cfg_file) + model['pretrained'] = None + model['test_cfg']['mode'] = 'whole' + + from mmseg.models import build_segmentor + segmentor = build_segmentor(model) + segmentor.init_weights() + + if isinstance(segmentor.decode_head, nn.ModuleList): + num_classes = segmentor.decode_head[-1].num_classes + else: + num_classes = segmentor.decode_head.num_classes + # batch_size=2 for BatchNorm + packed_inputs = _demo_mm_inputs( + batch_size=2, image_shapes=(3, 4, 4), num_classes=num_classes) + # convert to cuda Tensor if applicable + if torch.cuda.is_available(): + segmentor = segmentor.cuda() + else: + segmentor = revert_sync_batchnorm(segmentor) + + # Test forward train + data = segmentor.data_preprocessor(packed_inputs, True) + losses = segmentor.forward(**data, mode='loss') + assert isinstance(losses, dict) + + packed_inputs = _demo_mm_inputs( + batch_size=1, image_shapes=(3, 32, 32), num_classes=num_classes) + data = segmentor.data_preprocessor(packed_inputs, False) + with torch.no_grad(): + segmentor.eval() + # Test forward predict + batch_results = segmentor.forward(**data, mode='predict') + assert len(batch_results) == 1 + assert is_list_of(batch_results, SegDataSample) + assert batch_results[0].pred_sem_seg.shape == (32, 32) + assert batch_results[0].seg_logits.data.shape == (num_classes, 32, 32) + assert batch_results[0].gt_sem_seg.shape == (32, 32) + + # Test forward tensor + batch_results = segmentor.forward(**data, mode='tensor') + assert isinstance(batch_results, Tensor) or is_tuple_of( + batch_results, Tensor) + + # Test forward predict without ground truth + data.pop('data_samples') + batch_results = segmentor.forward(**data, mode='predict') + assert len(batch_results) == 1 + assert is_list_of(batch_results, SegDataSample) + assert batch_results[0].pred_sem_seg.shape == (32, 32) + + # Test forward tensor without ground truth + batch_results = segmentor.forward(**data, mode='tensor') + assert isinstance(batch_results, Tensor) or is_tuple_of( + batch_results, Tensor) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/__init__.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/__init__.py new file mode 100644 index 0000000..ef101fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/__init__.py @@ -0,0 +1 @@ +# Copyright (c) OpenMMLab. All rights reserved. diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ann_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ann_head.py new file mode 100644 index 0000000..c1e44bc --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ann_head.py @@ -0,0 +1,20 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import ANNHead +from .utils import to_cuda + + +def test_ann_head(): + + inputs = [torch.randn(1, 4, 45, 45), torch.randn(1, 8, 21, 21)] + head = ANNHead( + in_channels=[4, 8], + channels=2, + num_classes=19, + in_index=[-2, -1], + project_channels=8) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 21, 21) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_apc_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_apc_head.py new file mode 100644 index 0000000..dc55ccc --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_apc_head.py @@ -0,0 +1,59 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import APCHead +from .utils import _conv_has_norm, to_cuda + + +def test_apc_head(): + + with pytest.raises(AssertionError): + # pool_scales must be list|tuple + APCHead(in_channels=8, channels=2, num_classes=19, pool_scales=1) + + # test no norm_cfg + head = APCHead(in_channels=8, channels=2, num_classes=19) + assert not _conv_has_norm(head, sync_bn=False) + + # test with norm_cfg + head = APCHead( + in_channels=8, + channels=2, + num_classes=19, + norm_cfg=dict(type='SyncBN')) + assert _conv_has_norm(head, sync_bn=True) + + # fusion=True + inputs = [torch.randn(1, 8, 45, 45)] + head = APCHead( + in_channels=8, + channels=2, + num_classes=19, + pool_scales=(1, 2, 3), + fusion=True) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.fusion is True + assert head.acm_modules[0].pool_scale == 1 + assert head.acm_modules[1].pool_scale == 2 + assert head.acm_modules[2].pool_scale == 3 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 45, 45) + + # fusion=False + inputs = [torch.randn(1, 8, 45, 45)] + head = APCHead( + in_channels=8, + channels=2, + num_classes=19, + pool_scales=(1, 2, 3), + fusion=False) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.fusion is False + assert head.acm_modules[0].pool_scale == 1 + assert head.acm_modules[1].pool_scale == 2 + assert head.acm_modules[2].pool_scale == 3 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 45, 45) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_aspp_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_aspp_head.py new file mode 100644 index 0000000..db9e893 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_aspp_head.py @@ -0,0 +1,76 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import ASPPHead, DepthwiseSeparableASPPHead +from .utils import _conv_has_norm, to_cuda + + +def test_aspp_head(): + + with pytest.raises(AssertionError): + # pool_scales must be list|tuple + ASPPHead(in_channels=8, channels=4, num_classes=19, dilations=1) + + # test no norm_cfg + head = ASPPHead(in_channels=8, channels=4, num_classes=19) + assert not _conv_has_norm(head, sync_bn=False) + + # test with norm_cfg + head = ASPPHead( + in_channels=8, + channels=4, + num_classes=19, + norm_cfg=dict(type='SyncBN')) + assert _conv_has_norm(head, sync_bn=True) + + inputs = [torch.randn(1, 8, 45, 45)] + head = ASPPHead( + in_channels=8, channels=4, num_classes=19, dilations=(1, 12, 24)) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.aspp_modules[0].conv.dilation == (1, 1) + assert head.aspp_modules[1].conv.dilation == (12, 12) + assert head.aspp_modules[2].conv.dilation == (24, 24) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 45, 45) + + +def test_dw_aspp_head(): + + # test w.o. c1 + inputs = [torch.randn(1, 8, 45, 45)] + head = DepthwiseSeparableASPPHead( + c1_in_channels=0, + c1_channels=0, + in_channels=8, + channels=4, + num_classes=19, + dilations=(1, 12, 24)) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.c1_bottleneck is None + assert head.aspp_modules[0].conv.dilation == (1, 1) + assert head.aspp_modules[1].depthwise_conv.dilation == (12, 12) + assert head.aspp_modules[2].depthwise_conv.dilation == (24, 24) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 45, 45) + + # test with c1 + inputs = [torch.randn(1, 4, 45, 45), torch.randn(1, 16, 21, 21)] + head = DepthwiseSeparableASPPHead( + c1_in_channels=4, + c1_channels=2, + in_channels=16, + channels=8, + num_classes=19, + dilations=(1, 12, 24)) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.c1_bottleneck.in_channels == 4 + assert head.c1_bottleneck.out_channels == 2 + assert head.aspp_modules[0].conv.dilation == (1, 1) + assert head.aspp_modules[1].depthwise_conv.dilation == (12, 12) + assert head.aspp_modules[2].depthwise_conv.dilation == (24, 24) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 45, 45) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_cc_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_cc_head.py new file mode 100644 index 0000000..0630417 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_cc_head.py @@ -0,0 +1,18 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import CCHead +from .utils import to_cuda + + +def test_cc_head(): + head = CCHead(in_channels=16, channels=8, num_classes=19) + assert len(head.convs) == 2 + assert hasattr(head, 'cca') + if not torch.cuda.is_available(): + pytest.skip('CCHead requires CUDA') + inputs = [torch.randn(1, 16, 23, 23)] + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_decode_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_decode_head.py new file mode 100644 index 0000000..88e6bed --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_decode_head.py @@ -0,0 +1,193 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from unittest.mock import patch + +import pytest +import torch +from mmengine.structures import PixelData + +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.structures import SegDataSample +from .utils import to_cuda + + +@patch.multiple(BaseDecodeHead, __abstractmethods__=set()) +def test_decode_head(): + + with pytest.raises(AssertionError): + # default input_transform doesn't accept multiple inputs + BaseDecodeHead([32, 16], 16, num_classes=19) + + with pytest.raises(AssertionError): + # default input_transform doesn't accept multiple inputs + BaseDecodeHead(32, 16, num_classes=19, in_index=[-1, -2]) + + with pytest.raises(AssertionError): + # supported mode is resize_concat only + BaseDecodeHead(32, 16, num_classes=19, input_transform='concat') + + with pytest.raises(AssertionError): + # in_channels should be list|tuple + BaseDecodeHead(32, 16, num_classes=19, input_transform='resize_concat') + + with pytest.raises(AssertionError): + # in_index should be list|tuple + BaseDecodeHead([32], + 16, + in_index=-1, + num_classes=19, + input_transform='resize_concat') + + with pytest.raises(AssertionError): + # len(in_index) should equal len(in_channels) + BaseDecodeHead([32, 16], + 16, + num_classes=19, + in_index=[-1], + input_transform='resize_concat') + + with pytest.raises(ValueError): + # out_channels should be equal to num_classes + BaseDecodeHead(32, 16, num_classes=19, out_channels=18) + + # test out_channels + head = BaseDecodeHead(32, 16, num_classes=2) + assert head.out_channels == 2 + + # test out_channels == 1 and num_classes == 2 + head = BaseDecodeHead(32, 16, num_classes=2, out_channels=1) + assert head.out_channels == 1 and head.num_classes == 2 + + # test default dropout + head = BaseDecodeHead(32, 16, num_classes=19) + assert hasattr(head, 'dropout') and head.dropout.p == 0.1 + + # test set dropout + head = BaseDecodeHead(32, 16, num_classes=19, dropout_ratio=0.2) + assert hasattr(head, 'dropout') and head.dropout.p == 0.2 + + # test no input_transform + inputs = [torch.randn(1, 32, 45, 45)] + head = BaseDecodeHead(32, 16, num_classes=19) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.in_channels == 32 + assert head.input_transform is None + transformed_inputs = head._transform_inputs(inputs) + assert transformed_inputs.shape == (1, 32, 45, 45) + + # test input_transform = resize_concat + inputs = [torch.randn(1, 32, 45, 45), torch.randn(1, 16, 21, 21)] + head = BaseDecodeHead([32, 16], + 16, + num_classes=19, + in_index=[0, 1], + input_transform='resize_concat') + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.in_channels == 48 + assert head.input_transform == 'resize_concat' + transformed_inputs = head._transform_inputs(inputs) + assert transformed_inputs.shape == (1, 48, 45, 45) + + # test multi-loss, loss_decode is dict + with pytest.raises(TypeError): + # loss_decode must be a dict or sequence of dict. + BaseDecodeHead(3, 16, num_classes=19, loss_decode=['CrossEntropyLoss']) + + inputs = torch.randn(2, 19, 8, 8).float() + data_samples = [ + SegDataSample(gt_sem_seg=PixelData(data=torch.ones(64, 64).long())) + for _ in range(2) + ] + + head = BaseDecodeHead( + 3, + 16, + num_classes=19, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + loss = head.loss_by_feat( + seg_logits=inputs, batch_data_samples=data_samples) + assert 'loss_ce' in loss + + # test multi-loss, loss_decode is list of dict + inputs = torch.randn(2, 19, 8, 8).float() + data_samples = [ + SegDataSample(gt_sem_seg=PixelData(data=torch.ones(64, 64).long())) + for _ in range(2) + ] + head = BaseDecodeHead( + 3, + 16, + num_classes=19, + loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_1'), + dict(type='CrossEntropyLoss', loss_name='loss_2') + ]) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + + loss = head.loss_by_feat( + seg_logits=inputs, batch_data_samples=data_samples) + assert 'loss_1' in loss + assert 'loss_2' in loss + + # 'loss_decode' must be a dict or sequence of dict + with pytest.raises(TypeError): + BaseDecodeHead(3, 16, num_classes=19, loss_decode=['CrossEntropyLoss']) + with pytest.raises(TypeError): + BaseDecodeHead(3, 16, num_classes=19, loss_decode=0) + + # test multi-loss, loss_decode is list of dict + inputs = torch.randn(2, 19, 8, 8).float() + data_samples = [ + SegDataSample(gt_sem_seg=PixelData(data=torch.ones(64, 64).long())) + for _ in range(2) + ] + head = BaseDecodeHead( + 3, + 16, + num_classes=19, + loss_decode=(dict(type='CrossEntropyLoss', loss_name='loss_1'), + dict(type='CrossEntropyLoss', loss_name='loss_2'), + dict(type='CrossEntropyLoss', loss_name='loss_3'))) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + loss = head.loss_by_feat( + seg_logits=inputs, batch_data_samples=data_samples) + assert 'loss_1' in loss + assert 'loss_2' in loss + assert 'loss_3' in loss + + # test multi-loss, loss_decode is list of dict, names of them are identical + inputs = torch.randn(2, 19, 8, 8).float() + data_samples = [ + SegDataSample(gt_sem_seg=PixelData(data=torch.ones(64, 64).long())) + for _ in range(2) + ] + head = BaseDecodeHead( + 3, + 16, + num_classes=19, + loss_decode=(dict(type='CrossEntropyLoss', loss_name='loss_ce'), + dict(type='CrossEntropyLoss', loss_name='loss_ce'), + dict(type='CrossEntropyLoss', loss_name='loss_ce'))) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + loss_3 = head.loss_by_feat( + seg_logits=inputs, batch_data_samples=data_samples) + + head = BaseDecodeHead( + 3, + 16, + num_classes=19, + loss_decode=(dict(type='CrossEntropyLoss', loss_name='loss_ce'))) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + loss = head.loss_by_feat( + seg_logits=inputs, batch_data_samples=data_samples) + assert 'loss_ce' in loss + assert 'loss_ce' in loss_3 + assert loss_3['loss_ce'] == 3 * loss['loss_ce'] diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dm_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dm_head.py new file mode 100644 index 0000000..a922ff7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dm_head.py @@ -0,0 +1,59 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import DMHead +from .utils import _conv_has_norm, to_cuda + + +def test_dm_head(): + + with pytest.raises(AssertionError): + # filter_sizes must be list|tuple + DMHead(in_channels=8, channels=4, num_classes=19, filter_sizes=1) + + # test no norm_cfg + head = DMHead(in_channels=8, channels=4, num_classes=19) + assert not _conv_has_norm(head, sync_bn=False) + + # test with norm_cfg + head = DMHead( + in_channels=8, + channels=4, + num_classes=19, + norm_cfg=dict(type='SyncBN')) + assert _conv_has_norm(head, sync_bn=True) + + # fusion=True + inputs = [torch.randn(1, 8, 23, 23)] + head = DMHead( + in_channels=8, + channels=4, + num_classes=19, + filter_sizes=(1, 3, 5), + fusion=True) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.fusion is True + assert head.dcm_modules[0].filter_size == 1 + assert head.dcm_modules[1].filter_size == 3 + assert head.dcm_modules[2].filter_size == 5 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # fusion=False + inputs = [torch.randn(1, 8, 23, 23)] + head = DMHead( + in_channels=8, + channels=4, + num_classes=19, + filter_sizes=(1, 3, 5), + fusion=False) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.fusion is False + assert head.dcm_modules[0].filter_size == 1 + assert head.dcm_modules[1].filter_size == 3 + assert head.dcm_modules[2].filter_size == 5 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dnl_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dnl_head.py new file mode 100644 index 0000000..720cb07 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dnl_head.py @@ -0,0 +1,44 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import DNLHead +from .utils import to_cuda + + +def test_dnl_head(): + # DNL with 'embedded_gaussian' mode + head = DNLHead(in_channels=8, channels=4, num_classes=19) + assert len(head.convs) == 2 + assert hasattr(head, 'dnl_block') + assert head.dnl_block.temperature == 0.05 + inputs = [torch.randn(1, 8, 23, 23)] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # NonLocal2d with 'dot_product' mode + head = DNLHead( + in_channels=8, channels=4, num_classes=19, mode='dot_product') + inputs = [torch.randn(1, 8, 23, 23)] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # NonLocal2d with 'gaussian' mode + head = DNLHead(in_channels=8, channels=4, num_classes=19, mode='gaussian') + inputs = [torch.randn(1, 8, 23, 23)] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # NonLocal2d with 'concatenation' mode + head = DNLHead( + in_channels=8, channels=4, num_classes=19, mode='concatenation') + inputs = [torch.randn(1, 8, 23, 23)] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dpt_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dpt_head.py new file mode 100644 index 0000000..0a6af61 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_dpt_head.py @@ -0,0 +1,49 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import DPTHead + + +def test_dpt_head(): + + with pytest.raises(AssertionError): + # input_transform must be 'multiple_select' + head = DPTHead( + in_channels=[768, 768, 768, 768], + channels=4, + num_classes=19, + in_index=[0, 1, 2, 3]) + + head = DPTHead( + in_channels=[768, 768, 768, 768], + channels=4, + num_classes=19, + in_index=[0, 1, 2, 3], + input_transform='multiple_select') + + inputs = [[torch.randn(4, 768, 2, 2), + torch.randn(4, 768)] for _ in range(4)] + output = head(inputs) + assert output.shape == torch.Size((4, 19, 16, 16)) + + # test readout operation + head = DPTHead( + in_channels=[768, 768, 768, 768], + channels=4, + num_classes=19, + in_index=[0, 1, 2, 3], + input_transform='multiple_select', + readout_type='add') + output = head(inputs) + assert output.shape == torch.Size((4, 19, 16, 16)) + + head = DPTHead( + in_channels=[768, 768, 768, 768], + channels=4, + num_classes=19, + in_index=[0, 1, 2, 3], + input_transform='multiple_select', + readout_type='project') + output = head(inputs) + assert output.shape == torch.Size((4, 19, 16, 16)) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ema_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ema_head.py new file mode 100644 index 0000000..1811cd2 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ema_head.py @@ -0,0 +1,23 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import EMAHead +from .utils import to_cuda + + +def test_emanet_head(): + head = EMAHead( + in_channels=4, + ema_channels=3, + channels=2, + num_stages=3, + num_bases=2, + num_classes=19) + for param in head.ema_mid_conv.parameters(): + assert not param.requires_grad + assert hasattr(head, 'ema_module') + inputs = [torch.randn(1, 4, 23, 23)] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_fcn_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_fcn_head.py new file mode 100644 index 0000000..664b543 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_fcn_head.py @@ -0,0 +1,131 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch +from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule +from mmengine.utils.dl_utils.parrots_wrapper import SyncBatchNorm + +from mmseg.models.decode_heads import DepthwiseSeparableFCNHead, FCNHead +from .utils import to_cuda + + +def test_fcn_head(): + + with pytest.raises(AssertionError): + # num_convs must be not less than 0 + FCNHead(num_classes=19, num_convs=-1) + + # test no norm_cfg + head = FCNHead(in_channels=8, channels=4, num_classes=19) + for m in head.modules(): + if isinstance(m, ConvModule): + assert not m.with_norm + + # test with norm_cfg + head = FCNHead( + in_channels=8, + channels=4, + num_classes=19, + norm_cfg=dict(type='SyncBN')) + for m in head.modules(): + if isinstance(m, ConvModule): + assert m.with_norm and isinstance(m.bn, SyncBatchNorm) + + # test concat_input=False + inputs = [torch.randn(1, 8, 23, 23)] + head = FCNHead( + in_channels=8, channels=4, num_classes=19, concat_input=False) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert len(head.convs) == 2 + assert not head.concat_input and not hasattr(head, 'conv_cat') + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # test concat_input=True + inputs = [torch.randn(1, 8, 23, 23)] + head = FCNHead( + in_channels=8, channels=4, num_classes=19, concat_input=True) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert len(head.convs) == 2 + assert head.concat_input + assert head.conv_cat.in_channels == 12 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # test kernel_size=3 + inputs = [torch.randn(1, 8, 23, 23)] + head = FCNHead(in_channels=8, channels=4, num_classes=19) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + for i in range(len(head.convs)): + assert head.convs[i].kernel_size == (3, 3) + assert head.convs[i].padding == 1 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # test kernel_size=1 + inputs = [torch.randn(1, 8, 23, 23)] + head = FCNHead(in_channels=8, channels=4, num_classes=19, kernel_size=1) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + for i in range(len(head.convs)): + assert head.convs[i].kernel_size == (1, 1) + assert head.convs[i].padding == 0 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # test num_conv + inputs = [torch.randn(1, 8, 23, 23)] + head = FCNHead(in_channels=8, channels=4, num_classes=19, num_convs=1) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert len(head.convs) == 1 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + # test num_conv = 0 + inputs = [torch.randn(1, 8, 23, 23)] + head = FCNHead( + in_channels=8, + channels=8, + num_classes=19, + num_convs=0, + concat_input=False) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert isinstance(head.convs, torch.nn.Identity) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) + + +def test_sep_fcn_head(): + # test sep_fcn_head with concat_input=False + head = DepthwiseSeparableFCNHead( + in_channels=128, + channels=128, + concat_input=False, + num_classes=19, + in_index=-1, + norm_cfg=dict(type='BN', requires_grad=True, momentum=0.01)) + x = [torch.rand(2, 128, 8, 8)] + output = head(x) + assert output.shape == (2, head.num_classes, 8, 8) + assert not head.concat_input + assert isinstance(head.convs[0], DepthwiseSeparableConvModule) + assert isinstance(head.convs[1], DepthwiseSeparableConvModule) + assert head.conv_seg.kernel_size == (1, 1) + + head = DepthwiseSeparableFCNHead( + in_channels=64, + channels=64, + concat_input=True, + num_classes=19, + in_index=-1, + norm_cfg=dict(type='BN', requires_grad=True, momentum=0.01)) + x = [torch.rand(3, 64, 8, 8)] + output = head(x) + assert output.shape == (3, head.num_classes, 8, 8) + assert head.concat_input + assert isinstance(head.convs[0], DepthwiseSeparableConvModule) + assert isinstance(head.convs[1], DepthwiseSeparableConvModule) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_gc_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_gc_head.py new file mode 100644 index 0000000..c62ac9a --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_gc_head.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import GCHead +from .utils import to_cuda + + +def test_gc_head(): + head = GCHead(in_channels=4, channels=4, num_classes=19) + assert len(head.convs) == 2 + assert hasattr(head, 'gc_block') + inputs = [torch.randn(1, 4, 23, 23)] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ham_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ham_head.py new file mode 100644 index 0000000..f802d2d --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ham_head.py @@ -0,0 +1,44 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import LightHamHead +from .utils import _conv_has_norm, to_cuda + +ham_norm_cfg = dict(type='GN', num_groups=32, requires_grad=True) + + +def test_ham_head(): + + # test without sync_bn + head = LightHamHead( + in_channels=[16, 32, 64], + in_index=[1, 2, 3], + channels=64, + ham_channels=64, + dropout_ratio=0.1, + num_classes=19, + norm_cfg=ham_norm_cfg, + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), + ham_kwargs=dict( + MD_S=1, + MD_R=64, + train_steps=6, + eval_steps=7, + inv_t=100, + rand_init=True)) + assert not _conv_has_norm(head, sync_bn=False) + + inputs = [ + torch.randn(1, 8, 32, 32), + torch.randn(1, 16, 16, 16), + torch.randn(1, 32, 8, 8), + torch.randn(1, 64, 4, 4) + ] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.in_channels == [16, 32, 64] + assert head.hamburger.ham_in.in_channels == 64 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 16, 16) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_isa_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_isa_head.py new file mode 100644 index 0000000..b177f6d --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_isa_head.py @@ -0,0 +1,20 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import ISAHead +from .utils import to_cuda + + +def test_isa_head(): + + inputs = [torch.randn(1, 8, 23, 23)] + isa_head = ISAHead( + in_channels=8, + channels=4, + num_classes=19, + isa_channels=4, + down_factor=(8, 8)) + if torch.cuda.is_available(): + isa_head, inputs = to_cuda(isa_head, inputs) + output = isa_head(inputs) + assert output.shape == (1, isa_head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_lraspp_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_lraspp_head.py new file mode 100644 index 0000000..a46e6a1 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_lraspp_head.py @@ -0,0 +1,68 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import LRASPPHead + + +def test_lraspp_head(): + with pytest.raises(ValueError): + # check invalid input_transform + LRASPPHead( + in_channels=(4, 4, 123), + in_index=(0, 1, 2), + channels=32, + input_transform='resize_concat', + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + + with pytest.raises(AssertionError): + # check invalid branch_channels + LRASPPHead( + in_channels=(4, 4, 123), + in_index=(0, 1, 2), + channels=32, + branch_channels=64, + input_transform='multiple_select', + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + + # test with default settings + lraspp_head = LRASPPHead( + in_channels=(4, 4, 123), + in_index=(0, 1, 2), + channels=32, + input_transform='multiple_select', + dropout_ratio=0.1, + num_classes=19, + norm_cfg=dict(type='BN'), + act_cfg=dict(type='ReLU'), + align_corners=False, + loss_decode=dict( + type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)) + inputs = [ + torch.randn(2, 4, 45, 45), + torch.randn(2, 4, 28, 28), + torch.randn(2, 123, 14, 14) + ] + with pytest.raises(RuntimeError): + # check invalid inputs + output = lraspp_head(inputs) + + inputs = [ + torch.randn(2, 4, 111, 111), + torch.randn(2, 4, 77, 77), + torch.randn(2, 123, 55, 55) + ] + output = lraspp_head(inputs) + assert output.shape == (2, 19, 111, 111) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_mask2former_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_mask2former_head.py new file mode 100644 index 0000000..45b353d --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_mask2former_head.py @@ -0,0 +1,153 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine import Config +from mmengine.structures import PixelData + +from mmseg.models.decode_heads import Mask2FormerHead +from mmseg.structures import SegDataSample +from mmseg.utils import SampleList +from .utils import to_cuda + + +def test_mask2former_head(): + num_classes = 19 + cfg = dict( + in_channels=[96, 192, 384, 768], + strides=[4, 8, 16, 32], + feat_channels=256, + out_channels=256, + num_classes=num_classes, + num_queries=100, + num_transformer_feat_level=3, + align_corners=False, + pixel_decoder=dict( + type='mmdet.MSDeformAttnPixelDecoder', + num_outs=3, + norm_cfg=dict(type='GN', num_groups=32), + act_cfg=dict(type='ReLU'), + encoder=dict( # DeformableDetrTransformerEncoder + num_layers=6, + layer_cfg=dict( # DeformableDetrTransformerEncoderLayer + self_attn_cfg=dict( # MultiScaleDeformableAttention + embed_dims=256, + num_heads=8, + num_levels=3, + num_points=4, + im2col_step=64, + dropout=0.0, + batch_first=True, + norm_cfg=None, + init_cfg=None), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=1024, + num_fcs=2, + ffn_drop=0.0, + act_cfg=dict(type='ReLU', inplace=True))), + init_cfg=None), + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + init_cfg=None), + enforce_decoder_input_project=False, + positional_encoding=dict( # SinePositionalEncoding + num_feats=128, normalize=True), + transformer_decoder=dict( # Mask2FormerTransformerDecoder + return_intermediate=True, + num_layers=9, + layer_cfg=dict( # Mask2FormerTransformerDecoderLayer + self_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + cross_attn_cfg=dict( # MultiheadAttention + embed_dims=256, + num_heads=8, + attn_drop=0.0, + proj_drop=0.0, + dropout_layer=None, + batch_first=True), + ffn_cfg=dict( + embed_dims=256, + feedforward_channels=2048, + num_fcs=2, + act_cfg=dict(type='ReLU', inplace=True), + ffn_drop=0.0, + dropout_layer=None, + add_identity=True)), + init_cfg=None), + loss_cls=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=False, + loss_weight=2.0, + reduction='mean', + class_weight=[1.0] * num_classes + [0.1]), + loss_mask=dict( + type='mmdet.CrossEntropyLoss', + use_sigmoid=True, + reduction='mean', + loss_weight=5.0), + loss_dice=dict( + type='mmdet.DiceLoss', + use_sigmoid=True, + activate=True, + reduction='mean', + naive_dice=True, + eps=1.0, + loss_weight=5.0), + train_cfg=dict( + num_points=12544, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='mmdet.HungarianAssigner', + match_costs=[ + dict(type='mmdet.ClassificationCost', weight=2.0), + dict( + type='mmdet.CrossEntropyLossCost', + weight=5.0, + use_sigmoid=True), + dict( + type='mmdet.DiceCost', + weight=5.0, + pred_act=True, + eps=1.0) + ]), + sampler=dict(type='mmdet.MaskPseudoSampler'))) + cfg = Config(cfg) + head = Mask2FormerHead(**cfg) + + inputs = [ + torch.rand((2, 96, 8, 8)), + torch.rand((2, 192, 4, 4)), + torch.rand((2, 384, 2, 2)), + torch.rand((2, 768, 1, 1)) + ] + + data_samples: SampleList = [] + for i in range(2): + data_sample = SegDataSample() + img_meta = {} + img_meta['img_shape'] = (32, 32) + img_meta['ori_shape'] = (32, 32) + data_sample.gt_sem_seg = PixelData( + data=torch.randint(0, num_classes, (1, 32, 32))) + data_sample.set_metainfo(img_meta) + data_samples.append(data_sample) + + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + for data_sample in data_samples: + data_sample.gt_sem_seg.data = data_sample.gt_sem_seg.data.cuda() + + loss_dict = head.loss(inputs, data_samples, None) + assert isinstance(loss_dict, dict) + + batch_img_metas = [] + for data_sample in data_samples: + batch_img_metas.append(data_sample.metainfo) + + seg_logits = head.predict(inputs, batch_img_metas, None) + assert seg_logits.shape == torch.Size((2, num_classes, 32, 32)) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_maskformer_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_maskformer_head.py new file mode 100644 index 0000000..6a47239 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_maskformer_head.py @@ -0,0 +1,54 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from os.path import dirname, join + +import torch +from mmengine import Config +from mmengine.registry import init_default_scope +from mmengine.structures import PixelData + +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample + + +def test_maskformer_head(): + init_default_scope('mmseg') + repo_dpath = dirname(dirname(__file__)) + cfg = Config.fromfile( + join( + repo_dpath, + '../../configs/maskformer/maskformer_r50-d32_8xb2-160k_ade20k-512x512.py' # noqa + )) + cfg.model.train_cfg = None + decode_head = MODELS.build(cfg.model.decode_head) + inputs = (torch.randn(1, 256, 32, 32), torch.randn(1, 512, 16, 16), + torch.randn(1, 1024, 8, 8), torch.randn(1, 2048, 4, 4)) + # test inference + batch_img_metas = [ + dict( + scale_factor=(1.0, 1.0), + img_shape=(512, 683), + ori_shape=(512, 683)) + ] + test_cfg = dict(mode='whole') + output = decode_head.predict(inputs, batch_img_metas, test_cfg) + assert output.shape == (1, 150, 512, 683) + + # test training + inputs = (torch.randn(2, 256, 32, 32), torch.randn(2, 512, 16, 16), + torch.randn(2, 1024, 8, 8), torch.randn(2, 2048, 4, 4)) + batch_data_samples = [] + img_meta = { + 'img_shape': (512, 512), + 'ori_shape': (480, 640), + 'pad_shape': (512, 512), + 'scale_factor': (1.425, 1.425), + } + for _ in range(2): + data_sample = SegDataSample( + gt_sem_seg=PixelData(data=torch.ones(512, 512).long())) + data_sample.set_metainfo(img_meta) + batch_data_samples.append(data_sample) + train_cfg = {} + losses = decode_head.loss(inputs, batch_data_samples, train_cfg) + assert (loss in losses.keys() + for loss in ('loss_cls', 'loss_mask', 'loss_dice')) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_nl_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_nl_head.py new file mode 100644 index 0000000..d4ef0b9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_nl_head.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import NLHead +from .utils import to_cuda + + +def test_nl_head(): + head = NLHead(in_channels=8, channels=4, num_classes=19) + assert len(head.convs) == 2 + assert hasattr(head, 'nl_block') + inputs = [torch.randn(1, 8, 23, 23)] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ocr_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ocr_head.py new file mode 100644 index 0000000..5e5d669 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_ocr_head.py @@ -0,0 +1,19 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import FCNHead, OCRHead +from .utils import to_cuda + + +def test_ocr_head(): + + inputs = [torch.randn(1, 8, 23, 23)] + ocr_head = OCRHead( + in_channels=8, channels=4, num_classes=19, ocr_channels=8) + fcn_head = FCNHead(in_channels=8, channels=4, num_classes=19) + if torch.cuda.is_available(): + head, inputs = to_cuda(ocr_head, inputs) + head, inputs = to_cuda(fcn_head, inputs) + prev_output = fcn_head(inputs) + output = ocr_head(inputs, prev_output) + assert output.shape == (1, ocr_head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_pidnet_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_pidnet_head.py new file mode 100644 index 0000000..a624737 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_pidnet_head.py @@ -0,0 +1,89 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine.registry import init_default_scope + +from mmseg.registry import MODELS + + +def test_pidnet_head(): + init_default_scope('mmseg') + + # Test PIDNet decode head Standard Forward + norm_cfg = dict(type='BN', requires_grad=True) + backbone_cfg = dict( + type='PIDNet', + in_channels=3, + channels=32, + ppm_channels=96, + num_stem_blocks=2, + num_branch_blocks=3, + align_corners=False, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU', inplace=True)) + decode_head_cfg = dict( + type='PIDHead', + in_channels=128, + channels=128, + num_classes=19, + norm_cfg=norm_cfg, + act_cfg=dict(type='ReLU', inplace=True), + align_corners=True, + loss_decode=[ + dict( + type='CrossEntropyLoss', + use_sigmoid=False, + class_weight=[ + 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, + 1.0489, 0.8786, 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, + 1.0865, 1.0955, 1.0865, 1.1529, 1.0507 + ], + loss_weight=0.4), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + class_weight=[ + 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, + 1.0489, 0.8786, 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, + 1.0865, 1.0955, 1.0865, 1.1529, 1.0507 + ], + loss_weight=1.0), + dict(type='BoundaryLoss', loss_weight=20.0), + dict( + type='OhemCrossEntropy', + thres=0.9, + min_kept=131072, + class_weight=[ + 0.8373, 0.918, 0.866, 1.0345, 1.0166, 0.9969, 0.9754, + 1.0489, 0.8786, 1.0023, 0.9539, 0.9843, 1.1116, 0.9037, + 1.0865, 1.0955, 1.0865, 1.1529, 1.0507 + ], + loss_weight=1.0) + ]) + backbone = MODELS.build(backbone_cfg) + head = MODELS.build(decode_head_cfg) + + # Test train mode + backbone.train() + head.train() + batch_size = 2 + imgs = torch.randn(batch_size, 3, 64, 128) + feats = backbone(imgs) + seg_logit = head(feats) + + assert isinstance(seg_logit, tuple) + assert len(seg_logit) == 3 + + p_logits, i_logits, d_logits = seg_logit + assert p_logits.shape == (batch_size, 19, 8, 16) + assert i_logits.shape == (batch_size, 19, 8, 16) + assert d_logits.shape == (batch_size, 1, 8, 16) + + # Test eval mode + backbone.eval() + head.eval() + feats = backbone(imgs) + seg_logit = head(feats) + + assert isinstance(seg_logit, torch.Tensor) + assert seg_logit.shape == (batch_size, 19, 8, 16) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_psa_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_psa_head.py new file mode 100644 index 0000000..34f592b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_psa_head.py @@ -0,0 +1,122 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import PSAHead +from .utils import _conv_has_norm, to_cuda + + +def test_psa_head(): + + with pytest.raises(AssertionError): + # psa_type must be in 'bi-direction', 'collect', 'distribute' + PSAHead( + in_channels=4, + channels=2, + num_classes=19, + mask_size=(13, 13), + psa_type='gather') + + # test no norm_cfg + head = PSAHead( + in_channels=4, channels=2, num_classes=19, mask_size=(13, 13)) + assert not _conv_has_norm(head, sync_bn=False) + + # test with norm_cfg + head = PSAHead( + in_channels=4, + channels=2, + num_classes=19, + mask_size=(13, 13), + norm_cfg=dict(type='SyncBN')) + assert _conv_has_norm(head, sync_bn=True) + + # test 'bi-direction' psa_type + inputs = [torch.randn(1, 4, 13, 13)] + head = PSAHead( + in_channels=4, channels=2, num_classes=19, mask_size=(13, 13)) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 13, 13) + + # test 'bi-direction' psa_type, shrink_factor=1 + inputs = [torch.randn(1, 4, 13, 13)] + head = PSAHead( + in_channels=4, + channels=2, + num_classes=19, + mask_size=(13, 13), + shrink_factor=1) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 13, 13) + + # test 'bi-direction' psa_type with soft_max + inputs = [torch.randn(1, 4, 13, 13)] + head = PSAHead( + in_channels=4, + channels=2, + num_classes=19, + mask_size=(13, 13), + psa_softmax=True) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 13, 13) + + # test 'collect' psa_type + inputs = [torch.randn(1, 4, 13, 13)] + head = PSAHead( + in_channels=4, + channels=2, + num_classes=19, + mask_size=(13, 13), + psa_type='collect') + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 13, 13) + + # test 'collect' psa_type, shrink_factor=1 + inputs = [torch.randn(1, 4, 13, 13)] + head = PSAHead( + in_channels=4, + channels=2, + num_classes=19, + mask_size=(13, 13), + shrink_factor=1, + psa_type='collect') + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 13, 13) + + # test 'collect' psa_type, shrink_factor=1, compact=True + inputs = [torch.randn(1, 4, 13, 13)] + head = PSAHead( + in_channels=4, + channels=2, + num_classes=19, + mask_size=(13, 13), + psa_type='collect', + shrink_factor=1, + compact=True) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 13, 13) + + # test 'distribute' psa_type + inputs = [torch.randn(1, 4, 13, 13)] + head = PSAHead( + in_channels=4, + channels=2, + num_classes=19, + mask_size=(13, 13), + psa_type='distribute') + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 13, 13) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_psp_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_psp_head.py new file mode 100644 index 0000000..fde4087 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_psp_head.py @@ -0,0 +1,36 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import PSPHead +from .utils import _conv_has_norm, to_cuda + + +def test_psp_head(): + + with pytest.raises(AssertionError): + # pool_scales must be list|tuple + PSPHead(in_channels=4, channels=2, num_classes=19, pool_scales=1) + + # test no norm_cfg + head = PSPHead(in_channels=4, channels=2, num_classes=19) + assert not _conv_has_norm(head, sync_bn=False) + + # test with norm_cfg + head = PSPHead( + in_channels=4, + channels=2, + num_classes=19, + norm_cfg=dict(type='SyncBN')) + assert _conv_has_norm(head, sync_bn=True) + + inputs = [torch.randn(1, 4, 23, 23)] + head = PSPHead( + in_channels=4, channels=2, num_classes=19, pool_scales=(1, 2, 3)) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + assert head.psp_modules[0][0].output_size == 1 + assert head.psp_modules[1][0].output_size == 2 + assert head.psp_modules[2][0].output_size == 3 + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 23, 23) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_san_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_san_head.py new file mode 100644 index 0000000..af85a6e --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_san_head.py @@ -0,0 +1,126 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine import Config +from mmengine.structures import PixelData + +from mmseg.models.decode_heads import SideAdapterCLIPHead +from mmseg.structures import SegDataSample +from .utils import list_to_cuda + + +def test_san_head(): + H, W = (64, 64) + clip_channels = 64 + img_channels = 4 + num_queries = 40 + out_dims = 64 + num_classes = 19 + cfg = dict( + num_classes=num_classes, + deep_supervision_idxs=[4], + san_cfg=dict( + in_channels=img_channels, + embed_dims=128, + clip_channels=clip_channels, + num_queries=num_queries, + cfg_encoder=dict(num_encode_layer=4, mlp_ratio=2, num_heads=2), + cfg_decoder=dict( + num_heads=4, + num_layers=1, + embed_channels=32, + mlp_channels=32, + num_mlp=2, + rescale=True)), + maskgen_cfg=dict( + sos_token_num=num_queries, + embed_dims=clip_channels, + out_dims=out_dims, + num_heads=4, + mlp_ratio=2), + train_cfg=dict( + num_points=100, + oversample_ratio=3.0, + importance_sample_ratio=0.75, + assigner=dict( + type='HungarianAssigner', + match_costs=[ + dict(type='ClassificationCost', weight=2.0), + dict( + type='CrossEntropyLossCost', + weight=5.0, + use_sigmoid=True), + dict(type='DiceCost', weight=5.0, pred_act=True, eps=1.0) + ])), + loss_decode=[ + dict( + type='CrossEntropyLoss', + loss_name='loss_cls_ce', + loss_weight=2.0, + class_weight=[1.0] * num_classes + [0.1]), + dict( + type='CrossEntropyLoss', + use_sigmoid=True, + loss_name='loss_mask_ce', + loss_weight=5.0), + dict( + type='DiceLoss', + ignore_index=None, + naive_dice=True, + eps=1, + loss_name='loss_mask_dice', + loss_weight=5.0) + ]) + + cfg = Config(cfg) + head = SideAdapterCLIPHead(**cfg) + + inputs = torch.rand((2, img_channels, H, W)) + clip_feature = [[ + torch.rand((2, clip_channels, H // 2, W // 2)), + torch.rand((2, clip_channels)) + ], + [ + torch.rand((2, clip_channels, H // 2, W // 2)), + torch.rand((2, clip_channels)) + ], + [ + torch.rand((2, clip_channels, H // 2, W // 2)), + torch.rand((2, clip_channels)) + ], + [ + torch.rand((2, clip_channels, H // 2, W // 2)), + torch.rand((2, clip_channels)) + ]] + class_embed = torch.rand((num_classes + 1, out_dims)) + + data_samples = [] + for i in range(2): + data_sample = SegDataSample() + img_meta = {} + img_meta['img_shape'] = (H, W) + img_meta['ori_shape'] = (H, W) + data_sample.gt_sem_seg = PixelData( + data=torch.randint(0, num_classes, (1, H, W))) + data_sample.set_metainfo(img_meta) + data_samples.append(data_sample) + + batch_img_metas = [] + for data_sample in data_samples: + batch_img_metas.append(data_sample.metainfo) + + if torch.cuda.is_available(): + head = head.cuda() + data = list_to_cuda([inputs, clip_feature, class_embed]) + for data_sample in data_samples: + data_sample.gt_sem_seg.data = data_sample.gt_sem_seg.data.cuda() + else: + data = [inputs, clip_feature, class_embed] + + # loss test + loss_dict = head.loss(data, data_samples, None) + assert isinstance(loss_dict, dict) + + # prediction test + with torch.no_grad(): + seg_logits = head.predict(data, batch_img_metas, None) + assert seg_logits.shape == torch.Size((2, num_classes, H, W)) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_segformer_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_segformer_head.py new file mode 100644 index 0000000..73afaba --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_segformer_head.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import SegformerHead + + +def test_segformer_head(): + with pytest.raises(AssertionError): + # `in_channels` must have same length as `in_index` + SegformerHead( + in_channels=(1, 2, 3), in_index=(0, 1), channels=5, num_classes=2) + + H, W = (64, 64) + in_channels = (32, 64, 160, 256) + shapes = [(H // 2**(i + 2), W // 2**(i + 2)) + for i in range(len(in_channels))] + model = SegformerHead( + in_channels=in_channels, + in_index=[0, 1, 2, 3], + channels=256, + num_classes=19) + + with pytest.raises(IndexError): + # in_index must match the input feature maps. + inputs = [ + torch.randn((1, in_channel, *shape)) + for in_channel, shape in zip(in_channels, shapes) + ][:3] + temp = model(inputs) + + # Normal Input + # ((1, 32, 16, 16), (1, 64, 8, 8), (1, 160, 4, 4), (1, 256, 2, 2) + inputs = [ + torch.randn((1, in_channel, *shape)) + for in_channel, shape in zip(in_channels, shapes) + ] + temp = model(inputs) + + assert temp.shape == (1, 19, H // 4, W // 4) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_segmenter_mask_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_segmenter_mask_head.py new file mode 100644 index 0000000..7b681ac --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_segmenter_mask_head.py @@ -0,0 +1,24 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.decode_heads import SegmenterMaskTransformerHead +from .utils import _conv_has_norm, to_cuda + + +def test_segmenter_mask_transformer_head(): + head = SegmenterMaskTransformerHead( + in_channels=2, + channels=2, + num_classes=150, + num_layers=2, + num_heads=3, + embed_dims=192, + dropout_ratio=0.0) + assert _conv_has_norm(head, sync_bn=True) + head.init_weights() + + inputs = [torch.randn(1, 2, 32, 32)] + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 32, 32) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_setr_mla_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_setr_mla_head.py new file mode 100644 index 0000000..301bc0b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_setr_mla_head.py @@ -0,0 +1,63 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import SETRMLAHead +from .utils import to_cuda + + +def test_setr_mla_head(capsys): + + with pytest.raises(AssertionError): + # MLA requires input multiple stage feature information. + SETRMLAHead(in_channels=8, channels=4, num_classes=19, in_index=1) + + with pytest.raises(AssertionError): + # multiple in_indexs requires multiple in_channels. + SETRMLAHead( + in_channels=8, channels=4, num_classes=19, in_index=(0, 1, 2, 3)) + + with pytest.raises(AssertionError): + # channels should be len(in_channels) * mla_channels + SETRMLAHead( + in_channels=(8, 8, 8, 8), + channels=8, + mla_channels=4, + in_index=(0, 1, 2, 3), + num_classes=19) + + # test inference of MLA head + img_size = (8, 8) + patch_size = 4 + head = SETRMLAHead( + in_channels=(8, 8, 8, 8), + channels=16, + mla_channels=4, + in_index=(0, 1, 2, 3), + num_classes=19, + norm_cfg=dict(type='BN')) + + h, w = img_size[0] // patch_size, img_size[1] // patch_size + # Input square NCHW format feature information + x = [ + torch.randn(1, 8, h, w), + torch.randn(1, 8, h, w), + torch.randn(1, 8, h, w), + torch.randn(1, 8, h, w) + ] + if torch.cuda.is_available(): + head, x = to_cuda(head, x) + out = head(x) + assert out.shape == (1, head.num_classes, h * 4, w * 4) + + # Input non-square NCHW format feature information + x = [ + torch.randn(1, 8, h, w * 2), + torch.randn(1, 8, h, w * 2), + torch.randn(1, 8, h, w * 2), + torch.randn(1, 8, h, w * 2) + ] + if torch.cuda.is_available(): + head, x = to_cuda(head, x) + out = head(x) + assert out.shape == (1, head.num_classes, h * 4, w * 8) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_setr_up_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_setr_up_head.py new file mode 100644 index 0000000..a051922 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_setr_up_head.py @@ -0,0 +1,56 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import SETRUPHead +from .utils import to_cuda + + +def test_setr_up_head(capsys): + + with pytest.raises(AssertionError): + # kernel_size must be [1/3] + SETRUPHead(num_classes=19, kernel_size=2) + + with pytest.raises(AssertionError): + # in_channels must be int type and in_channels must be same + # as embed_dim. + SETRUPHead(in_channels=(4, 4), channels=2, num_classes=19) + + # test init_cfg of head + head = SETRUPHead( + in_channels=4, + channels=2, + norm_cfg=dict(type='SyncBN'), + num_classes=19, + init_cfg=dict(type='Kaiming')) + super(SETRUPHead, head).init_weights() + + # test inference of Naive head + # the auxiliary head of Naive head is same as Naive head + img_size = (4, 4) + patch_size = 2 + head = SETRUPHead( + in_channels=4, + channels=2, + num_classes=19, + num_convs=1, + up_scale=4, + kernel_size=1, + norm_cfg=dict(type='BN')) + + h, w = img_size[0] // patch_size, img_size[1] // patch_size + + # Input square NCHW format feature information + x = [torch.randn(1, 4, h, w)] + if torch.cuda.is_available(): + head, x = to_cuda(head, x) + out = head(x) + assert out.shape == (1, head.num_classes, h * 4, w * 4) + + # Input non-square NCHW format feature information + x = [torch.randn(1, 4, h, w * 2)] + if torch.cuda.is_available(): + head, x = to_cuda(head, x) + out = head(x) + assert out.shape == (1, head.num_classes, h * 4, w * 8) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_uper_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_uper_head.py new file mode 100644 index 0000000..09456a8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_uper_head.py @@ -0,0 +1,35 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import UPerHead +from .utils import _conv_has_norm, to_cuda + + +def test_uper_head(): + + with pytest.raises(AssertionError): + # fpn_in_channels must be list|tuple + UPerHead(in_channels=4, channels=2, num_classes=19) + + # test no norm_cfg + head = UPerHead( + in_channels=[4, 2], channels=2, num_classes=19, in_index=[-2, -1]) + assert not _conv_has_norm(head, sync_bn=False) + + # test with norm_cfg + head = UPerHead( + in_channels=[4, 2], + channels=2, + num_classes=19, + norm_cfg=dict(type='SyncBN'), + in_index=[-2, -1]) + assert _conv_has_norm(head, sync_bn=True) + + inputs = [torch.randn(1, 4, 45, 45), torch.randn(1, 2, 21, 21)] + head = UPerHead( + in_channels=[4, 2], channels=2, num_classes=19, in_index=[-2, -1]) + if torch.cuda.is_available(): + head, inputs = to_cuda(head, inputs) + outputs = head(inputs) + assert outputs.shape == (1, head.num_classes, 45, 45) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_vpd_depth_head.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_vpd_depth_head.py new file mode 100644 index 0000000..e3a4f75 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/test_vpd_depth_head.py @@ -0,0 +1,50 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from unittest import TestCase + +import torch +from mmengine.structures import PixelData + +from mmseg.models.decode_heads import VPDDepthHead +from mmseg.structures import SegDataSample + + +class TestVPDDepthHead(TestCase): + + def setUp(self): + """Set up common resources.""" + self.in_channels = [320, 640, 1280, 1280] + self.max_depth = 10.0 + self.loss_decode = dict( + type='SiLogLoss' + ) # Replace with your actual loss type and parameters + self.vpd_depth_head = VPDDepthHead( + max_depth=self.max_depth, + in_channels=self.in_channels, + loss_decode=self.loss_decode) + + def test_forward(self): + """Test the forward method.""" + # Create a mock input tensor. Replace shape as per your needs. + x = [ + torch.randn(1, 320, 32, 32), + torch.randn(1, 640, 16, 16), + torch.randn(1, 1280, 8, 8), + torch.randn(1, 1280, 4, 4) + ] + + output = self.vpd_depth_head.forward(x) + print(output.shape) + + self.assertEqual(output.shape, (1, 1, 256, 256)) + + def test_loss_by_feat(self): + """Test the loss_by_feat method.""" + # Create mock data for `pred_depth_map` and `batch_data_samples`. + pred_depth_map = torch.randn(1, 1, 32, 32) + gt_depth_map = PixelData(data=torch.rand(1, 32, 32)) + batch_data_samples = [SegDataSample(gt_depth_map=gt_depth_map)] + + loss = self.vpd_depth_head.loss_by_feat(pred_depth_map, + batch_data_samples) + + self.assertIsNotNone(loss) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_heads/utils.py b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/utils.py new file mode 100644 index 0000000..7282340 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_heads/utils.py @@ -0,0 +1,31 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmcv.cnn import ConvModule +from mmengine.utils.dl_utils.parrots_wrapper import SyncBatchNorm + + +def _conv_has_norm(module, sync_bn): + for m in module.modules(): + if isinstance(m, ConvModule): + if not m.with_norm: + return False + if sync_bn: + if not isinstance(m.bn, SyncBatchNorm): + return False + return True + + +def to_cuda(module, data): + module = module.cuda() + if isinstance(data, list): + for i in range(len(data)): + data[i] = data[i].cuda() + return module, data + + +def list_to_cuda(data): + if isinstance(data, list): + for i in range(len(data)): + data[i] = list_to_cuda(data[i]) + return data + else: + return data.cuda() diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_cross_entropy_loss.py b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_cross_entropy_loss.py new file mode 100644 index 0000000..8c6b86d --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_cross_entropy_loss.py @@ -0,0 +1,28 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +import torch.nn.functional as F + +from mmseg.models.losses import CrossEntropyLoss, weight_reduce_loss + + +def test_cross_entropy_loss_class_weights(): + loss_class = CrossEntropyLoss + pred = torch.rand((1, 10, 4, 4)) + target = torch.randint(0, 10, (1, 4, 4)) + class_weight = torch.ones(10) + avg_factor = target.numel() + + cross_entropy_loss = F.cross_entropy( + pred, target, weight=class_weight, reduction='none', ignore_index=-100) + + expected_loss = weight_reduce_loss( + cross_entropy_loss, + weight=None, + reduction='mean', + avg_factor=avg_factor) + + # Test loss forward + loss = loss_class(class_weight=class_weight.tolist())(pred, target) + + assert isinstance(loss, torch.Tensor) + assert expected_loss == loss diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_dice_loss.py b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_dice_loss.py new file mode 100644 index 0000000..34253da --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_dice_loss.py @@ -0,0 +1,96 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.losses import DiceLoss + + +@pytest.mark.parametrize('naive_dice', [True, False]) +def test_dice_loss(naive_dice): + loss_class = DiceLoss + pred = torch.rand((1, 10, 4, 4)) + target = torch.randint(0, 10, (1, 4, 4)) + weight = torch.rand(1) + # Test loss forward + loss = loss_class(naive_dice=naive_dice)(pred, target) + assert isinstance(loss, torch.Tensor) + + # Test loss forward with weight + loss = loss_class(naive_dice=naive_dice)(pred, target, weight) + assert isinstance(loss, torch.Tensor) + + # Test loss forward with reduction_override + loss = loss_class(naive_dice=naive_dice)( + pred, target, reduction_override='mean') + assert isinstance(loss, torch.Tensor) + + # Test loss forward with avg_factor + loss = loss_class(naive_dice=naive_dice)(pred, target, avg_factor=10) + assert isinstance(loss, torch.Tensor) + + with pytest.raises(ValueError): + # loss can evaluate with avg_factor only if + # reduction is None, 'none' or 'mean'. + reduction_override = 'sum' + loss_class(naive_dice=naive_dice)( + pred, target, avg_factor=10, reduction_override=reduction_override) + + # Test loss forward with avg_factor and reduction + for reduction_override in [None, 'none', 'mean']: + loss_class(naive_dice=naive_dice)( + pred, target, avg_factor=10, reduction_override=reduction_override) + assert isinstance(loss, torch.Tensor) + + # Test loss forward with has_acted=False and use_sigmoid=False + for use_sigmoid in [True, False]: + loss_class( + use_sigmoid=use_sigmoid, activate=True, + naive_dice=naive_dice)(pred, target) + assert isinstance(loss, torch.Tensor) + + # Test loss forward with weight.ndim != loss.ndim + with pytest.raises(AssertionError): + weight = torch.rand((2, 8)) + loss_class(naive_dice=naive_dice)(pred, target, weight) + + # Test loss forward with len(weight) != len(pred) + with pytest.raises(AssertionError): + weight = torch.rand(8) + loss_class(naive_dice=naive_dice)(pred, target, weight) + + # Test _expand_onehot_labels_dice + pred = torch.tensor([[[[1, 1], [1, 0]], [[0, 1], [1, 1]]]]).float() + target = torch.tensor([[[0, 0], [0, 1]]]) + target_onehot = torch.tensor([[[[1, 1], [1, 0]], [[0, 0], [0, 1]]]]) + weight = torch.rand(1) + loss = loss_class(naive_dice=naive_dice)(pred, target, weight) + loss_onehot = loss_class(naive_dice=naive_dice)(pred, target_onehot, + weight) + assert torch.equal(loss, loss_onehot) + + # Test Whether Loss is 0 when pred == target, eps == 0 and naive_dice=False + target = torch.randint(0, 2, (1, 10, 4, 4)) + pred = target.float() + target = target.sigmoid() + weight = torch.rand(1) + loss = loss_class( + naive_dice=False, use_sigmoid=True, eps=0)(pred, target, weight) + assert loss.item() == 0 + + # Test ignore_index when ignore_index is the only class + with pytest.raises(AssertionError): + pred = torch.ones((1, 1, 4, 4)) + target = torch.randint(0, 1, (1, 4, 4)) + weight = torch.rand(1) + loss = loss_class( + naive_dice=naive_dice, use_sigmoid=False, ignore_index=0, + eps=0)(pred, target, weight) + + # Test ignore_index with naive_dice = False + pred = torch.tensor([[[[1, 1], [1, 0]], [[0, 1], [1, 1]]]]).float() + target = torch.tensor([[[[1, 1], [1, 0]], [[1, 0], [0, 1]]]]).sigmoid() + weight = torch.rand(1) + loss = loss_class( + naive_dice=False, use_sigmoid=True, ignore_index=1, + eps=0)(pred, target, weight) + assert loss.item() == 0 diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_huasdorff_distance_loss.py b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_huasdorff_distance_loss.py new file mode 100644 index 0000000..29c2732 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_huasdorff_distance_loss.py @@ -0,0 +1,29 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.losses import HuasdorffDisstanceLoss + + +def test_huasdorff_distance_loss(): + loss_class = HuasdorffDisstanceLoss + pred = torch.rand((10, 8, 6, 6)) + target = torch.rand((10, 6, 6)) + class_weight = torch.rand(8) + + # Test loss forward + loss = loss_class()(pred, target) + assert isinstance(loss, torch.Tensor) + + # Test loss forward with avg_factor + loss = loss_class()(pred, target, avg_factor=10) + assert isinstance(loss, torch.Tensor) + + # Test loss forward with avg_factor and reduction is None, 'sum' and 'mean' + for reduction in [None, 'sum', 'mean']: + loss = loss_class()(pred, target, avg_factor=10, reduction=reduction) + assert isinstance(loss, torch.Tensor) + + # Test loss forward with class_weight + with pytest.raises(AssertionError): + loss_class(class_weight=class_weight)(pred, target) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_kldiv_loss.py b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_kldiv_loss.py new file mode 100644 index 0000000..48bcc4b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_kldiv_loss.py @@ -0,0 +1,40 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.losses.kldiv_loss import KLDivLoss + + +def test_kldiv_loss_with_none_reduction(): + loss_class = KLDivLoss + pred = torch.rand((8, 5, 5)) + target = torch.rand((8, 5, 5)) + reduction = 'none' + + # Test loss forward + loss = loss_class(reduction=reduction)(pred, target) + assert isinstance(loss, torch.Tensor) + assert loss.shape == (8, 5, 5), f'{loss.shape}' + + +def test_kldiv_loss_with_mean_reduction(): + loss_class = KLDivLoss + pred = torch.rand((8, 5, 5)) + target = torch.rand((8, 5, 5)) + reduction = 'mean' + + # Test loss forward + loss = loss_class(reduction=reduction)(pred, target) + assert isinstance(loss, torch.Tensor) + assert loss.shape == (8, ), f'{loss.shape}' + + +def test_kldiv_loss_with_sum_reduction(): + loss_class = KLDivLoss + pred = torch.rand((8, 5, 5)) + target = torch.rand((8, 5, 5)) + reduction = 'sum' + + # Test loss forward + loss = loss_class(reduction=reduction)(pred, target) + assert isinstance(loss, torch.Tensor) + assert loss.shape == (8, ), f'{loss.shape}' diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_silog_loss.py b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_silog_loss.py new file mode 100644 index 0000000..022434b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_silog_loss.py @@ -0,0 +1,20 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from unittest import TestCase + +import torch + +from mmseg.models.losses import SiLogLoss + + +class TestSiLogLoss(TestCase): + + def test_SiLogLoss_forward(self): + pred = torch.tensor([[1.0, 2.0], [3.5, 4.0]], dtype=torch.float32) + target = torch.tensor([[0.0, 2.0], [3.0, 4.0]], dtype=torch.float32) + weight = torch.tensor([1.0, 0.5], dtype=torch.float32) + + loss_module = SiLogLoss() + loss = loss_module.forward(pred, target, weight) + + expected_loss = 0.02 + self.assertAlmostEqual(loss.item(), expected_loss, places=2) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_tversky_loss.py b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_tversky_loss.py new file mode 100644 index 0000000..c5c581d --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_losses/test_tversky_loss.py @@ -0,0 +1,77 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + + +def test_tversky_lose(): + from mmseg.models import build_loss + + # test alpha + beta != 1 + with pytest.raises(AssertionError): + loss_cfg = dict( + type='TverskyLoss', + class_weight=[1.0, 2.0, 3.0], + loss_weight=1.0, + alpha=0.4, + beta=0.7, + loss_name='loss_tversky') + tversky_loss = build_loss(loss_cfg) + logits = torch.rand(8, 3, 4, 4) + labels = (torch.rand(8, 4, 4) * 3).long() + tversky_loss(logits, labels, ignore_index=1) + + # test tversky loss + loss_cfg = dict( + type='TverskyLoss', + class_weight=[1.0, 2.0, 3.0], + loss_weight=1.0, + ignore_index=1, + loss_name='loss_tversky') + tversky_loss = build_loss(loss_cfg) + logits = torch.rand(8, 3, 4, 4) + labels = (torch.rand(8, 4, 4) * 3).long() + tversky_loss(logits, labels) + + # test loss with class weights from file + import os + import tempfile + + import mmengine + import numpy as np + tmp_file = tempfile.NamedTemporaryFile() + + mmengine.dump([1.0, 2.0, 3.0], f'{tmp_file.name}.pkl', + 'pkl') # from pkl file + loss_cfg = dict( + type='TverskyLoss', + class_weight=f'{tmp_file.name}.pkl', + loss_weight=1.0, + ignore_index=1, + loss_name='loss_tversky') + tversky_loss = build_loss(loss_cfg) + tversky_loss(logits, labels) + + np.save(f'{tmp_file.name}.npy', np.array([1.0, 2.0, 3.0])) # from npy file + loss_cfg = dict( + type='TverskyLoss', + class_weight=f'{tmp_file.name}.pkl', + loss_weight=1.0, + ignore_index=1, + loss_name='loss_tversky') + tversky_loss = build_loss(loss_cfg) + tversky_loss(logits, labels) + tmp_file.close() + os.remove(f'{tmp_file.name}.pkl') + os.remove(f'{tmp_file.name}.npy') + + # test tversky loss has name `loss_tversky` + loss_cfg = dict( + type='TverskyLoss', + smooth=2, + loss_weight=1.0, + ignore_index=1, + alpha=0.3, + beta=0.7, + loss_name='loss_tversky') + tversky_loss = build_loss(loss_cfg) + assert tversky_loss.loss_name == 'loss_tversky' diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_necks/__init__.py b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/__init__.py new file mode 100644 index 0000000..ef101fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/__init__.py @@ -0,0 +1 @@ +# Copyright (c) OpenMMLab. All rights reserved. diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_feature2pyramid.py b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_feature2pyramid.py new file mode 100644 index 0000000..44fd02c --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_feature2pyramid.py @@ -0,0 +1,38 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models import Feature2Pyramid + + +def test_feature2pyramid(): + # test + rescales = [4, 2, 1, 0.5] + embed_dim = 64 + inputs = [torch.randn(1, embed_dim, 32, 32) for i in range(len(rescales))] + + fpn = Feature2Pyramid( + embed_dim, rescales, norm_cfg=dict(type='BN', requires_grad=True)) + outputs = fpn(inputs) + assert outputs[0].shape == torch.Size([1, 64, 128, 128]) + assert outputs[1].shape == torch.Size([1, 64, 64, 64]) + assert outputs[2].shape == torch.Size([1, 64, 32, 32]) + assert outputs[3].shape == torch.Size([1, 64, 16, 16]) + + # test rescales = [2, 1, 0.5, 0.25] + rescales = [2, 1, 0.5, 0.25] + inputs = [torch.randn(1, embed_dim, 32, 32) for i in range(len(rescales))] + + fpn = Feature2Pyramid( + embed_dim, rescales, norm_cfg=dict(type='BN', requires_grad=True)) + outputs = fpn(inputs) + assert outputs[0].shape == torch.Size([1, 64, 64, 64]) + assert outputs[1].shape == torch.Size([1, 64, 32, 32]) + assert outputs[2].shape == torch.Size([1, 64, 16, 16]) + assert outputs[3].shape == torch.Size([1, 64, 8, 8]) + + # test rescales = [4, 2, 0.25, 0] + rescales = [4, 2, 0.25, 0] + with pytest.raises(KeyError): + fpn = Feature2Pyramid( + embed_dim, rescales, norm_cfg=dict(type='BN', requires_grad=True)) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_fpn.py b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_fpn.py new file mode 100644 index 0000000..c294006 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_fpn.py @@ -0,0 +1,30 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models import FPN + + +def test_fpn(): + in_channels = [64, 128, 256, 512] + inputs = [ + torch.randn(1, c, 56 // 2**i, 56 // 2**i) + for i, c in enumerate(in_channels) + ] + + fpn = FPN(in_channels, 64, len(in_channels)) + outputs = fpn(inputs) + assert outputs[0].shape == torch.Size([1, 64, 56, 56]) + assert outputs[1].shape == torch.Size([1, 64, 28, 28]) + assert outputs[2].shape == torch.Size([1, 64, 14, 14]) + assert outputs[3].shape == torch.Size([1, 64, 7, 7]) + + fpn = FPN( + in_channels, + 64, + len(in_channels), + upsample_cfg=dict(mode='nearest', scale_factor=2.0)) + outputs = fpn(inputs) + assert outputs[0].shape == torch.Size([1, 64, 56, 56]) + assert outputs[1].shape == torch.Size([1, 64, 28, 28]) + assert outputs[2].shape == torch.Size([1, 64, 14, 14]) + assert outputs[3].shape == torch.Size([1, 64, 7, 7]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_ic_neck.py b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_ic_neck.py new file mode 100644 index 0000000..3d13008 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_ic_neck.py @@ -0,0 +1,53 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.necks import ICNeck +from mmseg.models.necks.ic_neck import CascadeFeatureFusion +from ..test_heads.utils import _conv_has_norm, to_cuda + + +def test_ic_neck(): + # test with norm_cfg + neck = ICNeck( + in_channels=(4, 16, 16), + out_channels=8, + norm_cfg=dict(type='SyncBN'), + align_corners=False) + assert _conv_has_norm(neck, sync_bn=True) + + inputs = [ + torch.randn(1, 4, 32, 64), + torch.randn(1, 16, 16, 32), + torch.randn(1, 16, 8, 16) + ] + neck = ICNeck( + in_channels=(4, 16, 16), + out_channels=4, + norm_cfg=dict(type='BN', requires_grad=True), + align_corners=False) + if torch.cuda.is_available(): + neck, inputs = to_cuda(neck, inputs) + + outputs = neck(inputs) + assert outputs[0].shape == (1, 4, 16, 32) + assert outputs[1].shape == (1, 4, 32, 64) + assert outputs[1].shape == (1, 4, 32, 64) + + +def test_ic_neck_cascade_feature_fusion(): + cff = CascadeFeatureFusion(64, 64, 32) + assert cff.conv_low.in_channels == 64 + assert cff.conv_low.out_channels == 32 + assert cff.conv_high.in_channels == 64 + assert cff.conv_high.out_channels == 32 + + +def test_ic_neck_input_channels(): + with pytest.raises(AssertionError): + # ICNet Neck input channel constraints. + ICNeck( + in_channels=(16, 64, 64, 64), + out_channels=32, + norm_cfg=dict(type='BN', requires_grad=True), + align_corners=False) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_jpu.py b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_jpu.py new file mode 100644 index 0000000..4c3fa9f --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_jpu.py @@ -0,0 +1,46 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.necks import JPU + + +def test_fastfcn_neck(): + # Test FastFCN Standard Forward + model = JPU( + in_channels=(64, 128, 256), + mid_channels=64, + start_level=0, + end_level=-1, + dilations=(1, 2, 4, 8), + ) + model.init_weights() + model.train() + batch_size = 1 + input = [ + torch.randn(batch_size, 64, 64, 128), + torch.randn(batch_size, 128, 32, 64), + torch.randn(batch_size, 256, 16, 32) + ] + feat = model(input) + + assert len(feat) == 3 + assert feat[0].shape == torch.Size([batch_size, 64, 64, 128]) + assert feat[1].shape == torch.Size([batch_size, 128, 32, 64]) + assert feat[2].shape == torch.Size([batch_size, 256, 64, 128]) + + with pytest.raises(AssertionError): + # FastFCN input and in_channels constraints. + JPU(in_channels=(256, 64, 128), start_level=0, end_level=5) + + # Test not default start_level + model = JPU(in_channels=(64, 128, 256), start_level=1, end_level=-1) + input = [ + torch.randn(batch_size, 64, 64, 128), + torch.randn(batch_size, 128, 32, 64), + torch.randn(batch_size, 256, 16, 32) + ] + feat = model(input) + assert len(feat) == 2 + assert feat[0].shape == torch.Size([batch_size, 128, 32, 64]) + assert feat[1].shape == torch.Size([batch_size, 2048, 32, 64]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_mla_neck.py b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_mla_neck.py new file mode 100644 index 0000000..e385418 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_mla_neck.py @@ -0,0 +1,16 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models import MLANeck + + +def test_mla(): + in_channels = [4, 4, 4, 4] + mla = MLANeck(in_channels, 32) + + inputs = [torch.randn(1, c, 12, 12) for i, c in enumerate(in_channels)] + outputs = mla(inputs) + assert outputs[0].shape == torch.Size([1, 32, 12, 12]) + assert outputs[1].shape == torch.Size([1, 32, 12, 12]) + assert outputs[2].shape == torch.Size([1, 32, 12, 12]) + assert outputs[3].shape == torch.Size([1, 32, 12, 12]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_multilevel_neck.py b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_multilevel_neck.py new file mode 100644 index 0000000..9c71d51 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_necks/test_multilevel_neck.py @@ -0,0 +1,32 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models import MultiLevelNeck + + +def test_multilevel_neck(): + + # Test init_weights + MultiLevelNeck([266], 32).init_weights() + + # Test multi feature maps + in_channels = [32, 64, 128, 256] + inputs = [torch.randn(1, c, 14, 14) for i, c in enumerate(in_channels)] + + neck = MultiLevelNeck(in_channels, 32) + outputs = neck(inputs) + assert outputs[0].shape == torch.Size([1, 32, 7, 7]) + assert outputs[1].shape == torch.Size([1, 32, 14, 14]) + assert outputs[2].shape == torch.Size([1, 32, 28, 28]) + assert outputs[3].shape == torch.Size([1, 32, 56, 56]) + + # Test one feature map + in_channels = [768] + inputs = [torch.randn(1, 768, 14, 14)] + + neck = MultiLevelNeck(in_channels, 32) + outputs = neck(inputs) + assert outputs[0].shape == torch.Size([1, 32, 7, 7]) + assert outputs[1].shape == torch.Size([1, 32, 14, 14]) + assert outputs[2].shape == torch.Size([1, 32, 28, 28]) + assert outputs[3].shape == torch.Size([1, 32, 56, 56]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/__init__.py b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/__init__.py new file mode 100644 index 0000000..ef101fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/__init__.py @@ -0,0 +1 @@ +# Copyright (c) OpenMMLab. All rights reserved. diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_cascade_encoder_decoder.py b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_cascade_encoder_decoder.py new file mode 100644 index 0000000..941816d --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_cascade_encoder_decoder.py @@ -0,0 +1,57 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine import ConfigDict + +from mmseg.models import build_segmentor +from .utils import _segmentor_forward_train_test + + +def test_cascade_encoder_decoder(): + + # test 1 decode head, w.o. aux head + cfg = ConfigDict( + type='CascadeEncoderDecoder', + num_stages=2, + backbone=dict(type='ExampleBackbone'), + decode_head=[ + dict(type='ExampleDecodeHead'), + dict(type='ExampleCascadeDecodeHead') + ]) + cfg.test_cfg = ConfigDict(mode='whole') + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) + + # test slide mode + cfg.test_cfg = ConfigDict(mode='slide', crop_size=(3, 3), stride=(2, 2)) + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) + + # test 1 decode head, 1 aux head + cfg = ConfigDict( + type='CascadeEncoderDecoder', + num_stages=2, + backbone=dict(type='ExampleBackbone'), + decode_head=[ + dict(type='ExampleDecodeHead'), + dict(type='ExampleCascadeDecodeHead') + ], + auxiliary_head=dict(type='ExampleDecodeHead')) + cfg.test_cfg = ConfigDict(mode='whole') + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) + + # test 1 decode head, 2 aux head + cfg = ConfigDict( + type='CascadeEncoderDecoder', + num_stages=2, + backbone=dict(type='ExampleBackbone'), + decode_head=[ + dict(type='ExampleDecodeHead'), + dict(type='ExampleCascadeDecodeHead') + ], + auxiliary_head=[ + dict(type='ExampleDecodeHead'), + dict(type='ExampleDecodeHead') + ]) + cfg.test_cfg = ConfigDict(mode='whole') + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_depth_estimator.py b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_depth_estimator.py new file mode 100644 index 0000000..e819c9e --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_depth_estimator.py @@ -0,0 +1,64 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from copy import deepcopy +from os.path import dirname, join +from unittest import TestCase + +import torch +from mmengine import Config, ConfigDict +from mmengine.structures import PixelData + +import mmseg +from mmseg.models.segmentors import DepthEstimator +from mmseg.structures import SegDataSample + + +class TestDepthEstimator(TestCase): + + def setUp(self) -> None: + repo_dpath = dirname(dirname(mmseg.__file__)) + config_dpath = join(repo_dpath, 'configs/_base_/models/vpd_sd.py') + vpd_cfg = Config.fromfile(config_dpath).stable_diffusion_cfg + vpd_cfg.pop('checkpoint') + + backbone_cfg = dict( + type='VPD', + diffusion_cfg=vpd_cfg, + class_embed_path='https://download.openmmlab.com/mmsegmentation/' + 'v0.5/vpd/nyu_class_embeddings.pth', + class_embed_select=True, + pad_shape=64, + unet_cfg=dict(use_attn=False), + ) + + head_cfg = dict( + type='VPDDepthHead', + max_depth=10, + ) + + self.model = DepthEstimator( + backbone=backbone_cfg, decode_head=head_cfg) + + inputs = torch.randn(1, 3, 64, 80) + data_sample = SegDataSample() + data_sample.gt_depth_map = PixelData(data=torch.rand(1, 64, 80)) + data_sample.set_metainfo(dict(img_shape=(64, 80), ori_shape=(64, 80))) + self.data = dict(inputs=inputs, data_samples=[data_sample]) + + def test_slide_flip_inference(self): + + self.model.test_cfg = ConfigDict( + dict(mode='slide_flip', crop_size=(64, 64), stride=(16, 16))) + + with torch.no_grad(): + out = self.model.predict(**deepcopy(self.data)) + + self.assertEqual(len(out), 1) + self.assertIn('pred_depth_map', out[0].keys()) + self.assertListEqual(list(out[0].pred_depth_map.shape), [64, 80]) + + def test__forward(self): + data = deepcopy(self.data) + data['inputs'] = data['inputs'][:, :, :64, :64] + with torch.no_grad(): + out = self.model._forward(**data) + self.assertListEqual(list(out.shape), [1, 1, 64, 64]) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_encoder_decoder.py b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_encoder_decoder.py new file mode 100644 index 0000000..5795f51 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_encoder_decoder.py @@ -0,0 +1,100 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine import ConfigDict +from mmengine.structures import PixelData + +from mmseg.models import build_segmentor +from mmseg.structures import SegDataSample +from .utils import _segmentor_forward_train_test + + +def test_encoder_decoder(): + + # test 1 decode head, w.o. aux head + + cfg = ConfigDict( + type='EncoderDecoder', + backbone=dict(type='ExampleBackbone'), + decode_head=dict(type='ExampleDecodeHead'), + train_cfg=None, + test_cfg=dict(mode='whole')) + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) + + # test out_channels == 1 + cfg = ConfigDict( + type='EncoderDecoder', + backbone=dict(type='ExampleBackbone'), + decode_head=dict( + type='ExampleDecodeHead', num_classes=2, out_channels=1), + train_cfg=None, + test_cfg=dict(mode='whole')) + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) + + # test slide mode + cfg.test_cfg = ConfigDict(mode='slide', crop_size=(3, 3), stride=(2, 2)) + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) + + # test 1 decode head, 1 aux head + cfg = ConfigDict( + type='EncoderDecoder', + backbone=dict(type='ExampleBackbone'), + decode_head=dict(type='ExampleDecodeHead'), + auxiliary_head=dict(type='ExampleDecodeHead')) + cfg.test_cfg = ConfigDict(mode='whole') + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) + + # test 1 decode head, 2 aux head + cfg = ConfigDict( + type='EncoderDecoder', + backbone=dict(type='ExampleBackbone'), + decode_head=dict(type='ExampleDecodeHead'), + auxiliary_head=[ + dict(type='ExampleDecodeHead'), + dict(type='ExampleDecodeHead') + ]) + cfg.test_cfg = ConfigDict(mode='whole') + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) + + +def test_postprocess_result(): + cfg = ConfigDict( + type='EncoderDecoder', + backbone=dict(type='ExampleBackbone'), + decode_head=dict(type='ExampleDecodeHead'), + train_cfg=None, + test_cfg=dict(mode='whole')) + model = build_segmentor(cfg) + + # test postprocess + data_sample = SegDataSample() + data_sample.gt_sem_seg = PixelData( + **{'data': torch.randint(0, 10, (1, 8, 8))}) + data_sample.set_metainfo({ + 'padding_size': (0, 2, 0, 2), + 'ori_shape': (8, 8) + }) + seg_logits = torch.zeros((1, 2, 10, 10)) + seg_logits[:, :, :8, :8] = 1 + data_samples = [data_sample] + + outputs = model.postprocess_result(seg_logits, data_samples) + assert outputs[0].seg_logits.data.shape == torch.Size((2, 8, 8)) + assert torch.allclose(outputs[0].seg_logits.data, torch.ones((2, 8, 8))) + + data_sample = SegDataSample() + data_sample.gt_sem_seg = PixelData( + **{'data': torch.randint(0, 10, (1, 8, 8))}) + data_sample.set_metainfo({ + 'img_padding_size': (0, 2, 0, 2), + 'ori_shape': (8, 8) + }) + + data_samples = [data_sample] + outputs = model.postprocess_result(seg_logits, data_samples) + assert outputs[0].seg_logits.data.shape == torch.Size((2, 8, 8)) + assert torch.allclose(outputs[0].seg_logits.data, torch.ones((2, 8, 8))) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_multimodal_encoder_decoder.py b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_multimodal_encoder_decoder.py new file mode 100644 index 0000000..75258d8 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_multimodal_encoder_decoder.py @@ -0,0 +1,24 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from mmengine import ConfigDict + +from mmseg.models import build_segmentor +from tests.test_models.test_segmentors.utils import \ + _segmentor_forward_train_test + + +def test_multimodal_encoder_decoder(): + + cfg = ConfigDict( + type='MultimodalEncoderDecoder', + asymetric_input=False, + image_encoder=dict(type='ExampleBackbone', out_indices=[1, 2, 3, 4]), + text_encoder=dict( + type='ExampleTextEncoder', + vocabulary=['A', 'B', 'C'], + output_dims=3), + decode_head=dict( + type='ExampleDecodeHead', out_channels=1, num_classes=2), + train_cfg=None, + test_cfg=dict(mode='whole')) + segmentor = build_segmentor(cfg) + _segmentor_forward_train_test(segmentor) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_seg_tta_model.py b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_seg_tta_model.py new file mode 100644 index 0000000..1e152ed --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/test_seg_tta_model.py @@ -0,0 +1,63 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import tempfile + +import torch +from mmengine import ConfigDict +from mmengine.model import BaseTTAModel +from mmengine.registry import init_default_scope +from mmengine.structures import PixelData + +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample +from .utils import * # noqa: F401,F403 + +init_default_scope('mmseg') + + +def test_encoder_decoder_tta(): + + segmentor_cfg = ConfigDict( + type='EncoderDecoder', + backbone=dict(type='ExampleBackbone'), + decode_head=dict(type='ExampleDecodeHead'), + train_cfg=None, + test_cfg=dict(mode='whole')) + + cfg = ConfigDict(type='SegTTAModel', module=segmentor_cfg) + + model: BaseTTAModel = MODELS.build(cfg) + + imgs = [] + data_samples = [] + directions = ['horizontal', 'vertical'] + for i in range(12): + flip_direction = directions[0] if i % 3 == 0 else directions[1] + imgs.append(torch.randn(1, 3, 10 + i, 10 + i)) + data_samples.append([ + SegDataSample( + metainfo=dict( + ori_shape=(10, 10), + img_shape=(10 + i, 10 + i), + flip=(i % 2 == 0), + flip_direction=flip_direction, + img_path=tempfile.mktemp()), + gt_sem_seg=PixelData(data=torch.randint(0, 19, (1, 10, 10)))) + ]) + + model.test_step(dict(inputs=imgs, data_samples=data_samples)) + + # test out_channels == 1 + segmentor_cfg = ConfigDict( + type='EncoderDecoder', + backbone=dict(type='ExampleBackbone'), + decode_head=dict( + type='ExampleDecodeHead', + num_classes=2, + out_channels=1, + threshold=0.4), + train_cfg=None, + test_cfg=dict(mode='whole')) + model.module = MODELS.build(segmentor_cfg) + for data_sample in data_samples: + data_sample[0].gt_sem_seg.data = torch.randint(0, 2, (1, 10, 10)) + model.test_step(dict(inputs=imgs, data_samples=data_samples)) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/utils.py b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/utils.py new file mode 100644 index 0000000..ac31e2b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_segmentors/utils.py @@ -0,0 +1,182 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch +from mmengine.optim import OptimWrapper +from mmengine.structures import PixelData +from torch import nn +from torch.optim import SGD + +from mmseg.models import SegDataPreProcessor +from mmseg.models.decode_heads.cascade_decode_head import BaseCascadeDecodeHead +from mmseg.models.decode_heads.decode_head import BaseDecodeHead +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample + + +def _demo_mm_inputs(input_shape=(1, 3, 8, 16), num_classes=10): + """Create a superset of inputs needed to run test or train batches. + + Args: + input_shape (tuple): + input batch dimensions + + num_classes (int): + number of semantic classes + """ + (N, C, H, W) = input_shape + + imgs = torch.randn(*input_shape) + segs = torch.randint( + low=0, high=num_classes - 1, size=(N, H, W), dtype=torch.long) + + img_metas = [{ + 'img_shape': (H, W), + 'ori_shape': (H, W), + 'pad_shape': (H, W, C), + 'filename': '.png', + 'scale_factor': 1.0, + 'flip': False, + 'flip_direction': 'horizontal' + } for _ in range(N)] + + data_samples = [ + SegDataSample( + gt_sem_seg=PixelData(data=segs[i]), metainfo=img_metas[i]) + for i in range(N) + ] + + mm_inputs = {'imgs': torch.FloatTensor(imgs), 'data_samples': data_samples} + + return mm_inputs + + +@MODELS.register_module() +class ExampleBackbone(nn.Module): + + def __init__(self, out_indices=None): + super().__init__() + self.conv = nn.Conv2d(3, 3, 3) + self.out_indices = out_indices + + def init_weights(self, pretrained=None): + pass + + def forward(self, x): + if self.out_indices is None: + return [self.conv(x)] + else: + outs = [] + for i in self.out_indices: + outs.append(self.conv(x)) + return outs + + +@MODELS.register_module() +class ExampleDecodeHead(BaseDecodeHead): + + def __init__(self, num_classes=19, out_channels=None, **kwargs): + super().__init__( + 3, 3, num_classes=num_classes, out_channels=out_channels, **kwargs) + + def forward(self, inputs): + return self.cls_seg(inputs[0]) + + +@MODELS.register_module() +class ExampleTextEncoder(nn.Module): + + def __init__(self, vocabulary=None, output_dims=None): + super().__init__() + self.vocabulary = vocabulary + self.output_dims = output_dims + + def forward(self): + return torch.randn((len(self.vocabulary), self.output_dims)) + + +@MODELS.register_module() +class ExampleCascadeDecodeHead(BaseCascadeDecodeHead): + + def __init__(self): + super().__init__(3, 3, num_classes=19) + + def forward(self, inputs, prev_out): + return self.cls_seg(inputs[0]) + + +def _segmentor_forward_train_test(segmentor): + if isinstance(segmentor.decode_head, nn.ModuleList): + num_classes = segmentor.decode_head[-1].num_classes + else: + num_classes = segmentor.decode_head.num_classes + # batch_size=2 for BatchNorm + mm_inputs = _demo_mm_inputs(num_classes=num_classes) + + # convert to cuda Tensor if applicable + if torch.cuda.is_available(): + segmentor = segmentor.cuda() + + # check data preprocessor + if not hasattr(segmentor, + 'data_preprocessor') or segmentor.data_preprocessor is None: + segmentor.data_preprocessor = SegDataPreProcessor() + + mm_inputs = segmentor.data_preprocessor(mm_inputs, True) + imgs = mm_inputs.pop('imgs') + data_samples = mm_inputs.pop('data_samples') + + # create optimizer wrapper + optimizer = SGD(segmentor.parameters(), lr=0.1) + optim_wrapper = OptimWrapper(optimizer) + + # Test forward train + losses = segmentor.forward(imgs, data_samples, mode='loss') + assert isinstance(losses, dict) + + # Test train_step + data_batch = dict(inputs=imgs, data_samples=data_samples) + outputs = segmentor.train_step(data_batch, optim_wrapper) + assert isinstance(outputs, dict) + assert 'loss' in outputs + + # Test val_step + with torch.no_grad(): + segmentor.eval() + data_batch = dict(inputs=imgs, data_samples=data_samples) + outputs = segmentor.val_step(data_batch) + assert isinstance(outputs, list) + + # Test forward simple test + with torch.no_grad(): + segmentor.eval() + data_batch = dict(inputs=imgs, data_samples=data_samples) + results = segmentor.forward(imgs, data_samples, mode='tensor') + assert isinstance(results, torch.Tensor) + + +def _segmentor_predict(segmentor): + if isinstance(segmentor.decode_head, nn.ModuleList): + num_classes = segmentor.decode_head[-1].num_classes + else: + num_classes = segmentor.decode_head.num_classes + # batch_size=2 for BatchNorm + mm_inputs = _demo_mm_inputs(num_classes=num_classes) + + # convert to cuda Tensor if applicable + if torch.cuda.is_available(): + segmentor = segmentor.cuda() + + # check data preprocessor + if not hasattr(segmentor, + 'data_preprocessor') or segmentor.data_preprocessor is None: + segmentor.data_preprocessor = SegDataPreProcessor() + + mm_inputs = segmentor.data_preprocessor(mm_inputs, True) + imgs = mm_inputs.pop('imgs') + data_samples = mm_inputs.pop('data_samples') + + # Test predict + with torch.no_grad(): + segmentor.eval() + data_batch = dict(inputs=imgs, data_samples=data_samples) + outputs = segmentor.predict(**data_batch) + assert isinstance(outputs, list) diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_utils/__init__.py b/Seg_All_In_One_MMSeg/tests/test_models/test_utils/__init__.py new file mode 100644 index 0000000..ef101fe --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_utils/__init__.py @@ -0,0 +1 @@ +# Copyright (c) OpenMMLab. All rights reserved. diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_utils/test_embed.py b/Seg_All_In_One_MMSeg/tests/test_models/test_utils/test_embed.py new file mode 100644 index 0000000..be20c97 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_utils/test_embed.py @@ -0,0 +1,461 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.utils.embed import AdaptivePadding, PatchEmbed, PatchMerging + + +def test_adaptive_padding(): + + for padding in ('same', 'corner'): + kernel_size = 16 + stride = 16 + dilation = 1 + input = torch.rand(1, 1, 15, 17) + adap_pool = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + out = adap_pool(input) + # padding to divisible by 16 + assert (out.shape[2], out.shape[3]) == (16, 32) + input = torch.rand(1, 1, 16, 17) + out = adap_pool(input) + # padding to divisible by 16 + assert (out.shape[2], out.shape[3]) == (16, 32) + + kernel_size = (2, 2) + stride = (2, 2) + dilation = (1, 1) + + adap_pad = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + input = torch.rand(1, 1, 11, 13) + out = adap_pad(input) + # padding to divisible by 2 + assert (out.shape[2], out.shape[3]) == (12, 14) + + kernel_size = (2, 2) + stride = (10, 10) + dilation = (1, 1) + + adap_pad = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + input = torch.rand(1, 1, 10, 13) + out = adap_pad(input) + # no padding + assert (out.shape[2], out.shape[3]) == (10, 13) + + kernel_size = (11, 11) + adap_pad = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + input = torch.rand(1, 1, 11, 13) + out = adap_pad(input) + # all padding + assert (out.shape[2], out.shape[3]) == (21, 21) + + # test padding as kernel is (7,9) + input = torch.rand(1, 1, 11, 13) + stride = (3, 4) + kernel_size = (4, 5) + dilation = (2, 2) + # actually (7, 9) + adap_pad = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + dilation_out = adap_pad(input) + assert (dilation_out.shape[2], dilation_out.shape[3]) == (16, 21) + kernel_size = (7, 9) + dilation = (1, 1) + adap_pad = AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding) + kernel79_out = adap_pad(input) + assert (kernel79_out.shape[2], kernel79_out.shape[3]) == (16, 21) + assert kernel79_out.shape == dilation_out.shape + + # assert only support "same" "corner" + with pytest.raises(AssertionError): + AdaptivePadding( + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=1) + + +def test_patch_embed(): + B = 2 + H = 3 + W = 4 + C = 3 + embed_dims = 10 + kernel_size = 3 + stride = 1 + dummy_input = torch.rand(B, C, H, W) + patch_merge_1 = PatchEmbed( + in_channels=C, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=0, + dilation=1, + norm_cfg=None) + + x1, shape = patch_merge_1(dummy_input) + # test out shape + assert x1.shape == (2, 2, 10) + # test outsize is correct + assert shape == (1, 2) + # test L = out_h * out_w + assert shape[0] * shape[1] == x1.shape[1] + + B = 2 + H = 10 + W = 10 + C = 3 + embed_dims = 10 + kernel_size = 5 + stride = 2 + dummy_input = torch.rand(B, C, H, W) + # test dilation + patch_merge_2 = PatchEmbed( + in_channels=C, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=0, + dilation=2, + norm_cfg=None, + ) + + x2, shape = patch_merge_2(dummy_input) + # test out shape + assert x2.shape == (2, 1, 10) + # test outsize is correct + assert shape == (1, 1) + # test L = out_h * out_w + assert shape[0] * shape[1] == x2.shape[1] + + stride = 2 + input_size = (10, 10) + + dummy_input = torch.rand(B, C, H, W) + # test stride and norm + patch_merge_3 = PatchEmbed( + in_channels=C, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=0, + dilation=2, + norm_cfg=dict(type='LN'), + input_size=input_size) + + x3, shape = patch_merge_3(dummy_input) + # test out shape + assert x3.shape == (2, 1, 10) + # test outsize is correct + assert shape == (1, 1) + # test L = out_h * out_w + assert shape[0] * shape[1] == x3.shape[1] + + # test the init_out_size with nn.Unfold + assert patch_merge_3.init_out_size[1] == (input_size[0] - 2 * 4 - + 1) // 2 + 1 + assert patch_merge_3.init_out_size[0] == (input_size[0] - 2 * 4 - + 1) // 2 + 1 + H = 11 + W = 12 + input_size = (H, W) + dummy_input = torch.rand(B, C, H, W) + # test stride and norm + patch_merge_3 = PatchEmbed( + in_channels=C, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=0, + dilation=2, + norm_cfg=dict(type='LN'), + input_size=input_size) + + _, shape = patch_merge_3(dummy_input) + # when input_size equal to real input + # the out_size should be equal to `init_out_size` + assert shape == patch_merge_3.init_out_size + + input_size = (H, W) + dummy_input = torch.rand(B, C, H, W) + # test stride and norm + patch_merge_3 = PatchEmbed( + in_channels=C, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=0, + dilation=2, + norm_cfg=dict(type='LN'), + input_size=input_size) + + _, shape = patch_merge_3(dummy_input) + # when input_size equal to real input + # the out_size should be equal to `init_out_size` + assert shape == patch_merge_3.init_out_size + + # test adap padding + for padding in ('same', 'corner'): + in_c = 2 + embed_dims = 3 + B = 2 + + # test stride is 1 + input_size = (5, 5) + kernel_size = (5, 5) + stride = (1, 1) + dilation = 1 + bias = False + + x = torch.rand(B, in_c, *input_size) + patch_embed = PatchEmbed( + in_channels=in_c, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + x_out, out_size = patch_embed(x) + assert x_out.size() == (B, 25, 3) + assert out_size == (5, 5) + assert x_out.size(1) == out_size[0] * out_size[1] + + # test kernel_size == stride + input_size = (5, 5) + kernel_size = (5, 5) + stride = (5, 5) + dilation = 1 + bias = False + + x = torch.rand(B, in_c, *input_size) + patch_embed = PatchEmbed( + in_channels=in_c, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + x_out, out_size = patch_embed(x) + assert x_out.size() == (B, 1, 3) + assert out_size == (1, 1) + assert x_out.size(1) == out_size[0] * out_size[1] + + # test kernel_size == stride + input_size = (6, 5) + kernel_size = (5, 5) + stride = (5, 5) + dilation = 1 + bias = False + + x = torch.rand(B, in_c, *input_size) + patch_embed = PatchEmbed( + in_channels=in_c, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + x_out, out_size = patch_embed(x) + assert x_out.size() == (B, 2, 3) + assert out_size == (2, 1) + assert x_out.size(1) == out_size[0] * out_size[1] + + # test different kernel_size with different stride + input_size = (6, 5) + kernel_size = (6, 2) + stride = (6, 2) + dilation = 1 + bias = False + + x = torch.rand(B, in_c, *input_size) + patch_embed = PatchEmbed( + in_channels=in_c, + embed_dims=embed_dims, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + x_out, out_size = patch_embed(x) + assert x_out.size() == (B, 3, 3) + assert out_size == (1, 3) + assert x_out.size(1) == out_size[0] * out_size[1] + + +def test_patch_merging(): + + # Test the model with int padding + in_c = 3 + out_c = 4 + kernel_size = 3 + stride = 3 + padding = 1 + dilation = 1 + bias = False + # test the case `pad_to_stride` is False + patch_merge = PatchMerging( + in_channels=in_c, + out_channels=out_c, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + B, L, C = 1, 100, 3 + input_size = (10, 10) + x = torch.rand(B, L, C) + x_out, out_size = patch_merge(x, input_size) + assert x_out.size() == (1, 16, 4) + assert out_size == (4, 4) + # assert out size is consistent with real output + assert x_out.size(1) == out_size[0] * out_size[1] + in_c = 4 + out_c = 5 + kernel_size = 6 + stride = 3 + padding = 2 + dilation = 2 + bias = False + patch_merge = PatchMerging( + in_channels=in_c, + out_channels=out_c, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + B, L, C = 1, 100, 4 + input_size = (10, 10) + x = torch.rand(B, L, C) + x_out, out_size = patch_merge(x, input_size) + assert x_out.size() == (1, 4, 5) + assert out_size == (2, 2) + # assert out size is consistent with real output + assert x_out.size(1) == out_size[0] * out_size[1] + + # Test with adaptive padding + for padding in ('same', 'corner'): + in_c = 2 + out_c = 3 + B = 2 + + # test stride is 1 + input_size = (5, 5) + kernel_size = (5, 5) + stride = (1, 1) + dilation = 1 + bias = False + L = input_size[0] * input_size[1] + + x = torch.rand(B, L, in_c) + patch_merge = PatchMerging( + in_channels=in_c, + out_channels=out_c, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + x_out, out_size = patch_merge(x, input_size) + assert x_out.size() == (B, 25, 3) + assert out_size == (5, 5) + assert x_out.size(1) == out_size[0] * out_size[1] + + # test kernel_size == stride + input_size = (5, 5) + kernel_size = (5, 5) + stride = (5, 5) + dilation = 1 + bias = False + L = input_size[0] * input_size[1] + + x = torch.rand(B, L, in_c) + patch_merge = PatchMerging( + in_channels=in_c, + out_channels=out_c, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + x_out, out_size = patch_merge(x, input_size) + assert x_out.size() == (B, 1, 3) + assert out_size == (1, 1) + assert x_out.size(1) == out_size[0] * out_size[1] + + # test kernel_size == stride + input_size = (6, 5) + kernel_size = (5, 5) + stride = (5, 5) + dilation = 1 + bias = False + L = input_size[0] * input_size[1] + + x = torch.rand(B, L, in_c) + patch_merge = PatchMerging( + in_channels=in_c, + out_channels=out_c, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + x_out, out_size = patch_merge(x, input_size) + assert x_out.size() == (B, 2, 3) + assert out_size == (2, 1) + assert x_out.size(1) == out_size[0] * out_size[1] + + # test different kernel_size with different stride + input_size = (6, 5) + kernel_size = (6, 2) + stride = (6, 2) + dilation = 1 + bias = False + L = input_size[0] * input_size[1] + + x = torch.rand(B, L, in_c) + patch_merge = PatchMerging( + in_channels=in_c, + out_channels=out_c, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias) + + x_out, out_size = patch_merge(x, input_size) + assert x_out.size() == (B, 3, 3) + assert out_size == (1, 3) + assert x_out.size(1) == out_size[0] * out_size[1] diff --git a/Seg_All_In_One_MMSeg/tests/test_models/test_utils/test_shape_convert.py b/Seg_All_In_One_MMSeg/tests/test_models/test_utils/test_shape_convert.py new file mode 100644 index 0000000..60e87f3 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_models/test_utils/test_shape_convert.py @@ -0,0 +1,89 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import torch + +from mmseg.models.utils import (nchw2nlc2nchw, nchw_to_nlc, nlc2nchw2nlc, + nlc_to_nchw) + + +def test_nchw2nlc2nchw(): + # Test nchw2nlc2nchw function + shape_nchw = (4, 2, 5, 5) + shape_nlc = (4, 25, 2) + + def test_func(x): + assert x.shape == torch.Size(shape_nlc) + return x + + x = torch.rand(*shape_nchw) + output = nchw2nlc2nchw(test_func, x) + assert output.shape == torch.Size(shape_nchw) + + def test_func2(x, arg): + assert x.shape == torch.Size(shape_nlc) + assert arg == 100 + return x + + x = torch.rand(*shape_nchw) + output = nchw2nlc2nchw(test_func2, x, arg=100) + assert output.shape == torch.Size(shape_nchw) + + def test_func3(x): + assert x.is_contiguous() + assert x.shape == torch.Size(shape_nlc) + return x + + x = torch.rand(*shape_nchw) + output = nchw2nlc2nchw(test_func3, x, contiguous=True) + assert output.shape == torch.Size(shape_nchw) + assert output.is_contiguous() + + +def test_nlc2nchw2nlc(): + # Test nlc2nchw2nlc function + shape_nchw = (4, 2, 5, 5) + shape_nlc = (4, 25, 2) + + def test_func(x): + assert x.shape == torch.Size(shape_nchw) + return x + + x = torch.rand(*shape_nlc) + output = nlc2nchw2nlc(test_func, x, shape_nchw[2:]) + assert output.shape == torch.Size(shape_nlc) + + def test_func2(x, arg): + assert x.shape == torch.Size(shape_nchw) + assert arg == 100 + return x + + x = torch.rand(*shape_nlc) + output = nlc2nchw2nlc(test_func2, x, shape_nchw[2:], arg=100) + assert output.shape == torch.Size(shape_nlc) + + def test_func3(x): + assert x.is_contiguous() + assert x.shape == torch.Size(shape_nchw) + return x + + x = torch.rand(*shape_nlc) + output = nlc2nchw2nlc(test_func3, x, shape_nchw[2:], contiguous=True) + assert output.shape == torch.Size(shape_nlc) + assert output.is_contiguous() + + +def test_nchw_to_nlc(): + # Test nchw_to_nlc function + shape_nchw = (4, 2, 5, 5) + shape_nlc = (4, 25, 2) + x = torch.rand(*shape_nchw) + y = nchw_to_nlc(x) + assert y.shape == torch.Size(shape_nlc) + + +def test_nlc_to_nchw(): + # Test nlc_to_nchw function + shape_nchw = (4, 2, 5, 5) + shape_nlc = (4, 25, 2) + x = torch.rand(*shape_nlc) + y = nlc_to_nchw(x, (5, 5)) + assert y.shape == torch.Size(shape_nchw) diff --git a/Seg_All_In_One_MMSeg/tests/test_sampler.py b/Seg_All_In_One_MMSeg/tests/test_sampler.py new file mode 100644 index 0000000..322be95 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_sampler.py @@ -0,0 +1,78 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import pytest +import torch + +from mmseg.models.decode_heads import FCNHead +from mmseg.structures import OHEMPixelSampler + + +def _context_for_ohem(): + return FCNHead(in_channels=32, channels=16, num_classes=19) + + +def _context_for_ohem_multiple_loss(): + return FCNHead( + in_channels=32, + channels=16, + num_classes=19, + loss_decode=[ + dict(type='CrossEntropyLoss', loss_name='loss_1'), + dict(type='CrossEntropyLoss', loss_name='loss_2') + ]) + + +def test_ohem_sampler(): + + with pytest.raises(AssertionError): + # seg_logit and seg_label must be of the same size + sampler = OHEMPixelSampler(context=_context_for_ohem()) + seg_logit = torch.randn(1, 19, 45, 45) + seg_label = torch.randint(0, 19, size=(1, 1, 89, 89)) + sampler.sample(seg_logit, seg_label) + + # test with thresh + sampler = OHEMPixelSampler( + context=_context_for_ohem(), thresh=0.7, min_kept=200) + seg_logit = torch.randn(1, 19, 45, 45) + seg_label = torch.randint(0, 19, size=(1, 1, 45, 45)) + seg_weight = sampler.sample(seg_logit, seg_label) + assert seg_weight.shape[0] == seg_logit.shape[0] + assert seg_weight.shape[1:] == seg_logit.shape[2:] + assert seg_weight.sum() > 200 + + # test w.o thresh + sampler = OHEMPixelSampler(context=_context_for_ohem(), min_kept=200) + seg_logit = torch.randn(1, 19, 45, 45) + seg_label = torch.randint(0, 19, size=(1, 1, 45, 45)) + seg_weight = sampler.sample(seg_logit, seg_label) + assert seg_weight.shape[0] == seg_logit.shape[0] + assert seg_weight.shape[1:] == seg_logit.shape[2:] + assert seg_weight.sum() == 200 + + # test multiple losses case + with pytest.raises(AssertionError): + # seg_logit and seg_label must be of the same size + sampler = OHEMPixelSampler(context=_context_for_ohem_multiple_loss()) + seg_logit = torch.randn(1, 19, 45, 45) + seg_label = torch.randint(0, 19, size=(1, 1, 89, 89)) + sampler.sample(seg_logit, seg_label) + + # test with thresh in multiple losses case + sampler = OHEMPixelSampler( + context=_context_for_ohem_multiple_loss(), thresh=0.7, min_kept=200) + seg_logit = torch.randn(1, 19, 45, 45) + seg_label = torch.randint(0, 19, size=(1, 1, 45, 45)) + seg_weight = sampler.sample(seg_logit, seg_label) + assert seg_weight.shape[0] == seg_logit.shape[0] + assert seg_weight.shape[1:] == seg_logit.shape[2:] + assert seg_weight.sum() > 200 + + # test w.o thresh in multiple losses case + sampler = OHEMPixelSampler( + context=_context_for_ohem_multiple_loss(), min_kept=200) + seg_logit = torch.randn(1, 19, 45, 45) + seg_label = torch.randint(0, 19, size=(1, 1, 45, 45)) + seg_weight = sampler.sample(seg_logit, seg_label) + assert seg_weight.shape[0] == seg_logit.shape[0] + assert seg_weight.shape[1:] == seg_logit.shape[2:] + assert seg_weight.sum() == 200 diff --git a/Seg_All_In_One_MMSeg/tests/test_structures/test_seg_data_sample.py b/Seg_All_In_One_MMSeg/tests/test_structures/test_seg_data_sample.py new file mode 100644 index 0000000..37796b6 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_structures/test_seg_data_sample.py @@ -0,0 +1,77 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from unittest import TestCase + +import numpy as np +import pytest +import torch +from mmengine.structures import PixelData + +from mmseg.structures import SegDataSample + + +def _equal(a, b): + if isinstance(a, (torch.Tensor, np.ndarray)): + return (a == b).all() + else: + return a == b + + +class TestSegDataSample(TestCase): + + def test_init(self): + meta_info = dict( + img_size=[256, 256], + scale_factor=np.array([1.5, 1.5]), + img_shape=torch.rand(4)) + + seg_data_sample = SegDataSample(metainfo=meta_info) + assert 'img_size' in seg_data_sample + assert seg_data_sample.img_size == [256, 256] + assert seg_data_sample.get('img_size') == [256, 256] + + def test_setter(self): + seg_data_sample = SegDataSample() + + # test gt_sem_seg + gt_sem_seg_data = dict(sem_seg=torch.rand(5, 4, 2)) + gt_sem_seg = PixelData(**gt_sem_seg_data) + seg_data_sample.gt_sem_seg = gt_sem_seg + assert 'gt_sem_seg' in seg_data_sample + assert _equal(seg_data_sample.gt_sem_seg.sem_seg, + gt_sem_seg_data['sem_seg']) + + # test pred_sem_seg + pred_sem_seg_data = dict(sem_seg=torch.rand(5, 4, 2)) + pred_sem_seg = PixelData(**pred_sem_seg_data) + seg_data_sample.pred_sem_seg = pred_sem_seg + assert 'pred_sem_seg' in seg_data_sample + assert _equal(seg_data_sample.pred_sem_seg.sem_seg, + pred_sem_seg_data['sem_seg']) + + # test seg_logits + seg_logits_data = dict(sem_seg=torch.rand(5, 4, 2)) + seg_logits = PixelData(**seg_logits_data) + seg_data_sample.seg_logits = seg_logits + assert 'seg_logits' in seg_data_sample + assert _equal(seg_data_sample.seg_logits.sem_seg, + seg_logits_data['sem_seg']) + + # test type error + with pytest.raises(AssertionError): + seg_data_sample.gt_sem_seg = torch.rand(2, 4) + + with pytest.raises(AssertionError): + seg_data_sample.pred_sem_seg = torch.rand(2, 4) + + with pytest.raises(AssertionError): + seg_data_sample.seg_logits = torch.rand(2, 4) + + def test_deleter(self): + seg_data_sample = SegDataSample() + + pred_sem_seg_data = dict(sem_seg=torch.rand(5, 4, 2)) + pred_sem_seg = PixelData(**pred_sem_seg_data) + seg_data_sample.pred_sem_seg = pred_sem_seg + assert 'pred_sem_seg' in seg_data_sample + del seg_data_sample.pred_sem_seg + assert 'pred_sem_seg' not in seg_data_sample diff --git a/Seg_All_In_One_MMSeg/tests/test_utils/test_io.py b/Seg_All_In_One_MMSeg/tests/test_utils/test_io.py new file mode 100644 index 0000000..05abd27 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_utils/test_io.py @@ -0,0 +1,33 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os.path as osp + +import numpy as np +import pytest +from mmengine import FileClient + +from mmseg.utils import datafrombytes + + +@pytest.mark.parametrize( + ['backend', 'suffix'], + [['nifti', '.nii.gz'], ['numpy', '.npy'], ['pickle', '.pkl']]) +def test_datafrombytes(backend, suffix): + + file_client = FileClient('disk') + file_path = osp.join(osp.dirname(__file__), '../data/biomedical' + suffix) + bytes = file_client.get(file_path) + data = datafrombytes(bytes, backend) + + if backend == 'pickle': + # test pickle loading + assert isinstance(data, dict) + else: + assert isinstance(data, np.ndarray) + if backend == 'nifti': + # test nifti file loading + assert len(data.shape) == 3 + else: + # test npy file loading + # testing data biomedical.npy includes data and label + assert len(data.shape) == 4 + assert data.shape[0] == 2 diff --git a/Seg_All_In_One_MMSeg/tests/test_utils/test_set_env.py b/Seg_All_In_One_MMSeg/tests/test_utils/test_set_env.py new file mode 100644 index 0000000..86a2d29 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_utils/test_set_env.py @@ -0,0 +1,39 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import datetime +import sys +from unittest import TestCase + +from mmengine import DefaultScope + +from mmseg.utils import register_all_modules + + +class TestSetupEnv(TestCase): + + def test_register_all_modules(self): + from mmseg.registry import DATASETS + + # not init default scope + sys.modules.pop('mmseg.datasets', None) + sys.modules.pop('mmseg.datasets.ade', None) + DATASETS._module_dict.pop('ADE20KDataset', None) + self.assertFalse('ADE20KDataset' in DATASETS.module_dict) + register_all_modules(init_default_scope=False) + self.assertTrue('ADE20KDataset' in DATASETS.module_dict) + + # init default scope + sys.modules.pop('mmseg.datasets') + sys.modules.pop('mmseg.datasets.ade') + DATASETS._module_dict.pop('ADE20KDataset', None) + self.assertFalse('ADE20KDataset' in DATASETS.module_dict) + register_all_modules(init_default_scope=True) + self.assertTrue('ADE20KDataset' in DATASETS.module_dict) + self.assertEqual(DefaultScope.get_current_instance().scope_name, + 'mmseg') + + # init default scope when another scope is init + name = f'test-{datetime.datetime.now()}' + DefaultScope.get_instance(name, scope_name='test') + with self.assertWarnsRegex( + Warning, 'The current default scope "test" is not "mmseg"'): + register_all_modules(init_default_scope=True) diff --git a/Seg_All_In_One_MMSeg/tests/test_visualization/test_local_visualizer.py b/Seg_All_In_One_MMSeg/tests/test_visualization/test_local_visualizer.py new file mode 100644 index 0000000..e3b2a88 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tests/test_visualization/test_local_visualizer.py @@ -0,0 +1,213 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import os +import os.path as osp +import tempfile +from unittest import TestCase + +import cv2 +import mmcv +import numpy as np +import torch +from mmengine.structures import PixelData + +from mmseg.structures import SegDataSample +from mmseg.visualization import SegLocalVisualizer + + +class TestSegLocalVisualizer(TestCase): + + def test_add_datasample(self): + h = 10 + w = 12 + num_class = 2 + out_file = 'out_file' + + image = np.random.randint(0, 256, size=(h, w, 3)).astype('uint8') + + # test gt_sem_seg + gt_sem_seg_data = dict(data=torch.randint(0, num_class, (1, h, w))) + gt_sem_seg = PixelData(**gt_sem_seg_data) + + def test_add_datasample_forward(gt_sem_seg): + data_sample = SegDataSample() + data_sample.gt_sem_seg = gt_sem_seg + + with tempfile.TemporaryDirectory() as tmp_dir: + seg_local_visualizer = SegLocalVisualizer( + vis_backends=[dict(type='LocalVisBackend')], + save_dir=tmp_dir) + seg_local_visualizer.dataset_meta = dict( + classes=('background', 'foreground'), + palette=[[120, 120, 120], [6, 230, 230]]) + + # test out_file + seg_local_visualizer.add_datasample(out_file, image, + data_sample) + + assert os.path.exists( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png')) + drawn_img = cv2.imread( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png')) + assert drawn_img.shape == (h, w, 3) + + # test gt_instances and pred_instances + pred_sem_seg_data = dict( + data=torch.randint(0, num_class, (1, h, w))) + pred_sem_seg = PixelData(**pred_sem_seg_data) + + data_sample.pred_sem_seg = pred_sem_seg + + seg_local_visualizer.add_datasample(out_file, image, + data_sample) + self._assert_image_and_shape( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png'), (h, w * 2, 3)) + + seg_local_visualizer.add_datasample( + out_file, image, data_sample, draw_gt=False) + self._assert_image_and_shape( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png'), (h, w, 3)) + + if torch.cuda.is_available(): + test_add_datasample_forward(gt_sem_seg.cuda()) + test_add_datasample_forward(gt_sem_seg) + + def test_cityscapes_add_datasample(self): + h = 128 + w = 256 + num_class = 19 + out_file = 'out_file_cityscapes' + + image = mmcv.imread( + osp.join( + osp.dirname(__file__), + '../data/pseudo_cityscapes_dataset/leftImg8bit/val/frankfurt/frankfurt_000000_000294_leftImg8bit.png' # noqa + ), + 'color') + sem_seg = mmcv.imread( + osp.join( + osp.dirname(__file__), + '../data/pseudo_cityscapes_dataset/gtFine/val/frankfurt/frankfurt_000000_000294_gtFine_labelTrainIds.png' # noqa + ), + 'unchanged') + sem_seg = torch.unsqueeze(torch.from_numpy(sem_seg), 0) + gt_sem_seg_data = dict(data=sem_seg) + gt_sem_seg = PixelData(**gt_sem_seg_data) + + def test_cityscapes_add_datasample_forward(gt_sem_seg): + data_sample = SegDataSample() + data_sample.gt_sem_seg = gt_sem_seg + + with tempfile.TemporaryDirectory() as tmp_dir: + seg_local_visualizer = SegLocalVisualizer( + vis_backends=[dict(type='LocalVisBackend')], + save_dir=tmp_dir) + seg_local_visualizer.dataset_meta = dict( + classes=('road', 'sidewalk', 'building', 'wall', 'fence', + 'pole', 'traffic light', 'traffic sign', + 'vegetation', 'terrain', 'sky', 'person', 'rider', + 'car', 'truck', 'bus', 'train', 'motorcycle', + 'bicycle'), + palette=[[128, 64, 128], [244, 35, 232], [70, 70, 70], + [102, 102, 156], [190, 153, 153], [153, 153, 153], + [250, 170, 30], [220, 220, 0], [107, 142, 35], + [152, 251, 152], [70, 130, 180], [220, 20, 60], + [255, 0, 0], [0, 0, 142], [0, 0, 70], + [0, 60, 100], [0, 80, 100], [0, 0, 230], + [119, 11, 32]]) + # test out_file + seg_local_visualizer.add_datasample( + out_file, + image, + data_sample, + out_file=osp.join(tmp_dir, 'test.png')) + self._assert_image_and_shape( + osp.join(tmp_dir, 'test.png'), (h, w, 3)) + + # test gt_instances and pred_instances + pred_sem_seg_data = dict( + data=torch.randint(0, num_class, (1, h, w))) + pred_sem_seg = PixelData(**pred_sem_seg_data) + + data_sample.pred_sem_seg = pred_sem_seg + + # test draw prediction with gt + seg_local_visualizer.add_datasample(out_file, image, + data_sample) + self._assert_image_and_shape( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png'), (h, w * 2, 3)) + # test draw prediction without gt + seg_local_visualizer.add_datasample( + out_file, image, data_sample, draw_gt=False) + self._assert_image_and_shape( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png'), (h, w, 3)) + + if torch.cuda.is_available(): + test_cityscapes_add_datasample_forward(gt_sem_seg.cuda()) + test_cityscapes_add_datasample_forward(gt_sem_seg) + + def _assert_image_and_shape(self, out_file, out_shape): + assert os.path.exists(out_file) + drawn_img = cv2.imread(out_file) + assert drawn_img.shape == out_shape + + def test_add_datasample_depth(self): + h = 10 + w = 12 + out_file = 'out_file' + + image = np.random.randint(0, 256, size=(h, w, 3)).astype('uint8') + + # test gt_depth_map + gt_depth_map = PixelData(data=torch.rand(1, h, w)) + + def test_add_datasample_forward_depth(gt_depth_map): + data_sample = SegDataSample() + data_sample.gt_depth_map = gt_depth_map + + with tempfile.TemporaryDirectory() as tmp_dir: + seg_local_visualizer = SegLocalVisualizer( + vis_backends=[dict(type='LocalVisBackend')], + save_dir=tmp_dir) + seg_local_visualizer.dataset_meta = dict( + classes=('background', 'foreground'), + palette=[[120, 120, 120], [6, 230, 230]]) + + # test out_file + seg_local_visualizer.add_datasample(out_file, image, + data_sample) + + assert os.path.exists( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png')) + drawn_img = cv2.imread( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png')) + assert drawn_img.shape == (h * 2, w, 3) + + # test gt_instances and pred_instances + + pred_depth_map = PixelData(data=torch.rand(1, h, w)) + + data_sample.pred_depth_map = pred_depth_map + + seg_local_visualizer.add_datasample(out_file, image, + data_sample) + self._assert_image_and_shape( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png'), (h * 2, w * 2, 3)) + + seg_local_visualizer.add_datasample( + out_file, image, data_sample, draw_gt=False) + self._assert_image_and_shape( + osp.join(tmp_dir, 'vis_data', 'vis_image', + out_file + '_0.png'), (h * 2, w, 3)) + + if torch.cuda.is_available(): + test_add_datasample_forward_depth(gt_depth_map.cuda()) + test_add_datasample_forward_depth(gt_depth_map) diff --git a/Seg_All_In_One_MMSeg/tools/analysis_tools/analyze_logs.py b/Seg_All_In_One_MMSeg/tools/analysis_tools/analyze_logs.py new file mode 100644 index 0000000..7464d23 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/analysis_tools/analyze_logs.py @@ -0,0 +1,130 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Modified from https://github.com/open- +mmlab/mmdetection/blob/master/tools/analysis_tools/analyze_logs.py.""" +import argparse +import json +from collections import defaultdict + +import matplotlib.pyplot as plt +import seaborn as sns + + +def plot_curve(log_dicts, args): + if args.backend is not None: + plt.switch_backend(args.backend) + sns.set_style(args.style) + # if legend is None, use {filename}_{key} as legend + legend = args.legend + if legend is None: + legend = [] + for json_log in args.json_logs: + for metric in args.keys: + legend.append(f'{json_log}_{metric}') + assert len(legend) == (len(args.json_logs) * len(args.keys)) + metrics = args.keys + + num_metrics = len(metrics) + for i, log_dict in enumerate(log_dicts): + epochs = list(log_dict.keys()) + for j, metric in enumerate(metrics): + print(f'plot curve of {args.json_logs[i]}, metric is {metric}') + plot_epochs = [] + plot_iters = [] + plot_values = [] + # In some log files exist lines of validation, + # `mode` list is used to only collect iter number + # of training line. + for epoch in epochs: + epoch_logs = log_dict[epoch] + if metric not in epoch_logs.keys(): + continue + if metric in ['mIoU', 'mAcc', 'aAcc']: + plot_epochs.append(epoch) + plot_values.append(epoch_logs[metric][0]) + else: + for idx in range(len(epoch_logs[metric])): + plot_iters.append(epoch_logs['step'][idx]) + plot_values.append(epoch_logs[metric][idx]) + ax = plt.gca() + label = legend[i * num_metrics + j] + if metric in ['mIoU', 'mAcc', 'aAcc']: + ax.set_xticks(plot_epochs) + plt.xlabel('step') + plt.plot(plot_epochs, plot_values, label=label, marker='o') + else: + plt.xlabel('iter') + plt.plot(plot_iters, plot_values, label=label, linewidth=0.5) + plt.legend() + if args.title is not None: + plt.title(args.title) + if args.out is None: + plt.show() + else: + print(f'save curve to: {args.out}') + plt.savefig(args.out) + plt.cla() + + +def parse_args(): + parser = argparse.ArgumentParser(description='Analyze Json Log') + parser.add_argument( + 'json_logs', + type=str, + nargs='+', + help='path of train log in json format') + parser.add_argument( + '--keys', + type=str, + nargs='+', + default=['mIoU'], + help='the metric that you want to plot') + parser.add_argument('--title', type=str, help='title of figure') + parser.add_argument( + '--legend', + type=str, + nargs='+', + default=None, + help='legend of each plot') + parser.add_argument( + '--backend', type=str, default=None, help='backend of plt') + parser.add_argument( + '--style', type=str, default='dark', help='style of plt') + parser.add_argument('--out', type=str, default=None) + args = parser.parse_args() + return args + + +def load_json_logs(json_logs): + # load and convert json_logs to log_dict, key is step, value is a sub dict + # keys of sub dict is different metrics + # value of sub dict is a list of corresponding values of all iterations + log_dicts = [dict() for _ in json_logs] + prev_step = 0 + for json_log, log_dict in zip(json_logs, log_dicts): + with open(json_log) as log_file: + for line in log_file: + log = json.loads(line.strip()) + # the final step in json file is 0. + if 'step' in log and log['step'] != 0: + step = log['step'] + prev_step = step + else: + step = prev_step + if step not in log_dict: + log_dict[step] = defaultdict(list) + for k, v in log.items(): + log_dict[step][k].append(v) + return log_dicts + + +def main(): + args = parse_args() + json_logs = args.json_logs + for json_log in json_logs: + assert json_log.endswith('.json') + log_dicts = load_json_logs(json_logs) + plot_curve(log_dicts, args) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/analysis_tools/benchmark.py b/Seg_All_In_One_MMSeg/tools/analysis_tools/benchmark.py new file mode 100644 index 0000000..afaeaba --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/analysis_tools/benchmark.py @@ -0,0 +1,121 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +import time + +import numpy as np +import torch +from mmengine import Config +from mmengine.fileio import dump +from mmengine.model.utils import revert_sync_batchnorm +from mmengine.registry import init_default_scope +from mmengine.runner import Runner, load_checkpoint +from mmengine.utils import mkdir_or_exist + +from mmseg.registry import MODELS + + +def parse_args(): + parser = argparse.ArgumentParser(description='MMSeg benchmark a model') + parser.add_argument('config', help='test config file path') + parser.add_argument('checkpoint', help='checkpoint file') + parser.add_argument( + '--log-interval', type=int, default=50, help='interval of logging') + parser.add_argument( + '--work-dir', + help=('if specified, the results will be dumped ' + 'into the directory as json')) + parser.add_argument('--repeat-times', type=int, default=1) + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + cfg = Config.fromfile(args.config) + + init_default_scope(cfg.get('default_scope', 'mmseg')) + + timestamp = time.strftime('%Y%m%d_%H%M%S', time.localtime()) + if args.work_dir is not None: + mkdir_or_exist(osp.abspath(args.work_dir)) + json_file = osp.join(args.work_dir, f'fps_{timestamp}.json') + else: + # use config filename as default work_dir if cfg.work_dir is None + work_dir = osp.join('./work_dirs', + osp.splitext(osp.basename(args.config))[0]) + mkdir_or_exist(osp.abspath(work_dir)) + json_file = osp.join(work_dir, f'fps_{timestamp}.json') + + repeat_times = args.repeat_times + # set cudnn_benchmark + torch.backends.cudnn.benchmark = False + cfg.model.pretrained = None + + benchmark_dict = dict(config=args.config, unit='img / s') + overall_fps_list = [] + cfg.test_dataloader.batch_size = 1 + for time_index in range(repeat_times): + print(f'Run {time_index + 1}:') + # build the dataloader + data_loader = Runner.build_dataloader(cfg.test_dataloader) + + # build the model and load checkpoint + cfg.model.train_cfg = None + model = MODELS.build(cfg.model) + + if 'checkpoint' in args and osp.exists(args.checkpoint): + load_checkpoint(model, args.checkpoint, map_location='cpu') + + if torch.cuda.is_available(): + model = model.cuda() + + model = revert_sync_batchnorm(model) + + model.eval() + + # the first several iterations may be very slow so skip them + num_warmup = 5 + pure_inf_time = 0 + total_iters = 200 + + # benchmark with 200 batches and take the average + for i, data in enumerate(data_loader): + data = model.data_preprocessor(data, True) + inputs = data['inputs'] + data_samples = data['data_samples'] + if torch.cuda.is_available(): + torch.cuda.synchronize() + start_time = time.perf_counter() + + with torch.no_grad(): + model(inputs, data_samples, mode='predict') + + if torch.cuda.is_available(): + torch.cuda.synchronize() + elapsed = time.perf_counter() - start_time + + if i >= num_warmup: + pure_inf_time += elapsed + if (i + 1) % args.log_interval == 0: + fps = (i + 1 - num_warmup) / pure_inf_time + print(f'Done image [{i + 1:<3}/ {total_iters}], ' + f'fps: {fps:.2f} img / s') + + if (i + 1) == total_iters: + fps = (i + 1 - num_warmup) / pure_inf_time + print(f'Overall fps: {fps:.2f} img / s\n') + benchmark_dict[f'overall_fps_{time_index + 1}'] = round(fps, 2) + overall_fps_list.append(fps) + break + benchmark_dict['average_fps'] = round(np.mean(overall_fps_list), 2) + benchmark_dict['fps_variance'] = round(np.var(overall_fps_list), 4) + print(f'Average fps of {repeat_times} evaluations: ' + f'{benchmark_dict["average_fps"]}') + print(f'The variance of {repeat_times} evaluations: ' + f'{benchmark_dict["fps_variance"]}') + dump(benchmark_dict, json_file, indent=4) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/analysis_tools/browse_dataset.py b/Seg_All_In_One_MMSeg/tools/analysis_tools/browse_dataset.py new file mode 100644 index 0000000..925c14a --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/analysis_tools/browse_dataset.py @@ -0,0 +1,77 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp + +from mmengine.config import Config, DictAction +from mmengine.utils import ProgressBar + +from mmseg.registry import DATASETS, VISUALIZERS +from mmseg.utils import register_all_modules + + +def parse_args(): + parser = argparse.ArgumentParser(description='Browse a dataset') + parser.add_argument('config', help='train config file path') + parser.add_argument( + '--output-dir', + default=None, + type=str, + help='If there is no display interface, you can save it') + parser.add_argument('--not-show', default=False, action='store_true') + parser.add_argument( + '--show-interval', + type=float, + default=2, + help='the interval of show (s)') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + cfg = Config.fromfile(args.config) + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + # register all modules in mmdet into the registries + register_all_modules() + + dataset = DATASETS.build(cfg.train_dataloader.dataset) + visualizer = VISUALIZERS.build(cfg.visualizer) + visualizer.dataset_meta = dataset.metainfo + + progress_bar = ProgressBar(len(dataset)) + for item in dataset: + img = item['inputs'].permute(1, 2, 0).numpy() + img = img[..., [2, 1, 0]] # bgr to rgb + data_sample = item['data_samples'].numpy() + img_path = osp.basename(item['data_samples'].img_path) + + out_file = osp.join( + args.output_dir, + osp.basename(img_path)) if args.output_dir is not None else None + + visualizer.add_datasample( + name=osp.basename(img_path), + image=img, + data_sample=data_sample, + draw_gt=True, + draw_pred=False, + wait_time=args.show_interval, + out_file=out_file, + show=not args.not_show) + progress_bar.update() + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/analysis_tools/confusion_matrix.py b/Seg_All_In_One_MMSeg/tools/analysis_tools/confusion_matrix.py new file mode 100644 index 0000000..39756cd --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/analysis_tools/confusion_matrix.py @@ -0,0 +1,197 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os + +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.ticker import MultipleLocator +from mmengine.config import Config, DictAction +from mmengine.registry import init_default_scope +from mmengine.utils import mkdir_or_exist, progressbar +from PIL import Image + +from mmseg.registry import DATASETS + +init_default_scope('mmseg') + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Generate confusion matrix from segmentation results') + parser.add_argument('config', help='test config file path') + parser.add_argument( + 'prediction_path', help='prediction path where test folder result') + parser.add_argument( + 'save_dir', help='directory where confusion matrix will be saved') + parser.add_argument( + '--show', action='store_true', help='show confusion matrix') + parser.add_argument( + '--color-theme', + default='winter', + help='theme of the matrix color map') + parser.add_argument( + '--title', + default='Normalized Confusion Matrix', + help='title of the matrix color map') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + args = parser.parse_args() + return args + + +def calculate_confusion_matrix(dataset, results): + """Calculate the confusion matrix. + + Args: + dataset (Dataset): Test or val dataset. + results (list[ndarray]): A list of segmentation results in each image. + """ + n = len(dataset.METAINFO['classes']) + confusion_matrix = np.zeros(shape=[n, n]) + assert len(dataset) == len(results) + ignore_index = dataset.ignore_index + reduce_zero_label = dataset.reduce_zero_label + prog_bar = progressbar.ProgressBar(len(results)) + for idx, per_img_res in enumerate(results): + res_segm = per_img_res + gt_segm = dataset[idx]['data_samples'] \ + .gt_sem_seg.data.squeeze().numpy().astype(np.uint8) + gt_segm, res_segm = gt_segm.flatten(), res_segm.flatten() + if reduce_zero_label: + gt_segm = gt_segm - 1 + to_ignore = gt_segm == ignore_index + + gt_segm, res_segm = gt_segm[~to_ignore], res_segm[~to_ignore] + inds = n * gt_segm + res_segm + mat = np.bincount(inds, minlength=n**2).reshape(n, n) + confusion_matrix += mat + prog_bar.update() + return confusion_matrix + + +def plot_confusion_matrix(confusion_matrix, + labels, + save_dir=None, + show=True, + title='Normalized Confusion Matrix', + color_theme='OrRd'): + """Draw confusion matrix with matplotlib. + + Args: + confusion_matrix (ndarray): The confusion matrix. + labels (list[str]): List of class names. + save_dir (str|optional): If set, save the confusion matrix plot to the + given path. Default: None. + show (bool): Whether to show the plot. Default: True. + title (str): Title of the plot. Default: `Normalized Confusion Matrix`. + color_theme (str): Theme of the matrix color map. Default: `winter`. + """ + # normalize the confusion matrix + per_label_sums = confusion_matrix.sum(axis=1)[:, np.newaxis] + confusion_matrix = \ + confusion_matrix.astype(np.float32) / per_label_sums * 100 + + num_classes = len(labels) + fig, ax = plt.subplots( + figsize=(2 * num_classes, 2 * num_classes * 0.8), dpi=300) + cmap = plt.get_cmap(color_theme) + im = ax.imshow(confusion_matrix, cmap=cmap) + colorbar = plt.colorbar(mappable=im, ax=ax) + colorbar.ax.tick_params(labelsize=20) # 设置 colorbar 标签的字体大小 + + title_font = {'weight': 'bold', 'size': 20} + ax.set_title(title, fontdict=title_font) + label_font = {'size': 40} + plt.ylabel('Ground Truth Label', fontdict=label_font) + plt.xlabel('Prediction Label', fontdict=label_font) + + # draw locator + xmajor_locator = MultipleLocator(1) + xminor_locator = MultipleLocator(0.5) + ax.xaxis.set_major_locator(xmajor_locator) + ax.xaxis.set_minor_locator(xminor_locator) + ymajor_locator = MultipleLocator(1) + yminor_locator = MultipleLocator(0.5) + ax.yaxis.set_major_locator(ymajor_locator) + ax.yaxis.set_minor_locator(yminor_locator) + + # draw grid + ax.grid(True, which='minor', linestyle='-') + + # draw label + ax.set_xticks(np.arange(num_classes)) + ax.set_yticks(np.arange(num_classes)) + ax.set_xticklabels(labels, fontsize=20) + ax.set_yticklabels(labels, fontsize=20) + + ax.tick_params( + axis='x', bottom=False, top=True, labelbottom=False, labeltop=True) + plt.setp( + ax.get_xticklabels(), rotation=45, ha='left', rotation_mode='anchor') + + # draw confusion matrix value + for i in range(num_classes): + for j in range(num_classes): + ax.text( + j, + i, + '{}%'.format( + round(confusion_matrix[i, j], 2 + ) if not np.isnan(confusion_matrix[i, j]) else -1), + ha='center', + va='center', + color='k', + size=20) + + ax.set_ylim(len(confusion_matrix) - 0.5, -0.5) # matplotlib>3.1.1 + + fig.tight_layout() + if save_dir is not None: + mkdir_or_exist(save_dir) + plt.savefig( + os.path.join(save_dir, 'confusion_matrix.png'), format='png') + if show: + plt.show() + + +def main(): + args = parse_args() + + cfg = Config.fromfile(args.config) + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + results = [] + for img in sorted(os.listdir(args.prediction_path)): + img = os.path.join(args.prediction_path, img) + image = Image.open(img) + image = np.copy(image) + results.append(image) + + assert isinstance(results, list) + if isinstance(results[0], np.ndarray): + pass + else: + raise TypeError('invalid type of prediction results') + + dataset = DATASETS.build(cfg.test_dataloader.dataset) + confusion_matrix = calculate_confusion_matrix(dataset, results) + plot_confusion_matrix( + confusion_matrix, + dataset.METAINFO['classes'], + save_dir=args.save_dir, + show=args.show, + title=args.title, + color_theme=args.color_theme) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/analysis_tools/get_flops.py b/Seg_All_In_One_MMSeg/tools/analysis_tools/get_flops.py new file mode 100644 index 0000000..78a7398 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/analysis_tools/get_flops.py @@ -0,0 +1,124 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import tempfile +from pathlib import Path + +import torch +from mmengine import Config, DictAction +from mmengine.logging import MMLogger +from mmengine.model import revert_sync_batchnorm +from mmengine.registry import init_default_scope + +from mmseg.models import BaseSegmentor +from mmseg.registry import MODELS +from mmseg.structures import SegDataSample + +try: + from mmengine.analysis import get_model_complexity_info + from mmengine.analysis.print_helper import _format_size +except ImportError: + raise ImportError('Please upgrade mmengine >= 0.6.0 to use this script.') + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Get the FLOPs of a segmentor') + parser.add_argument('config', help='train config file path') + parser.add_argument( + '--shape', + type=int, + nargs='+', + default=[2048, 1024], + help='input image size') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + args = parser.parse_args() + return args + + +def inference(args: argparse.Namespace, logger: MMLogger) -> dict: + config_name = Path(args.config) + + if not config_name.exists(): + logger.error(f'Config file {config_name} does not exist') + + cfg: Config = Config.fromfile(config_name) + cfg.work_dir = tempfile.TemporaryDirectory().name + cfg.log_level = 'WARN' + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + init_default_scope(cfg.get('scope', 'mmseg')) + + if len(args.shape) == 1: + input_shape = (3, args.shape[0], args.shape[0]) + elif len(args.shape) == 2: + input_shape = (3, ) + tuple(args.shape) + else: + raise ValueError('invalid input shape') + result = {} + + model: BaseSegmentor = MODELS.build(cfg.model) + if hasattr(model, 'auxiliary_head'): + model.auxiliary_head = None + if torch.cuda.is_available(): + model.cuda() + model = revert_sync_batchnorm(model) + result['ori_shape'] = input_shape[-2:] + result['pad_shape'] = input_shape[-2:] + data_batch = { + 'inputs': [torch.rand(input_shape)], + 'data_samples': [SegDataSample(metainfo=result)] + } + data = model.data_preprocessor(data_batch) + model.eval() + if cfg.model.decode_head.type in ['MaskFormerHead', 'Mask2FormerHead']: + # TODO: Support MaskFormer and Mask2Former + raise NotImplementedError('MaskFormer and Mask2Former are not ' + 'supported yet.') + outputs = get_model_complexity_info( + model, + input_shape=None, + inputs=data['inputs'], + show_table=False, + show_arch=False) + result['flops'] = _format_size(outputs['flops']) + result['params'] = _format_size(outputs['params']) + result['compute_type'] = 'direct: randomly generate a picture' + return result + + +def main(): + + args = parse_args() + logger = MMLogger.get_instance(name='MMLogger') + + result = inference(args, logger) + split_line = '=' * 30 + ori_shape = result['ori_shape'] + pad_shape = result['pad_shape'] + flops = result['flops'] + params = result['params'] + compute_type = result['compute_type'] + + if pad_shape != ori_shape: + print(f'{split_line}\nUse size divisor set input shape ' + f'from {ori_shape} to {pad_shape}') + print(f'{split_line}\nCompute type: {compute_type}\n' + f'Input shape: {pad_shape}\nFlops: {flops}\n' + f'Params: {params}\n{split_line}') + print('!!!Please be cautious if you use the results in papers. ' + 'You may need to check if all ops are supported and verify ' + 'that the flops computation is correct.') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/analysis_tools/visualization_cam.py b/Seg_All_In_One_MMSeg/tools/analysis_tools/visualization_cam.py new file mode 100644 index 0000000..00cdb3e --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/analysis_tools/visualization_cam.py @@ -0,0 +1,127 @@ +# Copyright (c) OpenMMLab. All rights reserved. +"""Use the pytorch-grad-cam tool to visualize Class Activation Maps (CAM). + +requirement: pip install grad-cam +""" + +from argparse import ArgumentParser + +import numpy as np +import torch +import torch.nn.functional as F +from mmengine import Config +from mmengine.model import revert_sync_batchnorm +from PIL import Image +from pytorch_grad_cam import GradCAM +from pytorch_grad_cam.utils.image import preprocess_image, show_cam_on_image + +from mmseg.apis import inference_model, init_model, show_result_pyplot +from mmseg.utils import register_all_modules + + +class SemanticSegmentationTarget: + """wrap the model. + + requirement: pip install grad-cam + + Args: + category (int): Visualization class. + mask (ndarray): Mask of class. + size (tuple): Image size. + """ + + def __init__(self, category, mask, size): + self.category = category + self.mask = torch.from_numpy(mask) + self.size = size + if torch.cuda.is_available(): + self.mask = self.mask.cuda() + + def __call__(self, model_output): + model_output = torch.unsqueeze(model_output, dim=0) + model_output = F.interpolate( + model_output, size=self.size, mode='bilinear') + model_output = torch.squeeze(model_output, dim=0) + + return (model_output[self.category, :, :] * self.mask).sum() + + +def main(): + parser = ArgumentParser() + parser.add_argument('img', help='Image file') + parser.add_argument('config', help='Config file') + parser.add_argument('checkpoint', help='Checkpoint file') + parser.add_argument( + '--out-file', + default='prediction.png', + help='Path to output prediction file') + parser.add_argument( + '--cam-file', default='vis_cam.png', help='Path to output cam file') + parser.add_argument( + '--target-layers', + default='backbone.layer4[2]', + help='Target layers to visualize CAM') + parser.add_argument( + '--category-index', default='7', help='Category to visualize CAM') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + args = parser.parse_args() + + # build the model from a config file and a checkpoint file + register_all_modules() + model = init_model(args.config, args.checkpoint, device=args.device) + if args.device == 'cpu': + model = revert_sync_batchnorm(model) + + # test a single image + result = inference_model(model, args.img) + + # show the results + show_result_pyplot( + model, + args.img, + result, + draw_gt=False, + show=False if args.out_file is not None else True, + out_file=args.out_file) + + # result data conversion + prediction_data = result.pred_sem_seg.data + pre_np_data = prediction_data.cpu().numpy().squeeze(0) + + target_layers = args.target_layers + target_layers = [eval(f'model.{target_layers}')] + + category = int(args.category_index) + mask_float = np.float32(pre_np_data == category) + + # data processing + image = np.array(Image.open(args.img).convert('RGB')) + height, width = image.shape[0], image.shape[1] + rgb_img = np.float32(image) / 255 + config = Config.fromfile(args.config) + image_mean = config.data_preprocessor['mean'] + image_std = config.data_preprocessor['std'] + input_tensor = preprocess_image( + rgb_img, + mean=[x / 255 for x in image_mean], + std=[x / 255 for x in image_std]) + + # Grad CAM(Class Activation Maps) + # Can also be LayerCAM, XGradCAM, GradCAMPlusPlus, EigenCAM, EigenGradCAM + targets = [ + SemanticSegmentationTarget(category, mask_float, (height, width)) + ] + with GradCAM( + model=model, + target_layers=target_layers, + use_cuda=torch.cuda.is_available()) as cam: + grayscale_cam = cam(input_tensor=input_tensor, targets=targets)[0, :] + cam_image = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True) + + # save cam file + Image.fromarray(cam_image).save(args.cam_file) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/chase_db1.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/chase_db1.py new file mode 100644 index 0000000..f4fefbd --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/chase_db1.py @@ -0,0 +1,89 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp +import tempfile +import zipfile + +import mmcv +from mmengine.utils import mkdir_or_exist + +CHASE_DB1_LEN = 28 * 3 +TRAINING_LEN = 60 + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert CHASE_DB1 dataset to mmsegmentation format') + parser.add_argument('dataset_path', help='path of CHASEDB1.zip') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + dataset_path = args.dataset_path + if args.out_dir is None: + out_dir = osp.join('data', 'CHASE_DB1') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(out_dir) + mkdir_or_exist(osp.join(out_dir, 'images')) + mkdir_or_exist(osp.join(out_dir, 'images', 'training')) + mkdir_or_exist(osp.join(out_dir, 'images', 'validation')) + mkdir_or_exist(osp.join(out_dir, 'annotations')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'training')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'validation')) + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + print('Extracting CHASEDB1.zip...') + zip_file = zipfile.ZipFile(dataset_path) + zip_file.extractall(tmp_dir) + + print('Generating training dataset...') + + assert len(os.listdir(tmp_dir)) == CHASE_DB1_LEN, \ + f'len(os.listdir(tmp_dir)) != {CHASE_DB1_LEN}' + + for img_name in sorted(os.listdir(tmp_dir))[:TRAINING_LEN]: + img = mmcv.imread(osp.join(tmp_dir, img_name)) + if osp.splitext(img_name)[1] == '.jpg': + mmcv.imwrite( + img, + osp.join(out_dir, 'images', 'training', + osp.splitext(img_name)[0] + '.png')) + else: + # The annotation img should be divided by 128, because some of + # the annotation imgs are not standard. We should set a + # threshold to convert the nonstandard annotation imgs. The + # value divided by 128 is equivalent to '1 if value >= 128 + # else 0' + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'training', + osp.splitext(img_name)[0] + '.png')) + + for img_name in sorted(os.listdir(tmp_dir))[TRAINING_LEN:]: + img = mmcv.imread(osp.join(tmp_dir, img_name)) + if osp.splitext(img_name)[1] == '.jpg': + mmcv.imwrite( + img, + osp.join(out_dir, 'images', 'validation', + osp.splitext(img_name)[0] + '.png')) + else: + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'validation', + osp.splitext(img_name)[0] + '.png')) + + print('Removing the temporary files...') + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/cityscapes.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/cityscapes.py new file mode 100644 index 0000000..0d6a801 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/cityscapes.py @@ -0,0 +1,56 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp + +from cityscapesscripts.preparation.json2labelImg import json2labelImg +from mmengine.utils import (mkdir_or_exist, scandir, track_parallel_progress, + track_progress) + + +def convert_json_to_label(json_file): + label_file = json_file.replace('_polygons.json', '_labelTrainIds.png') + json2labelImg(json_file, label_file, 'trainIds') + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert Cityscapes annotations to TrainIds') + parser.add_argument('cityscapes_path', help='cityscapes data path') + parser.add_argument('--gt-dir', default='gtFine', type=str) + parser.add_argument('-o', '--out-dir', help='output path') + parser.add_argument( + '--nproc', default=1, type=int, help='number of process') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + cityscapes_path = args.cityscapes_path + out_dir = args.out_dir if args.out_dir else cityscapes_path + mkdir_or_exist(out_dir) + + gt_dir = osp.join(cityscapes_path, args.gt_dir) + + poly_files = [] + for poly in scandir(gt_dir, '_polygons.json', recursive=True): + poly_file = osp.join(gt_dir, poly) + poly_files.append(poly_file) + if args.nproc > 1: + track_parallel_progress(convert_json_to_label, poly_files, args.nproc) + else: + track_progress(convert_json_to_label, poly_files) + + split_names = ['train', 'val', 'test'] + + for split in split_names: + filenames = [] + for poly in scandir( + osp.join(gt_dir, split), '_polygons.json', recursive=True): + filenames.append(poly.replace('_gtFine_polygons.json', '')) + with open(osp.join(out_dir, f'{split}.txt'), 'w') as f: + f.writelines(f + '\n' for f in filenames) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/coco_stuff10k.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/coco_stuff10k.py new file mode 100644 index 0000000..920127e --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/coco_stuff10k.py @@ -0,0 +1,308 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +import shutil +from functools import partial + +import numpy as np +from mmengine.utils import (mkdir_or_exist, track_parallel_progress, + track_progress) +from PIL import Image +from scipy.io import loadmat + +COCO_LEN = 10000 + +clsID_to_trID = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + 10: 10, + 11: 11, + 13: 12, + 14: 13, + 15: 14, + 16: 15, + 17: 16, + 18: 17, + 19: 18, + 20: 19, + 21: 20, + 22: 21, + 23: 22, + 24: 23, + 25: 24, + 27: 25, + 28: 26, + 31: 27, + 32: 28, + 33: 29, + 34: 30, + 35: 31, + 36: 32, + 37: 33, + 38: 34, + 39: 35, + 40: 36, + 41: 37, + 42: 38, + 43: 39, + 44: 40, + 46: 41, + 47: 42, + 48: 43, + 49: 44, + 50: 45, + 51: 46, + 52: 47, + 53: 48, + 54: 49, + 55: 50, + 56: 51, + 57: 52, + 58: 53, + 59: 54, + 60: 55, + 61: 56, + 62: 57, + 63: 58, + 64: 59, + 65: 60, + 67: 61, + 70: 62, + 72: 63, + 73: 64, + 74: 65, + 75: 66, + 76: 67, + 77: 68, + 78: 69, + 79: 70, + 80: 71, + 81: 72, + 82: 73, + 84: 74, + 85: 75, + 86: 76, + 87: 77, + 88: 78, + 89: 79, + 90: 80, + 92: 81, + 93: 82, + 94: 83, + 95: 84, + 96: 85, + 97: 86, + 98: 87, + 99: 88, + 100: 89, + 101: 90, + 102: 91, + 103: 92, + 104: 93, + 105: 94, + 106: 95, + 107: 96, + 108: 97, + 109: 98, + 110: 99, + 111: 100, + 112: 101, + 113: 102, + 114: 103, + 115: 104, + 116: 105, + 117: 106, + 118: 107, + 119: 108, + 120: 109, + 121: 110, + 122: 111, + 123: 112, + 124: 113, + 125: 114, + 126: 115, + 127: 116, + 128: 117, + 129: 118, + 130: 119, + 131: 120, + 132: 121, + 133: 122, + 134: 123, + 135: 124, + 136: 125, + 137: 126, + 138: 127, + 139: 128, + 140: 129, + 141: 130, + 142: 131, + 143: 132, + 144: 133, + 145: 134, + 146: 135, + 147: 136, + 148: 137, + 149: 138, + 150: 139, + 151: 140, + 152: 141, + 153: 142, + 154: 143, + 155: 144, + 156: 145, + 157: 146, + 158: 147, + 159: 148, + 160: 149, + 161: 150, + 162: 151, + 163: 152, + 164: 153, + 165: 154, + 166: 155, + 167: 156, + 168: 157, + 169: 158, + 170: 159, + 171: 160, + 172: 161, + 173: 162, + 174: 163, + 175: 164, + 176: 165, + 177: 166, + 178: 167, + 179: 168, + 180: 169, + 181: 170, + 182: 171 +} + + +def convert_to_trainID(tuple_path, in_img_dir, in_ann_dir, out_img_dir, + out_mask_dir, is_train): + imgpath, maskpath = tuple_path + shutil.copyfile( + osp.join(in_img_dir, imgpath), + osp.join(out_img_dir, 'train2014', imgpath) if is_train else osp.join( + out_img_dir, 'test2014', imgpath)) + annotate = loadmat(osp.join(in_ann_dir, maskpath)) + mask = annotate['S'].astype(np.uint8) + mask_copy = mask.copy() + for clsID, trID in clsID_to_trID.items(): + mask_copy[mask == clsID] = trID + seg_filename = osp.join(out_mask_dir, 'train2014', + maskpath.split('.')[0] + + '_labelTrainIds.png') if is_train else osp.join( + out_mask_dir, 'test2014', + maskpath.split('.')[0] + '_labelTrainIds.png') + Image.fromarray(mask_copy).save(seg_filename, 'PNG') + + +def generate_coco_list(folder): + train_list = osp.join(folder, 'imageLists', 'train.txt') + test_list = osp.join(folder, 'imageLists', 'test.txt') + train_paths = [] + test_paths = [] + + with open(train_list) as f: + for filename in f: + basename = filename.strip() + imgpath = basename + '.jpg' + maskpath = basename + '.mat' + train_paths.append((imgpath, maskpath)) + + with open(test_list) as f: + for filename in f: + basename = filename.strip() + imgpath = basename + '.jpg' + maskpath = basename + '.mat' + test_paths.append((imgpath, maskpath)) + + return train_paths, test_paths + + +def parse_args(): + parser = argparse.ArgumentParser( + description=\ + 'Convert COCO Stuff 10k annotations to mmsegmentation format') # noqa + parser.add_argument('coco_path', help='coco stuff path') + parser.add_argument('-o', '--out_dir', help='output path') + parser.add_argument( + '--nproc', default=16, type=int, help='number of process') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + coco_path = args.coco_path + nproc = args.nproc + + out_dir = args.out_dir or coco_path + out_img_dir = osp.join(out_dir, 'images') + out_mask_dir = osp.join(out_dir, 'annotations') + + mkdir_or_exist(osp.join(out_img_dir, 'train2014')) + mkdir_or_exist(osp.join(out_img_dir, 'test2014')) + mkdir_or_exist(osp.join(out_mask_dir, 'train2014')) + mkdir_or_exist(osp.join(out_mask_dir, 'test2014')) + + train_list, test_list = generate_coco_list(coco_path) + assert (len(train_list) + + len(test_list)) == COCO_LEN, 'Wrong length of list {} & {}'.format( + len(train_list), len(test_list)) + + if args.nproc > 1: + track_parallel_progress( + partial( + convert_to_trainID, + in_img_dir=osp.join(coco_path, 'images'), + in_ann_dir=osp.join(coco_path, 'annotations'), + out_img_dir=out_img_dir, + out_mask_dir=out_mask_dir, + is_train=True), + train_list, + nproc=nproc) + track_parallel_progress( + partial( + convert_to_trainID, + in_img_dir=osp.join(coco_path, 'images'), + in_ann_dir=osp.join(coco_path, 'annotations'), + out_img_dir=out_img_dir, + out_mask_dir=out_mask_dir, + is_train=False), + test_list, + nproc=nproc) + else: + track_progress( + partial( + convert_to_trainID, + in_img_dir=osp.join(coco_path, 'images'), + in_ann_dir=osp.join(coco_path, 'annotations'), + out_img_dir=out_img_dir, + out_mask_dir=out_mask_dir, + is_train=True), train_list) + track_progress( + partial( + convert_to_trainID, + in_img_dir=osp.join(coco_path, 'images'), + in_ann_dir=osp.join(coco_path, 'annotations'), + out_img_dir=out_img_dir, + out_mask_dir=out_mask_dir, + is_train=False), test_list) + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/coco_stuff164k.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/coco_stuff164k.py new file mode 100644 index 0000000..a13114a --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/coco_stuff164k.py @@ -0,0 +1,265 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +import shutil +from functools import partial +from glob import glob + +import numpy as np +from mmengine.utils import (mkdir_or_exist, track_parallel_progress, + track_progress) +from PIL import Image + +COCO_LEN = 123287 + +clsID_to_trID = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + 10: 10, + 12: 11, + 13: 12, + 14: 13, + 15: 14, + 16: 15, + 17: 16, + 18: 17, + 19: 18, + 20: 19, + 21: 20, + 22: 21, + 23: 22, + 24: 23, + 26: 24, + 27: 25, + 30: 26, + 31: 27, + 32: 28, + 33: 29, + 34: 30, + 35: 31, + 36: 32, + 37: 33, + 38: 34, + 39: 35, + 40: 36, + 41: 37, + 42: 38, + 43: 39, + 45: 40, + 46: 41, + 47: 42, + 48: 43, + 49: 44, + 50: 45, + 51: 46, + 52: 47, + 53: 48, + 54: 49, + 55: 50, + 56: 51, + 57: 52, + 58: 53, + 59: 54, + 60: 55, + 61: 56, + 62: 57, + 63: 58, + 64: 59, + 66: 60, + 69: 61, + 71: 62, + 72: 63, + 73: 64, + 74: 65, + 75: 66, + 76: 67, + 77: 68, + 78: 69, + 79: 70, + 80: 71, + 81: 72, + 83: 73, + 84: 74, + 85: 75, + 86: 76, + 87: 77, + 88: 78, + 89: 79, + 91: 80, + 92: 81, + 93: 82, + 94: 83, + 95: 84, + 96: 85, + 97: 86, + 98: 87, + 99: 88, + 100: 89, + 101: 90, + 102: 91, + 103: 92, + 104: 93, + 105: 94, + 106: 95, + 107: 96, + 108: 97, + 109: 98, + 110: 99, + 111: 100, + 112: 101, + 113: 102, + 114: 103, + 115: 104, + 116: 105, + 117: 106, + 118: 107, + 119: 108, + 120: 109, + 121: 110, + 122: 111, + 123: 112, + 124: 113, + 125: 114, + 126: 115, + 127: 116, + 128: 117, + 129: 118, + 130: 119, + 131: 120, + 132: 121, + 133: 122, + 134: 123, + 135: 124, + 136: 125, + 137: 126, + 138: 127, + 139: 128, + 140: 129, + 141: 130, + 142: 131, + 143: 132, + 144: 133, + 145: 134, + 146: 135, + 147: 136, + 148: 137, + 149: 138, + 150: 139, + 151: 140, + 152: 141, + 153: 142, + 154: 143, + 155: 144, + 156: 145, + 157: 146, + 158: 147, + 159: 148, + 160: 149, + 161: 150, + 162: 151, + 163: 152, + 164: 153, + 165: 154, + 166: 155, + 167: 156, + 168: 157, + 169: 158, + 170: 159, + 171: 160, + 172: 161, + 173: 162, + 174: 163, + 175: 164, + 176: 165, + 177: 166, + 178: 167, + 179: 168, + 180: 169, + 181: 170, + 255: 255 +} + + +def convert_to_trainID(maskpath, out_mask_dir, is_train): + mask = np.array(Image.open(maskpath)) + mask_copy = mask.copy() + for clsID, trID in clsID_to_trID.items(): + mask_copy[mask == clsID] = trID + seg_filename = osp.join( + out_mask_dir, 'train2017', + osp.basename(maskpath).split('.')[0] + + '_labelTrainIds.png') if is_train else osp.join( + out_mask_dir, 'val2017', + osp.basename(maskpath).split('.')[0] + '_labelTrainIds.png') + Image.fromarray(mask_copy).save(seg_filename, 'PNG') + + +def parse_args(): + parser = argparse.ArgumentParser( + description=\ + 'Convert COCO Stuff 164k annotations to mmsegmentation format') # noqa + parser.add_argument('coco_path', help='coco stuff path') + parser.add_argument('-o', '--out_dir', help='output path') + parser.add_argument( + '--nproc', default=16, type=int, help='number of process') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + coco_path = args.coco_path + nproc = args.nproc + + out_dir = args.out_dir or coco_path + out_img_dir = osp.join(out_dir, 'images') + out_mask_dir = osp.join(out_dir, 'annotations') + + mkdir_or_exist(osp.join(out_mask_dir, 'train2017')) + mkdir_or_exist(osp.join(out_mask_dir, 'val2017')) + + if out_dir != coco_path: + shutil.copytree(osp.join(coco_path, 'images'), out_img_dir) + + train_list = glob(osp.join(coco_path, 'annotations', 'train2017', '*.png')) + train_list = [file for file in train_list if '_labelTrainIds' not in file] + test_list = glob(osp.join(coco_path, 'annotations', 'val2017', '*.png')) + test_list = [file for file in test_list if '_labelTrainIds' not in file] + assert (len(train_list) + + len(test_list)) == COCO_LEN, 'Wrong length of list {} & {}'.format( + len(train_list), len(test_list)) + + if args.nproc > 1: + track_parallel_progress( + partial( + convert_to_trainID, out_mask_dir=out_mask_dir, is_train=True), + train_list, + nproc=nproc) + track_parallel_progress( + partial( + convert_to_trainID, out_mask_dir=out_mask_dir, is_train=False), + test_list, + nproc=nproc) + else: + track_progress( + partial( + convert_to_trainID, out_mask_dir=out_mask_dir, is_train=True), + train_list) + track_progress( + partial( + convert_to_trainID, out_mask_dir=out_mask_dir, is_train=False), + test_list) + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/drive.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/drive.py new file mode 100644 index 0000000..076fd05 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/drive.py @@ -0,0 +1,114 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp +import tempfile +import zipfile + +import cv2 +import mmcv +from mmengine.utils import mkdir_or_exist + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert DRIVE dataset to mmsegmentation format') + parser.add_argument( + 'training_path', help='the training part of DRIVE dataset') + parser.add_argument( + 'testing_path', help='the testing part of DRIVE dataset') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + training_path = args.training_path + testing_path = args.testing_path + if args.out_dir is None: + out_dir = osp.join('data', 'DRIVE') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(out_dir) + mkdir_or_exist(osp.join(out_dir, 'images')) + mkdir_or_exist(osp.join(out_dir, 'images', 'training')) + mkdir_or_exist(osp.join(out_dir, 'images', 'validation')) + mkdir_or_exist(osp.join(out_dir, 'annotations')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'training')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'validation')) + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + print('Extracting training.zip...') + zip_file = zipfile.ZipFile(training_path) + zip_file.extractall(tmp_dir) + + print('Generating training dataset...') + now_dir = osp.join(tmp_dir, 'training', 'images') + for img_name in os.listdir(now_dir): + img = mmcv.imread(osp.join(now_dir, img_name)) + mmcv.imwrite( + img, + osp.join( + out_dir, 'images', 'training', + osp.splitext(img_name)[0].replace('_training', '') + + '.png')) + + now_dir = osp.join(tmp_dir, 'training', '1st_manual') + for img_name in os.listdir(now_dir): + cap = cv2.VideoCapture(osp.join(now_dir, img_name)) + ret, img = cap.read() + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'training', + osp.splitext(img_name)[0] + '.png')) + + print('Extracting test.zip...') + zip_file = zipfile.ZipFile(testing_path) + zip_file.extractall(tmp_dir) + + print('Generating validation dataset...') + now_dir = osp.join(tmp_dir, 'test', 'images') + for img_name in os.listdir(now_dir): + img = mmcv.imread(osp.join(now_dir, img_name)) + mmcv.imwrite( + img, + osp.join( + out_dir, 'images', 'validation', + osp.splitext(img_name)[0].replace('_test', '') + '.png')) + + now_dir = osp.join(tmp_dir, 'test', '1st_manual') + if osp.exists(now_dir): + for img_name in os.listdir(now_dir): + cap = cv2.VideoCapture(osp.join(now_dir, img_name)) + ret, img = cap.read() + # The annotation img should be divided by 128, because some of + # the annotation imgs are not standard. We should set a + # threshold to convert the nonstandard annotation imgs. The + # value divided by 128 is equivalent to '1 if value >= 128 + # else 0' + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'validation', + osp.splitext(img_name)[0] + '.png')) + + now_dir = osp.join(tmp_dir, 'test', '2nd_manual') + if osp.exists(now_dir): + for img_name in os.listdir(now_dir): + cap = cv2.VideoCapture(osp.join(now_dir, img_name)) + ret, img = cap.read() + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'validation', + osp.splitext(img_name)[0] + '.png')) + + print('Removing the temporary files...') + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/hrf.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/hrf.py new file mode 100644 index 0000000..3bfd80c --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/hrf.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp +import tempfile +import zipfile + +import mmcv +from mmengine.utils import mkdir_or_exist + +HRF_LEN = 15 +TRAINING_LEN = 5 + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert HRF dataset to mmsegmentation format') + parser.add_argument('healthy_path', help='the path of healthy.zip') + parser.add_argument( + 'healthy_manualsegm_path', help='the path of healthy_manualsegm.zip') + parser.add_argument('glaucoma_path', help='the path of glaucoma.zip') + parser.add_argument( + 'glaucoma_manualsegm_path', help='the path of glaucoma_manualsegm.zip') + parser.add_argument( + 'diabetic_retinopathy_path', + help='the path of diabetic_retinopathy.zip') + parser.add_argument( + 'diabetic_retinopathy_manualsegm_path', + help='the path of diabetic_retinopathy_manualsegm.zip') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + images_path = [ + args.healthy_path, args.glaucoma_path, args.diabetic_retinopathy_path + ] + annotations_path = [ + args.healthy_manualsegm_path, args.glaucoma_manualsegm_path, + args.diabetic_retinopathy_manualsegm_path + ] + if args.out_dir is None: + out_dir = osp.join('data', 'HRF') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(out_dir) + mkdir_or_exist(osp.join(out_dir, 'images')) + mkdir_or_exist(osp.join(out_dir, 'images', 'training')) + mkdir_or_exist(osp.join(out_dir, 'images', 'validation')) + mkdir_or_exist(osp.join(out_dir, 'annotations')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'training')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'validation')) + + print('Generating images...') + for now_path in images_path: + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + zip_file = zipfile.ZipFile(now_path) + zip_file.extractall(tmp_dir) + + assert len(os.listdir(tmp_dir)) == HRF_LEN, \ + f'len(os.listdir(tmp_dir)) != {HRF_LEN}' + + for filename in sorted(os.listdir(tmp_dir))[:TRAINING_LEN]: + img = mmcv.imread(osp.join(tmp_dir, filename)) + mmcv.imwrite( + img, + osp.join(out_dir, 'images', 'training', + osp.splitext(filename)[0] + '.png')) + for filename in sorted(os.listdir(tmp_dir))[TRAINING_LEN:]: + img = mmcv.imread(osp.join(tmp_dir, filename)) + mmcv.imwrite( + img, + osp.join(out_dir, 'images', 'validation', + osp.splitext(filename)[0] + '.png')) + + print('Generating annotations...') + for now_path in annotations_path: + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + zip_file = zipfile.ZipFile(now_path) + zip_file.extractall(tmp_dir) + + assert len(os.listdir(tmp_dir)) == HRF_LEN, \ + f'len(os.listdir(tmp_dir)) != {HRF_LEN}' + + for filename in sorted(os.listdir(tmp_dir))[:TRAINING_LEN]: + img = mmcv.imread(osp.join(tmp_dir, filename)) + # The annotation img should be divided by 128, because some of + # the annotation imgs are not standard. We should set a + # threshold to convert the nonstandard annotation imgs. The + # value divided by 128 is equivalent to '1 if value >= 128 + # else 0' + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'training', + osp.splitext(filename)[0] + '.png')) + for filename in sorted(os.listdir(tmp_dir))[TRAINING_LEN:]: + img = mmcv.imread(osp.join(tmp_dir, filename)) + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'validation', + osp.splitext(filename)[0] + '.png')) + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/isaid.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/isaid.py new file mode 100644 index 0000000..1d5ccd9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/isaid.py @@ -0,0 +1,246 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import glob +import os +import os.path as osp +import shutil +import tempfile +import zipfile + +import mmcv +import numpy as np +from mmengine.utils import ProgressBar, mkdir_or_exist +from PIL import Image + +iSAID_palette = \ + { + 0: (0, 0, 0), + 1: (0, 0, 63), + 2: (0, 63, 63), + 3: (0, 63, 0), + 4: (0, 63, 127), + 5: (0, 63, 191), + 6: (0, 63, 255), + 7: (0, 127, 63), + 8: (0, 127, 127), + 9: (0, 0, 127), + 10: (0, 0, 191), + 11: (0, 0, 255), + 12: (0, 191, 127), + 13: (0, 127, 191), + 14: (0, 127, 255), + 15: (0, 100, 155) + } + +iSAID_invert_palette = {v: k for k, v in iSAID_palette.items()} + + +def iSAID_convert_from_color(arr_3d, palette=iSAID_invert_palette): + """RGB-color encoding to grayscale labels.""" + arr_2d = np.zeros((arr_3d.shape[0], arr_3d.shape[1]), dtype=np.uint8) + + for c, i in palette.items(): + m = np.all(arr_3d == np.array(c).reshape(1, 1, 3), axis=2) + arr_2d[m] = i + + return arr_2d + + +def slide_crop_image(src_path, out_dir, mode, patch_H, patch_W, overlap): + img = np.asarray(Image.open(src_path).convert('RGB')) + + img_H, img_W, _ = img.shape + + if img_H < patch_H and img_W > patch_W: + + img = mmcv.impad(img, shape=(patch_H, img_W), pad_val=0) + + img_H, img_W, _ = img.shape + + elif img_H > patch_H and img_W < patch_W: + + img = mmcv.impad(img, shape=(img_H, patch_W), pad_val=0) + + img_H, img_W, _ = img.shape + + elif img_H < patch_H and img_W < patch_W: + + img = mmcv.impad(img, shape=(patch_H, patch_W), pad_val=0) + + img_H, img_W, _ = img.shape + + for x in range(0, img_W, patch_W - overlap): + for y in range(0, img_H, patch_H - overlap): + x_str = x + x_end = x + patch_W + if x_end > img_W: + diff_x = x_end - img_W + x_str -= diff_x + x_end = img_W + y_str = y + y_end = y + patch_H + if y_end > img_H: + diff_y = y_end - img_H + y_str -= diff_y + y_end = img_H + + img_patch = img[y_str:y_end, x_str:x_end, :] + img_patch = Image.fromarray(img_patch.astype(np.uint8)) + image = osp.basename(src_path).split('.')[0] + '_' + str( + y_str) + '_' + str(y_end) + '_' + str(x_str) + '_' + str( + x_end) + '.png' + # print(image) + save_path_image = osp.join(out_dir, 'img_dir', mode, str(image)) + img_patch.save(save_path_image, format='BMP') + + +def slide_crop_label(src_path, out_dir, mode, patch_H, patch_W, overlap): + label = mmcv.imread(src_path, channel_order='rgb') + label = iSAID_convert_from_color(label) + img_H, img_W = label.shape + + if img_H < patch_H and img_W > patch_W: + + label = mmcv.impad(label, shape=(patch_H, img_W), pad_val=255) + + img_H = patch_H + + elif img_H > patch_H and img_W < patch_W: + + label = mmcv.impad(label, shape=(img_H, patch_W), pad_val=255) + + img_W = patch_W + + elif img_H < patch_H and img_W < patch_W: + + label = mmcv.impad(label, shape=(patch_H, patch_W), pad_val=255) + + img_H = patch_H + img_W = patch_W + + for x in range(0, img_W, patch_W - overlap): + for y in range(0, img_H, patch_H - overlap): + x_str = x + x_end = x + patch_W + if x_end > img_W: + diff_x = x_end - img_W + x_str -= diff_x + x_end = img_W + y_str = y + y_end = y + patch_H + if y_end > img_H: + diff_y = y_end - img_H + y_str -= diff_y + y_end = img_H + + lab_patch = label[y_str:y_end, x_str:x_end] + lab_patch = Image.fromarray(lab_patch.astype(np.uint8), mode='P') + + image = osp.basename(src_path).split('.')[0].split( + '_')[0] + '_' + str(y_str) + '_' + str(y_end) + '_' + str( + x_str) + '_' + str(x_end) + '_instance_color_RGB' + '.png' + lab_patch.save(osp.join(out_dir, 'ann_dir', mode, str(image))) + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert iSAID dataset to mmsegmentation format') + parser.add_argument('dataset_path', help='iSAID folder path') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + + parser.add_argument( + '--patch_width', + default=896, + type=int, + help='Width of the cropped image patch') + parser.add_argument( + '--patch_height', + default=896, + type=int, + help='Height of the cropped image patch') + parser.add_argument( + '--overlap_area', default=384, type=int, help='Overlap area') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + dataset_path = args.dataset_path + # image patch width and height + patch_H, patch_W = args.patch_width, args.patch_height + + overlap = args.overlap_area # overlap area + + if args.out_dir is None: + out_dir = osp.join('data', 'iSAID') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'val')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'test')) + + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'val')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'test')) + + assert os.path.exists(os.path.join(dataset_path, 'train')), \ + f'train is not in {dataset_path}' + assert os.path.exists(os.path.join(dataset_path, 'val')), \ + f'val is not in {dataset_path}' + assert os.path.exists(os.path.join(dataset_path, 'test')), \ + f'test is not in {dataset_path}' + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + for dataset_mode in ['train', 'val', 'test']: + + # for dataset_mode in [ 'test']: + print(f'Extracting {dataset_mode}ing.zip...') + img_zipp_list = glob.glob( + os.path.join(dataset_path, dataset_mode, 'images', '*.zip')) + print('Find the data', img_zipp_list) + for img_zipp in img_zipp_list: + zip_file = zipfile.ZipFile(img_zipp) + zip_file.extractall(os.path.join(tmp_dir, dataset_mode, 'img')) + src_path_list = glob.glob( + os.path.join(tmp_dir, dataset_mode, 'img', 'images', '*.png')) + + src_prog_bar = ProgressBar(len(src_path_list)) + for i, img_path in enumerate(src_path_list): + if dataset_mode != 'test': + slide_crop_image(img_path, out_dir, dataset_mode, patch_H, + patch_W, overlap) + + else: + shutil.move(img_path, + os.path.join(out_dir, 'img_dir', dataset_mode)) + src_prog_bar.update() + + if dataset_mode != 'test': + label_zipp_list = glob.glob( + os.path.join(dataset_path, dataset_mode, 'Semantic_masks', + '*.zip')) + for label_zipp in label_zipp_list: + zip_file = zipfile.ZipFile(label_zipp) + zip_file.extractall( + os.path.join(tmp_dir, dataset_mode, 'lab')) + + lab_path_list = glob.glob( + os.path.join(tmp_dir, dataset_mode, 'lab', 'images', + '*.png')) + lab_prog_bar = ProgressBar(len(lab_path_list)) + for i, lab_path in enumerate(lab_path_list): + slide_crop_label(lab_path, out_dir, dataset_mode, patch_H, + patch_W, overlap) + lab_prog_bar.update() + + print('Removing the temporary files...') + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/levircd.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/levircd.py new file mode 100644 index 0000000..8717f3e --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/levircd.py @@ -0,0 +1,99 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import glob +import math +import os +import os.path as osp + +import mmcv +import numpy as np +from mmengine.utils import ProgressBar + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert levir-cd dataset to mmsegmentation format') + parser.add_argument('--dataset_path', help='potsdam folder path') + parser.add_argument('-o', '--out_dir', help='output path') + parser.add_argument( + '--clip_size', + type=int, + help='clipped size of image after preparation', + default=256) + parser.add_argument( + '--stride_size', + type=int, + help='stride of clipping original images', + default=256) + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + input_folder = args.dataset_path + png_files = glob.glob( + os.path.join(input_folder, '**/*.png'), recursive=True) + output_folder = args.out_dir + prog_bar = ProgressBar(len(png_files)) + for png_file in png_files: + new_path = os.path.join( + output_folder, + os.path.relpath(os.path.dirname(png_file), input_folder)) + os.makedirs(os.path.dirname(new_path), exist_ok=True) + label = False + if 'label' in png_file: + label = True + clip_big_image(png_file, new_path, args, label) + prog_bar.update() + + +def clip_big_image(image_path, clip_save_dir, args, to_label=False): + image = mmcv.imread(image_path) + + h, w, c = image.shape + clip_size = args.clip_size + stride_size = args.stride_size + + num_rows = math.ceil((h - clip_size) / stride_size) if math.ceil( + (h - clip_size) / + stride_size) * stride_size + clip_size >= h else math.ceil( + (h - clip_size) / stride_size) + 1 + num_cols = math.ceil((w - clip_size) / stride_size) if math.ceil( + (w - clip_size) / + stride_size) * stride_size + clip_size >= w else math.ceil( + (w - clip_size) / stride_size) + 1 + + x, y = np.meshgrid(np.arange(num_cols + 1), np.arange(num_rows + 1)) + xmin = x * clip_size + ymin = y * clip_size + + xmin = xmin.ravel() + ymin = ymin.ravel() + xmin_offset = np.where(xmin + clip_size > w, w - xmin - clip_size, + np.zeros_like(xmin)) + ymin_offset = np.where(ymin + clip_size > h, h - ymin - clip_size, + np.zeros_like(ymin)) + boxes = np.stack([ + xmin + xmin_offset, ymin + ymin_offset, + np.minimum(xmin + clip_size, w), + np.minimum(ymin + clip_size, h) + ], + axis=1) + + if to_label: + image[image == 255] = 1 + image = image[:, :, 0] + for box in boxes: + start_x, start_y, end_x, end_y = box + clipped_image = image[start_y:end_y, start_x:end_x] \ + if to_label else image[start_y:end_y, start_x:end_x, :] + idx = osp.basename(image_path).split('.')[0] + mmcv.imwrite( + clipped_image.astype(np.uint8), + osp.join(clip_save_dir, + f'{idx}_{start_x}_{start_y}_{end_x}_{end_y}.png')) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/loveda.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/loveda.py new file mode 100644 index 0000000..5b0ef4b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/loveda.py @@ -0,0 +1,73 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp +import shutil +import tempfile +import zipfile + +from mmengine.utils import mkdir_or_exist + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert LoveDA dataset to mmsegmentation format') + parser.add_argument('dataset_path', help='LoveDA folder path') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + dataset_path = args.dataset_path + if args.out_dir is None: + out_dir = osp.join('data', 'loveDA') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(out_dir) + mkdir_or_exist(osp.join(out_dir, 'img_dir')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'val')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'test')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'val')) + + assert 'Train.zip' in os.listdir(dataset_path), \ + f'Train.zip is not in {dataset_path}' + assert 'Val.zip' in os.listdir(dataset_path), \ + f'Val.zip is not in {dataset_path}' + assert 'Test.zip' in os.listdir(dataset_path), \ + f'Test.zip is not in {dataset_path}' + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + for dataset in ['Train', 'Val', 'Test']: + zip_file = zipfile.ZipFile( + os.path.join(dataset_path, dataset + '.zip')) + zip_file.extractall(tmp_dir) + data_type = dataset.lower() + for location in ['Rural', 'Urban']: + for image_type in ['images_png', 'masks_png']: + if image_type == 'images_png': + dst = osp.join(out_dir, 'img_dir', data_type) + else: + dst = osp.join(out_dir, 'ann_dir', data_type) + if dataset == 'Test' and image_type == 'masks_png': + continue + else: + src_dir = osp.join(tmp_dir, dataset, location, + image_type) + src_lst = os.listdir(src_dir) + for file in src_lst: + shutil.move(osp.join(src_dir, file), dst) + print('Removing the temporary files...') + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/nyu.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/nyu.py new file mode 100644 index 0000000..49e09e7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/nyu.py @@ -0,0 +1,89 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +import shutil +import tempfile +import zipfile + +from mmengine.utils import mkdir_or_exist + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert NYU Depth dataset to mmsegmentation format') + parser.add_argument('raw_data', help='the path of raw data') + parser.add_argument( + '-o', '--out_dir', help='output path', default='./data/nyu') + args = parser.parse_args() + return args + + +def reorganize(raw_data_dir: str, out_dir: str): + """Reorganize NYU Depth dataset files into the required directory + structure. + + Args: + raw_data_dir (str): Path to the raw data directory. + out_dir (str): Output directory for the organized dataset. + """ + + def move_data(data_list, dst_prefix, fname_func): + """Move data files from source to destination directory. + + Args: + data_list (list): List of data file paths. + dst_prefix (str): Prefix to be added to destination paths. + fname_func (callable): Function to process file names + """ + for data_item in data_list: + data_item = data_item.strip().strip('/') + new_item = fname_func(data_item) + shutil.move( + osp.join(raw_data_dir, data_item), + osp.join(out_dir, dst_prefix, new_item)) + + def process_phase(phase): + """Process a dataset phase (e.g., 'train' or 'test').""" + with open(osp.join(raw_data_dir, f'nyu_{phase}.txt')) as f: + data = filter(lambda x: len(x.strip()) > 0, f.readlines()) + data = map(lambda x: x.split()[:2], data) + images, annos = zip(*data) + + move_data(images, f'images/{phase}', + lambda x: x.replace('/rgb', '')) + move_data(annos, f'annotations/{phase}', + lambda x: x.replace('/sync_depth', '')) + + process_phase('train') + process_phase('test') + + +def main(): + args = parse_args() + + print('Making directories...') + mkdir_or_exist(args.out_dir) + for subdir in [ + 'images/train', 'images/test', 'annotations/train', + 'annotations/test' + ]: + mkdir_or_exist(osp.join(args.out_dir, subdir)) + + print('Generating images and annotations...') + + if args.raw_data.endswith('.zip'): + with tempfile.TemporaryDirectory() as tmp_dir: + zip_file = zipfile.ZipFile(args.raw_data) + zip_file.extractall(tmp_dir) + reorganize(osp.join(tmp_dir, 'nyu'), args.out_dir) + else: + assert osp.isdir( + args.raw_data + ), 'the argument --raw-data should be either a zip file or directory.' + reorganize(args.raw_data, args.out_dir) + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/pascal_context.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/pascal_context.py new file mode 100644 index 0000000..a92d1dc --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/pascal_context.py @@ -0,0 +1,87 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from functools import partial + +import numpy as np +from detail import Detail +from mmengine.utils import mkdir_or_exist, track_progress +from PIL import Image + +_mapping = np.sort( + np.array([ + 0, 2, 259, 260, 415, 324, 9, 258, 144, 18, 19, 22, 23, 397, 25, 284, + 158, 159, 416, 33, 162, 420, 454, 295, 296, 427, 44, 45, 46, 308, 59, + 440, 445, 31, 232, 65, 354, 424, 68, 326, 72, 458, 34, 207, 80, 355, + 85, 347, 220, 349, 360, 98, 187, 104, 105, 366, 189, 368, 113, 115 + ])) +_key = np.array(range(len(_mapping))).astype('uint8') + + +def generate_labels(img_id, detail, out_dir): + + def _class_to_index(mask, _mapping, _key): + # assert the values + values = np.unique(mask) + for i in range(len(values)): + assert (values[i] in _mapping) + index = np.digitize(mask.ravel(), _mapping, right=True) + return _key[index].reshape(mask.shape) + + mask = Image.fromarray( + _class_to_index(detail.getMask(img_id), _mapping=_mapping, _key=_key)) + filename = img_id['file_name'] + mask.save(osp.join(out_dir, filename.replace('jpg', 'png'))) + return osp.splitext(osp.basename(filename))[0] + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert PASCAL VOC annotations to mmsegmentation format') + parser.add_argument('devkit_path', help='pascal voc devkit path') + parser.add_argument('json_path', help='annoation json filepath') + parser.add_argument('-o', '--out_dir', help='output path') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + devkit_path = args.devkit_path + if args.out_dir is None: + out_dir = osp.join(devkit_path, 'VOC2010', 'SegmentationClassContext') + else: + out_dir = args.out_dir + json_path = args.json_path + mkdir_or_exist(out_dir) + img_dir = osp.join(devkit_path, 'VOC2010', 'JPEGImages') + + train_detail = Detail(json_path, img_dir, 'train') + train_ids = train_detail.getImgs() + + val_detail = Detail(json_path, img_dir, 'val') + val_ids = val_detail.getImgs() + + mkdir_or_exist( + osp.join(devkit_path, 'VOC2010/ImageSets/SegmentationContext')) + + train_list = track_progress( + partial(generate_labels, detail=train_detail, out_dir=out_dir), + train_ids) + with open( + osp.join(devkit_path, 'VOC2010/ImageSets/SegmentationContext', + 'train.txt'), 'w') as f: + f.writelines(line + '\n' for line in sorted(train_list)) + + val_list = track_progress( + partial(generate_labels, detail=val_detail, out_dir=out_dir), val_ids) + with open( + osp.join(devkit_path, 'VOC2010/ImageSets/SegmentationContext', + 'val.txt'), 'w') as f: + f.writelines(line + '\n' for line in sorted(val_list)) + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/potsdam.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/potsdam.py new file mode 100644 index 0000000..f3c713e --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/potsdam.py @@ -0,0 +1,158 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import glob +import math +import os +import os.path as osp +import tempfile +import zipfile + +import mmcv +import numpy as np +from mmengine.utils import ProgressBar, mkdir_or_exist + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert potsdam dataset to mmsegmentation format') + parser.add_argument('dataset_path', help='potsdam folder path') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + parser.add_argument( + '--clip_size', + type=int, + help='clipped size of image after preparation', + default=512) + parser.add_argument( + '--stride_size', + type=int, + help='stride of clipping original images', + default=256) + args = parser.parse_args() + return args + + +def clip_big_image(image_path, clip_save_dir, args, to_label=False): + # Original image of Potsdam dataset is very large, thus pre-processing + # of them is adopted. Given fixed clip size and stride size to generate + # clipped image, the intersection of width and height is determined. + # For example, given one 5120 x 5120 original image, the clip size is + # 512 and stride size is 256, thus it would generate 20x20 = 400 images + # whose size are all 512x512. + image = mmcv.imread(image_path) + + h, w, c = image.shape + clip_size = args.clip_size + stride_size = args.stride_size + + num_rows = math.ceil((h - clip_size) / stride_size) if math.ceil( + (h - clip_size) / + stride_size) * stride_size + clip_size >= h else math.ceil( + (h - clip_size) / stride_size) + 1 + num_cols = math.ceil((w - clip_size) / stride_size) if math.ceil( + (w - clip_size) / + stride_size) * stride_size + clip_size >= w else math.ceil( + (w - clip_size) / stride_size) + 1 + + x, y = np.meshgrid(np.arange(num_cols + 1), np.arange(num_rows + 1)) + xmin = x * clip_size + ymin = y * clip_size + + xmin = xmin.ravel() + ymin = ymin.ravel() + xmin_offset = np.where(xmin + clip_size > w, w - xmin - clip_size, + np.zeros_like(xmin)) + ymin_offset = np.where(ymin + clip_size > h, h - ymin - clip_size, + np.zeros_like(ymin)) + boxes = np.stack([ + xmin + xmin_offset, ymin + ymin_offset, + np.minimum(xmin + clip_size, w), + np.minimum(ymin + clip_size, h) + ], + axis=1) + + if to_label: + color_map = np.array([[0, 0, 0], [255, 255, 255], [255, 0, 0], + [255, 255, 0], [0, 255, 0], [0, 255, 255], + [0, 0, 255]]) + flatten_v = np.matmul( + image.reshape(-1, c), + np.array([2, 3, 4]).reshape(3, 1)) + out = np.zeros_like(flatten_v) + for idx, class_color in enumerate(color_map): + value_idx = np.matmul(class_color, + np.array([2, 3, 4]).reshape(3, 1)) + out[flatten_v == value_idx] = idx + image = out.reshape(h, w) + + for box in boxes: + start_x, start_y, end_x, end_y = box + clipped_image = image[start_y:end_y, + start_x:end_x] if to_label else image[ + start_y:end_y, start_x:end_x, :] + idx_i, idx_j = osp.basename(image_path).split('_')[2:4] + mmcv.imwrite( + clipped_image.astype(np.uint8), + osp.join( + clip_save_dir, + f'{idx_i}_{idx_j}_{start_x}_{start_y}_{end_x}_{end_y}.png')) + + +def main(): + args = parse_args() + splits = { + 'train': [ + '2_10', '2_11', '2_12', '3_10', '3_11', '3_12', '4_10', '4_11', + '4_12', '5_10', '5_11', '5_12', '6_10', '6_11', '6_12', '6_7', + '6_8', '6_9', '7_10', '7_11', '7_12', '7_7', '7_8', '7_9' + ], + 'val': [ + '5_15', '6_15', '6_13', '3_13', '4_14', '6_14', '5_14', '2_13', + '4_15', '2_14', '5_13', '4_13', '3_14', '7_13' + ] + } + + dataset_path = args.dataset_path + if args.out_dir is None: + out_dir = osp.join('data', 'potsdam') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'val')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'val')) + + zipp_list = glob.glob(os.path.join(dataset_path, '*.zip')) + print('Find the data', zipp_list) + + for zipp in zipp_list: + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + zip_file = zipfile.ZipFile(zipp) + zip_file.extractall(tmp_dir) + src_path_list = glob.glob(os.path.join(tmp_dir, '*.tif')) + if not len(src_path_list): + sub_tmp_dir = os.path.join(tmp_dir, os.listdir(tmp_dir)[0]) + src_path_list = glob.glob(os.path.join(sub_tmp_dir, '*.tif')) + + prog_bar = ProgressBar(len(src_path_list)) + for i, src_path in enumerate(src_path_list): + idx_i, idx_j = osp.basename(src_path).split('_')[2:4] + data_type = 'train' if f'{idx_i}_{idx_j}' in splits[ + 'train'] else 'val' + if 'label' in src_path: + dst_dir = osp.join(out_dir, 'ann_dir', data_type) + clip_big_image(src_path, dst_dir, args, to_label=True) + else: + dst_dir = osp.join(out_dir, 'img_dir', data_type) + clip_big_image(src_path, dst_dir, args, to_label=False) + prog_bar.update() + + print('Removing the temporary files...') + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/refuge.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/refuge.py new file mode 100644 index 0000000..1186866 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/refuge.py @@ -0,0 +1,110 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp +import tempfile +import zipfile + +import mmcv +import numpy as np +from mmengine.utils import mkdir_or_exist + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert REFUGE dataset to mmsegmentation format') + parser.add_argument('--raw_data_root', help='the root path of raw data') + + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + args = parser.parse_args() + return args + + +def extract_img(root: str, + cur_dir: str, + out_dir: str, + mode: str = 'train', + file_type: str = 'img') -> None: + """_summary_ + + Args: + Args: + root (str): root where the extracted data is saved + cur_dir (cur_dir): dir where the zip_file exists + out_dir (str): root dir where the data is saved + + mode (str, optional): Defaults to 'train'. + file_type (str, optional): Defaults to 'img',else to 'mask'. + """ + zip_file = zipfile.ZipFile(cur_dir) + zip_file.extractall(root) + for cur_dir, dirs, files in os.walk(root): + # filter child dirs and directories with "Illustration" and "MACOSX" + if len(dirs) == 0 and \ + cur_dir.split('\\')[-1].find('Illustration') == -1 and \ + cur_dir.find('MACOSX') == -1: + + file_names = [ + file for file in files + if file.endswith('.jpg') or file.endswith('.bmp') + ] + for filename in sorted(file_names): + img = mmcv.imread(osp.join(cur_dir, filename)) + + if file_type == 'annotations': + img = img[:, :, 0] + img[np.where(img == 0)] = 1 + img[np.where(img == 128)] = 2 + img[np.where(img == 255)] = 0 + mmcv.imwrite( + img, + osp.join(out_dir, file_type, mode, + osp.splitext(filename)[0] + '.png')) + + +def main(): + args = parse_args() + + raw_data_root = args.raw_data_root + if args.out_dir is None: + out_dir = osp.join('./data', 'REFUGE') + + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(out_dir) + mkdir_or_exist(osp.join(out_dir, 'images')) + mkdir_or_exist(osp.join(out_dir, 'images', 'training')) + mkdir_or_exist(osp.join(out_dir, 'images', 'validation')) + mkdir_or_exist(osp.join(out_dir, 'images', 'test')) + mkdir_or_exist(osp.join(out_dir, 'annotations')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'training')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'validation')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'test')) + + print('Generating images and annotations...') + # process data from the child dir on the first rank + cur_dir, dirs, files = list(os.walk(raw_data_root))[0] + print('====================') + + files = list(filter(lambda x: x.endswith('.zip'), files)) + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + for file in files: + # search data folders for training,validation,test + mode = list( + filter(lambda x: file.lower().find(x) != -1, + ['training', 'test', 'validation']))[0] + file_root = osp.join(tmp_dir, file[:-4]) + file_type = 'images' if file.find('Anno') == -1 and file.find( + 'GT') == -1 else 'annotations' + extract_img(file_root, osp.join(cur_dir, file), out_dir, mode, + file_type) + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/stare.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/stare.py new file mode 100644 index 0000000..4a23ba4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/stare.py @@ -0,0 +1,167 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import gzip +import os +import os.path as osp +import tarfile +import tempfile + +import mmcv +from mmengine.utils import mkdir_or_exist + +STARE_LEN = 20 +TRAINING_LEN = 10 + + +def un_gz(src, dst): + g_file = gzip.GzipFile(src) + with open(dst, 'wb+') as f: + f.write(g_file.read()) + g_file.close() + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert STARE dataset to mmsegmentation format') + parser.add_argument('image_path', help='the path of stare-images.tar') + parser.add_argument('labels_ah', help='the path of labels-ah.tar') + parser.add_argument('labels_vk', help='the path of labels-vk.tar') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + image_path = args.image_path + labels_ah = args.labels_ah + labels_vk = args.labels_vk + if args.out_dir is None: + out_dir = osp.join('data', 'STARE') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(out_dir) + mkdir_or_exist(osp.join(out_dir, 'images')) + mkdir_or_exist(osp.join(out_dir, 'images', 'training')) + mkdir_or_exist(osp.join(out_dir, 'images', 'validation')) + mkdir_or_exist(osp.join(out_dir, 'annotations')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'training')) + mkdir_or_exist(osp.join(out_dir, 'annotations', 'validation')) + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + mkdir_or_exist(osp.join(tmp_dir, 'gz')) + mkdir_or_exist(osp.join(tmp_dir, 'files')) + + print('Extracting stare-images.tar...') + with tarfile.open(image_path) as f: + f.extractall(osp.join(tmp_dir, 'gz')) + + for filename in os.listdir(osp.join(tmp_dir, 'gz')): + un_gz( + osp.join(tmp_dir, 'gz', filename), + osp.join(tmp_dir, 'files', + osp.splitext(filename)[0])) + + now_dir = osp.join(tmp_dir, 'files') + + assert len(os.listdir(now_dir)) == STARE_LEN, \ + f'len(os.listdir(now_dir)) != {STARE_LEN}' + + for filename in sorted(os.listdir(now_dir))[:TRAINING_LEN]: + img = mmcv.imread(osp.join(now_dir, filename)) + mmcv.imwrite( + img, + osp.join(out_dir, 'images', 'training', + osp.splitext(filename)[0] + '.png')) + + for filename in sorted(os.listdir(now_dir))[TRAINING_LEN:]: + img = mmcv.imread(osp.join(now_dir, filename)) + mmcv.imwrite( + img, + osp.join(out_dir, 'images', 'validation', + osp.splitext(filename)[0] + '.png')) + + print('Removing the temporary files...') + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + mkdir_or_exist(osp.join(tmp_dir, 'gz')) + mkdir_or_exist(osp.join(tmp_dir, 'files')) + + print('Extracting labels-ah.tar...') + with tarfile.open(labels_ah) as f: + f.extractall(osp.join(tmp_dir, 'gz')) + + for filename in os.listdir(osp.join(tmp_dir, 'gz')): + un_gz( + osp.join(tmp_dir, 'gz', filename), + osp.join(tmp_dir, 'files', + osp.splitext(filename)[0])) + + now_dir = osp.join(tmp_dir, 'files') + + assert len(os.listdir(now_dir)) == STARE_LEN, \ + f'len(os.listdir(now_dir)) != {STARE_LEN}' + + for filename in sorted(os.listdir(now_dir))[:TRAINING_LEN]: + img = mmcv.imread(osp.join(now_dir, filename)) + # The annotation img should be divided by 128, because some of + # the annotation imgs are not standard. We should set a threshold + # to convert the nonstandard annotation imgs. The value divided by + # 128 equivalent to '1 if value >= 128 else 0' + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'training', + osp.splitext(filename)[0] + '.png')) + + for filename in sorted(os.listdir(now_dir))[TRAINING_LEN:]: + img = mmcv.imread(osp.join(now_dir, filename)) + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'validation', + osp.splitext(filename)[0] + '.png')) + + print('Removing the temporary files...') + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + mkdir_or_exist(osp.join(tmp_dir, 'gz')) + mkdir_or_exist(osp.join(tmp_dir, 'files')) + + print('Extracting labels-vk.tar...') + with tarfile.open(labels_vk) as f: + f.extractall(osp.join(tmp_dir, 'gz')) + + for filename in os.listdir(osp.join(tmp_dir, 'gz')): + un_gz( + osp.join(tmp_dir, 'gz', filename), + osp.join(tmp_dir, 'files', + osp.splitext(filename)[0])) + + now_dir = osp.join(tmp_dir, 'files') + + assert len(os.listdir(now_dir)) == STARE_LEN, \ + f'len(os.listdir(now_dir)) != {STARE_LEN}' + + for filename in sorted(os.listdir(now_dir))[:TRAINING_LEN]: + img = mmcv.imread(osp.join(now_dir, filename)) + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'training', + osp.splitext(filename)[0] + '.png')) + + for filename in sorted(os.listdir(now_dir))[TRAINING_LEN:]: + img = mmcv.imread(osp.join(now_dir, filename)) + mmcv.imwrite( + img[:, :, 0] // 128, + osp.join(out_dir, 'annotations', 'validation', + osp.splitext(filename)[0] + '.png')) + + print('Removing the temporary files...') + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/synapse.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/synapse.py new file mode 100644 index 0000000..42dac6b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/synapse.py @@ -0,0 +1,155 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp + +import nibabel as nib +import numpy as np +from mmengine.utils import mkdir_or_exist +from PIL import Image + + +def read_files_from_txt(txt_path): + with open(txt_path) as f: + files = f.readlines() + files = [file.strip() for file in files] + return files + + +def read_nii_file(nii_path): + img = nib.load(nii_path).get_fdata() + return img + + +def split_3d_image(img): + c, _, _ = img.shape + res = [] + for i in range(c): + res.append(img[i, :, :]) + return res + + +def label_mapping(label): + """Label mapping from TransUNet paper setting. It only has 9 classes, which + are 'background', 'aorta', 'gallbladder', 'left_kidney', 'right_kidney', + 'liver', 'pancreas', 'spleen', 'stomach', respectively. Other foreground + classes in original dataset are all set to background. + + More details could be found here: https://arxiv.org/abs/2102.04306 + """ + maped_label = np.zeros_like(label) + maped_label[label == 8] = 1 + maped_label[label == 4] = 2 + maped_label[label == 3] = 3 + maped_label[label == 2] = 4 + maped_label[label == 6] = 5 + maped_label[label == 11] = 6 + maped_label[label == 1] = 7 + maped_label[label == 7] = 8 + return maped_label + + +def pares_args(): + parser = argparse.ArgumentParser( + description='Convert synapse dataset to mmsegmentation format') + parser.add_argument( + '--dataset-path', type=str, help='synapse dataset path.') + parser.add_argument( + '--save-path', + default='data/synapse', + type=str, + help='save path of the dataset.') + args = parser.parse_args() + return args + + +def main(): + args = pares_args() + dataset_path = args.dataset_path + save_path = args.save_path + + if not osp.exists(dataset_path): + raise ValueError('The dataset path does not exist. ' + 'Please enter a correct dataset path.') + if not osp.exists(osp.join(dataset_path, 'img')) \ + or not osp.exists(osp.join(dataset_path, 'label')): + raise FileNotFoundError('The dataset structure is incorrect. ' + 'Please check your dataset.') + + train_id = read_files_from_txt(osp.join(dataset_path, 'train.txt')) + train_id = [idx[3:7] for idx in train_id] + + test_id = read_files_from_txt(osp.join(dataset_path, 'val.txt')) + test_id = [idx[3:7] for idx in test_id] + + mkdir_or_exist(osp.join(save_path, 'img_dir/train')) + mkdir_or_exist(osp.join(save_path, 'img_dir/val')) + mkdir_or_exist(osp.join(save_path, 'ann_dir/train')) + mkdir_or_exist(osp.join(save_path, 'ann_dir/val')) + + # It follows data preparation pipeline from here: + # https://github.com/Beckschen/TransUNet/tree/main/datasets + for i, idx in enumerate(train_id): + img_3d = read_nii_file( + osp.join(dataset_path, 'img', 'img' + idx + '.nii.gz')) + label_3d = read_nii_file( + osp.join(dataset_path, 'label', 'label' + idx + '.nii.gz')) + + img_3d = np.clip(img_3d, -125, 275) + img_3d = (img_3d + 125) / 400 + img_3d *= 255 + img_3d = np.transpose(img_3d, [2, 0, 1]) + img_3d = np.flip(img_3d, 2) + + label_3d = np.transpose(label_3d, [2, 0, 1]) + label_3d = np.flip(label_3d, 2) + label_3d = label_mapping(label_3d) + + for c in range(img_3d.shape[0]): + img = img_3d[c] + label = label_3d[c] + + img = Image.fromarray(img).convert('RGB') + label = Image.fromarray(label).convert('L') + img.save( + osp.join( + save_path, 'img_dir/train', 'case' + idx.zfill(4) + + '_slice' + str(c).zfill(3) + '.jpg')) + label.save( + osp.join( + save_path, 'ann_dir/train', 'case' + idx.zfill(4) + + '_slice' + str(c).zfill(3) + '.png')) + + for i, idx in enumerate(test_id): + img_3d = read_nii_file( + osp.join(dataset_path, 'img', 'img' + idx + '.nii.gz')) + label_3d = read_nii_file( + osp.join(dataset_path, 'label', 'label' + idx + '.nii.gz')) + + img_3d = np.clip(img_3d, -125, 275) + img_3d = (img_3d + 125) / 400 + img_3d *= 255 + img_3d = np.transpose(img_3d, [2, 0, 1]) + img_3d = np.flip(img_3d, 2) + + label_3d = np.transpose(label_3d, [2, 0, 1]) + label_3d = np.flip(label_3d, 2) + label_3d = label_mapping(label_3d) + + for c in range(img_3d.shape[0]): + img = img_3d[c] + label = label_3d[c] + + img = Image.fromarray(img).convert('RGB') + label = Image.fromarray(label).convert('L') + img.save( + osp.join( + save_path, 'img_dir/val', 'case' + idx.zfill(4) + + '_slice' + str(c).zfill(3) + '.jpg')) + label.save( + osp.join( + save_path, 'ann_dir/val', 'case' + idx.zfill(4) + + '_slice' + str(c).zfill(3) + '.png')) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/vaihingen.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/vaihingen.py new file mode 100644 index 0000000..db98014 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/vaihingen.py @@ -0,0 +1,156 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import glob +import math +import os +import os.path as osp +import tempfile +import zipfile + +import mmcv +import numpy as np +from mmengine.utils import ProgressBar, mkdir_or_exist + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert vaihingen dataset to mmsegmentation format') + parser.add_argument('dataset_path', help='vaihingen folder path') + parser.add_argument('--tmp_dir', help='path of the temporary directory') + parser.add_argument('-o', '--out_dir', help='output path') + parser.add_argument( + '--clip_size', + type=int, + help='clipped size of image after preparation', + default=512) + parser.add_argument( + '--stride_size', + type=int, + help='stride of clipping original images', + default=256) + args = parser.parse_args() + return args + + +def clip_big_image(image_path, clip_save_dir, to_label=False): + # Original image of Vaihingen dataset is very large, thus pre-processing + # of them is adopted. Given fixed clip size and stride size to generate + # clipped image, the intersection of width and height is determined. + # For example, given one 5120 x 5120 original image, the clip size is + # 512 and stride size is 256, thus it would generate 20x20 = 400 images + # whose size are all 512x512. + image = mmcv.imread(image_path) + + h, w, c = image.shape + cs = args.clip_size + ss = args.stride_size + + num_rows = math.ceil((h - cs) / ss) if math.ceil( + (h - cs) / ss) * ss + cs >= h else math.ceil((h - cs) / ss) + 1 + num_cols = math.ceil((w - cs) / ss) if math.ceil( + (w - cs) / ss) * ss + cs >= w else math.ceil((w - cs) / ss) + 1 + + x, y = np.meshgrid(np.arange(num_cols + 1), np.arange(num_rows + 1)) + xmin = x * cs + ymin = y * cs + + xmin = xmin.ravel() + ymin = ymin.ravel() + xmin_offset = np.where(xmin + cs > w, w - xmin - cs, np.zeros_like(xmin)) + ymin_offset = np.where(ymin + cs > h, h - ymin - cs, np.zeros_like(ymin)) + boxes = np.stack([ + xmin + xmin_offset, ymin + ymin_offset, + np.minimum(xmin + cs, w), + np.minimum(ymin + cs, h) + ], + axis=1) + + if to_label: + color_map = np.array([[0, 0, 0], [255, 255, 255], [255, 0, 0], + [255, 255, 0], [0, 255, 0], [0, 255, 255], + [0, 0, 255]]) + flatten_v = np.matmul( + image.reshape(-1, c), + np.array([2, 3, 4]).reshape(3, 1)) + out = np.zeros_like(flatten_v) + for idx, class_color in enumerate(color_map): + value_idx = np.matmul(class_color, + np.array([2, 3, 4]).reshape(3, 1)) + out[flatten_v == value_idx] = idx + image = out.reshape(h, w) + + for box in boxes: + start_x, start_y, end_x, end_y = box + clipped_image = image[start_y:end_y, + start_x:end_x] if to_label else image[ + start_y:end_y, start_x:end_x, :] + area_idx = osp.basename(image_path).split('_')[3].strip('.tif') + mmcv.imwrite( + clipped_image.astype(np.uint8), + osp.join(clip_save_dir, + f'{area_idx}_{start_x}_{start_y}_{end_x}_{end_y}.png')) + + +def main(): + splits = { + 'train': [ + 'area1', 'area11', 'area13', 'area15', 'area17', 'area21', + 'area23', 'area26', 'area28', 'area3', 'area30', 'area32', + 'area34', 'area37', 'area5', 'area7' + ], + 'val': [ + 'area6', 'area24', 'area35', 'area16', 'area14', 'area22', + 'area10', 'area4', 'area2', 'area20', 'area8', 'area31', 'area33', + 'area27', 'area38', 'area12', 'area29' + ], + } + + dataset_path = args.dataset_path + if args.out_dir is None: + out_dir = osp.join('data', 'vaihingen') + else: + out_dir = args.out_dir + + print('Making directories...') + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'img_dir', 'val')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'train')) + mkdir_or_exist(osp.join(out_dir, 'ann_dir', 'val')) + + zipp_list = glob.glob(os.path.join(dataset_path, '*.zip')) + print('Find the data', zipp_list) + + with tempfile.TemporaryDirectory(dir=args.tmp_dir) as tmp_dir: + for zipp in zipp_list: + zip_file = zipfile.ZipFile(zipp) + zip_file.extractall(tmp_dir) + src_path_list = glob.glob(os.path.join(tmp_dir, '*.tif')) + if 'ISPRS_semantic_labeling_Vaihingen' in zipp: + src_path_list = glob.glob( + os.path.join(os.path.join(tmp_dir, 'top'), '*.tif')) + if 'ISPRS_semantic_labeling_Vaihingen_ground_truth_eroded_COMPLETE' in zipp: # noqa + src_path_list = glob.glob(os.path.join(tmp_dir, '*.tif')) + # delete unused area9 ground truth + for area_ann in src_path_list: + if 'area9' in area_ann: + src_path_list.remove(area_ann) + prog_bar = ProgressBar(len(src_path_list)) + for i, src_path in enumerate(src_path_list): + area_idx = osp.basename(src_path).split('_')[3].strip('.tif') + data_type = 'train' if area_idx in splits['train'] else 'val' + if 'noBoundary' in src_path: + dst_dir = osp.join(out_dir, 'ann_dir', data_type) + clip_big_image(src_path, dst_dir, to_label=True) + else: + dst_dir = osp.join(out_dir, 'img_dir', data_type) + clip_big_image(src_path, dst_dir, to_label=False) + prog_bar.update() + + print('Removing the temporary files...') + + print('Done!') + + +if __name__ == '__main__': + args = parse_args() + main() diff --git a/Seg_All_In_One_MMSeg/tools/dataset_converters/voc_aug.py b/Seg_All_In_One_MMSeg/tools/dataset_converters/voc_aug.py new file mode 100644 index 0000000..a536f42 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dataset_converters/voc_aug.py @@ -0,0 +1,92 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from functools import partial + +import numpy as np +from mmengine.utils import mkdir_or_exist, scandir, track_parallel_progress +from PIL import Image +from scipy.io import loadmat + +AUG_LEN = 10582 + + +def convert_mat(mat_file, in_dir, out_dir): + data = loadmat(osp.join(in_dir, mat_file)) + mask = data['GTcls'][0]['Segmentation'][0].astype(np.uint8) + seg_filename = osp.join(out_dir, mat_file.replace('.mat', '.png')) + Image.fromarray(mask).save(seg_filename, 'PNG') + + +def generate_aug_list(merged_list, excluded_list): + return list(set(merged_list) - set(excluded_list)) + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert PASCAL VOC annotations to mmsegmentation format') + parser.add_argument('devkit_path', help='pascal voc devkit path') + parser.add_argument('aug_path', help='pascal voc aug path') + parser.add_argument('-o', '--out_dir', help='output path') + parser.add_argument( + '--nproc', default=1, type=int, help='number of process') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + devkit_path = args.devkit_path + aug_path = args.aug_path + nproc = args.nproc + if args.out_dir is None: + out_dir = osp.join(devkit_path, 'VOC2012', 'SegmentationClassAug') + else: + out_dir = args.out_dir + mkdir_or_exist(out_dir) + in_dir = osp.join(aug_path, 'dataset', 'cls') + + track_parallel_progress( + partial(convert_mat, in_dir=in_dir, out_dir=out_dir), + list(scandir(in_dir, suffix='.mat')), + nproc=nproc) + + full_aug_list = [] + with open(osp.join(aug_path, 'dataset', 'train.txt')) as f: + full_aug_list += [line.strip() for line in f] + with open(osp.join(aug_path, 'dataset', 'val.txt')) as f: + full_aug_list += [line.strip() for line in f] + + with open( + osp.join(devkit_path, 'VOC2012/ImageSets/Segmentation', + 'train.txt')) as f: + ori_train_list = [line.strip() for line in f] + with open( + osp.join(devkit_path, 'VOC2012/ImageSets/Segmentation', + 'val.txt')) as f: + val_list = [line.strip() for line in f] + + aug_train_list = generate_aug_list(ori_train_list + full_aug_list, + val_list) + assert len(aug_train_list) == AUG_LEN, 'len(aug_train_list) != {}'.format( + AUG_LEN) + + with open( + osp.join(devkit_path, 'VOC2012/ImageSets/Segmentation', + 'trainaug.txt'), 'w') as f: + f.writelines(line + '\n' for line in aug_train_list) + + aug_list = generate_aug_list(full_aug_list, ori_train_list + val_list) + assert len(aug_list) == AUG_LEN - len( + ori_train_list), 'len(aug_list) != {}'.format(AUG_LEN - + len(ori_train_list)) + with open( + osp.join(devkit_path, 'VOC2012/ImageSets/Segmentation', 'aug.txt'), + 'w') as f: + f.writelines(line + '\n' for line in aug_list) + + print('Done!') + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/deployment/pytorch2torchscript.py b/Seg_All_In_One_MMSeg/tools/deployment/pytorch2torchscript.py new file mode 100644 index 0000000..e69e705 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/deployment/pytorch2torchscript.py @@ -0,0 +1,185 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse + +import numpy as np +import torch +import torch._C +import torch.serialization +from mmengine import Config +from mmengine.runner import load_checkpoint +from torch import nn + +from mmseg.models import build_segmentor + +torch.manual_seed(3) + + +def digit_version(version_str): + digit_version = [] + for x in version_str.split('.'): + if x.isdigit(): + digit_version.append(int(x)) + elif x.find('rc') != -1: + patch_version = x.split('rc') + digit_version.append(int(patch_version[0]) - 1) + digit_version.append(int(patch_version[1])) + return digit_version + + +def check_torch_version(): + torch_minimum_version = '1.8.0' + torch_version = digit_version(torch.__version__) + + assert (torch_version >= digit_version(torch_minimum_version)), \ + f'Torch=={torch.__version__} is not support for converting to ' \ + f'torchscript. Please install pytorch>={torch_minimum_version}.' + + +def _convert_batchnorm(module): + module_output = module + if isinstance(module, torch.nn.SyncBatchNorm): + module_output = torch.nn.BatchNorm2d(module.num_features, module.eps, + module.momentum, module.affine, + module.track_running_stats) + if module.affine: + module_output.weight.data = module.weight.data.clone().detach() + module_output.bias.data = module.bias.data.clone().detach() + # keep requires_grad unchanged + module_output.weight.requires_grad = module.weight.requires_grad + module_output.bias.requires_grad = module.bias.requires_grad + module_output.running_mean = module.running_mean + module_output.running_var = module.running_var + module_output.num_batches_tracked = module.num_batches_tracked + for name, child in module.named_children(): + module_output.add_module(name, _convert_batchnorm(child)) + del module + return module_output + + +def _demo_mm_inputs(input_shape, num_classes): + """Create a superset of inputs needed to run test or train batches. + + Args: + input_shape (tuple): + input batch dimensions + num_classes (int): + number of semantic classes + """ + (N, C, H, W) = input_shape + rng = np.random.RandomState(0) + imgs = rng.rand(*input_shape) + segs = rng.randint( + low=0, high=num_classes - 1, size=(N, 1, H, W)).astype(np.uint8) + img_metas = [{ + 'img_shape': (H, W, C), + 'ori_shape': (H, W, C), + 'pad_shape': (H, W, C), + 'filename': '.png', + 'scale_factor': 1.0, + 'flip': False, + } for _ in range(N)] + mm_inputs = { + 'imgs': torch.FloatTensor(imgs).requires_grad_(True), + 'img_metas': img_metas, + 'gt_semantic_seg': torch.LongTensor(segs) + } + return mm_inputs + + +def pytorch2libtorch(model, + input_shape, + show=False, + output_file='tmp.pt', + verify=False): + """Export Pytorch model to TorchScript model and verify the outputs are + same between Pytorch and TorchScript. + + Args: + model (nn.Module): Pytorch model we want to export. + input_shape (tuple): Use this input shape to construct + the corresponding dummy input and execute the model. + show (bool): Whether print the computation graph. Default: False. + output_file (string): The path to where we store the + output TorchScript model. Default: `tmp.pt`. + verify (bool): Whether compare the outputs between + Pytorch and TorchScript. Default: False. + """ + if isinstance(model.decode_head, nn.ModuleList): + num_classes = model.decode_head[-1].num_classes + else: + num_classes = model.decode_head.num_classes + + mm_inputs = _demo_mm_inputs(input_shape, num_classes) + + imgs = mm_inputs.pop('imgs') + + # replace the original forword with forward_dummy + model.forward = model.forward_dummy + model.eval() + traced_model = torch.jit.trace( + model, + example_inputs=imgs, + check_trace=verify, + ) + + if show: + print(traced_model.graph) + + traced_model.save(output_file) + print(f'Successfully exported TorchScript model: {output_file}') + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Convert MMSeg to TorchScript') + parser.add_argument('config', help='test config file path') + parser.add_argument('--checkpoint', help='checkpoint file', default=None) + parser.add_argument( + '--show', action='store_true', help='show TorchScript graph') + parser.add_argument( + '--verify', action='store_true', help='verify the TorchScript model') + parser.add_argument('--output-file', type=str, default='tmp.pt') + parser.add_argument( + '--shape', + type=int, + nargs='+', + default=[512, 512], + help='input image size (height, width)') + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = parse_args() + check_torch_version() + + if len(args.shape) == 1: + input_shape = (1, 3, args.shape[0], args.shape[0]) + elif len(args.shape) == 2: + input_shape = ( + 1, + 3, + ) + tuple(args.shape) + else: + raise ValueError('invalid input shape') + + cfg = Config.fromfile(args.config) + cfg.model.pretrained = None + + # build the model and load checkpoint + cfg.model.train_cfg = None + segmentor = build_segmentor( + cfg.model, train_cfg=None, test_cfg=cfg.get('test_cfg')) + # convert SyncBN to BN + segmentor = _convert_batchnorm(segmentor) + + if args.checkpoint: + load_checkpoint(segmentor, args.checkpoint, map_location='cpu') + + # convert the PyTorch model to LibTorch model + pytorch2libtorch( + segmentor, + input_shape, + show=args.show, + output_file=args.output_file, + verify=args.verify) diff --git a/Seg_All_In_One_MMSeg/tools/dist_test.sh b/Seg_All_In_One_MMSeg/tools/dist_test.sh new file mode 100644 index 0000000..89711fd --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dist_test.sh @@ -0,0 +1,20 @@ +CONFIG=$1 +CHECKPOINT=$2 +GPUS=$3 +NNODES=${NNODES:-1} +NODE_RANK=${NODE_RANK:-0} +PORT=${PORT:-29500} +MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"} + +PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \ +python -m torch.distributed.launch \ + --nnodes=$NNODES \ + --node_rank=$NODE_RANK \ + --master_addr=$MASTER_ADDR \ + --nproc_per_node=$GPUS \ + --master_port=$PORT \ + $(dirname "$0")/test.py \ + $CONFIG \ + $CHECKPOINT \ + --launcher pytorch \ + ${@:4} diff --git a/Seg_All_In_One_MMSeg/tools/dist_train.sh b/Seg_All_In_One_MMSeg/tools/dist_train.sh new file mode 100644 index 0000000..a857df7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/dist_train.sh @@ -0,0 +1,17 @@ +CONFIG=$1 +GPUS=$2 +NNODES=${NNODES:-1} +NODE_RANK=${NODE_RANK:-0} +PORT=${PORT:-29500} +MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"} + +PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \ +python -m torch.distributed.launch \ + --nnodes=$NNODES \ + --node_rank=$NODE_RANK \ + --master_addr=$MASTER_ADDR \ + --nproc_per_node=$GPUS \ + --master_port=$PORT \ + $(dirname "$0")/train.py \ + $CONFIG \ + --launcher pytorch ${@:3} diff --git a/Seg_All_In_One_MMSeg/tools/misc/browse_dataset.py b/Seg_All_In_One_MMSeg/tools/misc/browse_dataset.py new file mode 100644 index 0000000..7863eb7 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/misc/browse_dataset.py @@ -0,0 +1,73 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp + +from mmengine import Config, DictAction +from mmengine.registry import init_default_scope +from mmengine.utils import ProgressBar + +from mmseg.registry import DATASETS, VISUALIZERS + + +def parse_args(): + parser = argparse.ArgumentParser(description='Browse a dataset') + parser.add_argument('config', help='train config file path') + parser.add_argument( + '--output-dir', + default=None, + type=str, + help='If there is no display interface, you can save it') + parser.add_argument('--not-show', default=False, action='store_true') + parser.add_argument( + '--show-interval', + type=float, + default=2, + help='the interval of show (s)') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + cfg = Config.fromfile(args.config) + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + # register all modules in mmseg into the registries + init_default_scope('mmseg') + + dataset = DATASETS.build(cfg.train_dataloader.dataset) + cfg.visualizer['save_dir'] = args.output_dir + visualizer = VISUALIZERS.build(cfg.visualizer) + visualizer.dataset_meta = dataset.METAINFO + + progress_bar = ProgressBar(len(dataset)) + for item in dataset: + img = item['inputs'].permute(1, 2, 0).numpy() + data_sample = item['data_samples'].numpy() + img_path = osp.basename(item['data_samples'].img_path) + + img = img[..., [2, 1, 0]] # bgr to rgb + + visualizer.add_datasample( + osp.basename(img_path), + img, + data_sample, + show=not args.not_show, + wait_time=args.show_interval) + + progress_bar.update() + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/misc/print_config.py b/Seg_All_In_One_MMSeg/tools/misc/print_config.py new file mode 100644 index 0000000..2a1c024 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/misc/print_config.py @@ -0,0 +1,69 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import warnings + +from mmengine import Config, DictAction + +from mmseg.apis import init_model + + +def parse_args(): + parser = argparse.ArgumentParser(description='Print the whole config') + parser.add_argument('config', help='config file path') + parser.add_argument( + '--graph', action='store_true', help='print the models graph') + parser.add_argument( + '--options', + nargs='+', + action=DictAction, + help="--options is deprecated in favor of --cfg_options' and it will " + 'not be supported in version v0.22.0. Override some settings in the ' + 'used config, the key-value pair in xxx=yyy format will be merged ' + 'into config file. If the value to be overwritten is a list, it ' + 'should be like key="[a,b]" or key=a,b It also allows nested ' + 'list/tuple values, e.g. key="[(a,b),(c,d)]" Note that the quotation ' + 'marks are necessary and that no white space is allowed.') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + args = parser.parse_args() + + if args.options and args.cfg_options: + raise ValueError( + '--options and --cfg-options cannot be both ' + 'specified, --options is deprecated in favor of --cfg-options. ' + '--options will not be supported in version v0.22.0.') + if args.options: + warnings.warn('--options is deprecated in favor of --cfg-options, ' + '--options will not be supported in version v0.22.0.') + args.cfg_options = args.options + + return args + + +def main(): + args = parse_args() + + cfg = Config.fromfile(args.config) + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + print(f'Config:\n{cfg.pretty_text}') + # dump config + cfg.dump('example.py') + # dump models graph + if args.graph: + model = init_model(args.config, device='cpu') + print(f'Model graph:\n{str(model)}') + with open('example-graph.txt', 'w') as f: + f.writelines(str(model)) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/misc/publish_model.py b/Seg_All_In_One_MMSeg/tools/misc/publish_model.py new file mode 100644 index 0000000..e035ad9 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/misc/publish_model.py @@ -0,0 +1,50 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import subprocess +from hashlib import sha256 + +import torch + +BLOCK_SIZE = 128 * 1024 + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Process a checkpoint to be published') + parser.add_argument('in_file', help='input checkpoint filename') + parser.add_argument('out_file', help='output checkpoint filename') + args = parser.parse_args() + return args + + +def sha256sum(filename: str) -> str: + """Compute SHA256 message digest from a file.""" + hash_func = sha256() + byte_array = bytearray(BLOCK_SIZE) + memory_view = memoryview(byte_array) + with open(filename, 'rb', buffering=0) as file: + for block in iter(lambda: file.readinto(memory_view), 0): + hash_func.update(memory_view[:block]) + return hash_func.hexdigest() + + +def process_checkpoint(in_file, out_file): + checkpoint = torch.load(in_file, map_location='cpu') + # remove optimizer for smaller file size + if 'optimizer' in checkpoint: + del checkpoint['optimizer'] + # if it is necessary to remove some sensitive data in checkpoint['meta'], + # add the code here. + torch.save(checkpoint, out_file) + sha = sha256sum(in_file) + final_file = out_file.rstrip('.pth') + f'-{sha[:8]}.pth' + subprocess.Popen(['mv', out_file, final_file]) + + +def main(): + args = parse_args() + process_checkpoint(args.in_file, args.out_file) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/beit2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/beit2mmseg.py new file mode 100644 index 0000000..20f8f0f --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/beit2mmseg.py @@ -0,0 +1,56 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from collections import OrderedDict + +import mmengine +import torch +from mmengine.runner import CheckpointLoader + + +def convert_beit(ckpt): + new_ckpt = OrderedDict() + + for k, v in ckpt.items(): + if k.startswith('patch_embed'): + new_key = k.replace('patch_embed.proj', 'patch_embed.projection') + new_ckpt[new_key] = v + if k.startswith('blocks'): + new_key = k.replace('blocks', 'layers') + if 'norm' in new_key: + new_key = new_key.replace('norm', 'ln') + elif 'mlp.fc1' in new_key: + new_key = new_key.replace('mlp.fc1', 'ffn.layers.0.0') + elif 'mlp.fc2' in new_key: + new_key = new_key.replace('mlp.fc2', 'ffn.layers.1') + new_ckpt[new_key] = v + else: + new_key = k + new_ckpt[new_key] = v + + return new_ckpt + + +def main(): + parser = argparse.ArgumentParser( + description='Convert keys in official pretrained beit models to' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path or url') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + args = parser.parse_args() + + checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu') + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + weight = convert_beit(state_dict) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(weight, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/clip2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/clip2mmseg.py new file mode 100644 index 0000000..9a97e4b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/clip2mmseg.py @@ -0,0 +1,163 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from collections import OrderedDict + +import mmengine +import torch +from mmengine.runner import CheckpointLoader + + +def convert_vitlayer(paras): + new_para_name = '' + if paras[0] == 'ln_1': + new_para_name = '.'.join(['ln1'] + paras[1:]) + elif paras[0] == 'attn': + new_para_name = '.'.join(['attn.attn'] + paras[1:]) + elif paras[0] == 'ln_2': + new_para_name = '.'.join(['ln2'] + paras[1:]) + elif paras[0] == 'mlp': + if paras[1] == 'c_fc': + new_para_name = '.'.join(['ffn.layers.0.0'] + paras[-1:]) + else: + new_para_name = '.'.join(['ffn.layers.1'] + paras[-1:]) + else: + print(f'Wrong for {paras}') + return new_para_name + + +def convert_translayer(paras): + new_para_name = '' + if paras[0] == 'attn': + new_para_name = '.'.join(['attentions.0.attn'] + paras[1:]) + elif paras[0] == 'ln_1': + new_para_name = '.'.join(['norms.0'] + paras[1:]) + elif paras[0] == 'ln_2': + new_para_name = '.'.join(['norms.1'] + paras[1:]) + elif paras[0] == 'mlp': + if paras[1] == 'c_fc': + new_para_name = '.'.join(['ffns.0.layers.0.0'] + paras[2:]) + elif paras[1] == 'c_proj': + new_para_name = '.'.join(['ffns.0.layers.1'] + paras[2:]) + else: + print(f'Wrong for {paras}') + else: + print(f'Wrong for {paras}') + return new_para_name + + +def convert_key_name(ckpt, visual_split): + new_ckpt = OrderedDict() + for k, v in ckpt.items(): + key_list = k.split('.') + if key_list[0] == 'visual': + new_transform_name = 'image_encoder' + if key_list[1] == 'class_embedding': + new_name = '.'.join([new_transform_name, 'cls_token']) + elif key_list[1] == 'positional_embedding': + new_name = '.'.join([new_transform_name, 'pos_embed']) + elif key_list[1] == 'conv1': + new_name = '.'.join([ + new_transform_name, 'patch_embed.projection', key_list[2] + ]) + elif key_list[1] == 'ln_pre': + new_name = '.'.join( + [new_transform_name, key_list[1], key_list[2]]) + elif key_list[1] == 'transformer': + new_layer_name = 'layers' + layer_index = key_list[3] + paras = key_list[4:] + if int(layer_index) < visual_split: + new_para_name = convert_vitlayer(paras) + new_name = '.'.join([ + new_transform_name, new_layer_name, layer_index, + new_para_name + ]) + else: + new_para_name = convert_translayer(paras) + new_transform_name = 'decode_head.rec_with_attnbias' + new_layer_name = 'layers' + layer_index = str(int(layer_index) - visual_split) + new_name = '.'.join([ + new_transform_name, new_layer_name, layer_index, + new_para_name + ]) + elif key_list[1] == 'proj': + new_name = 'decode_head.rec_with_attnbias.proj.weight' + elif key_list[1] == 'ln_post': + new_name = k.replace('visual', 'decode_head.rec_with_attnbias') + else: + print(f'pop parameter: {k}') + continue + else: + text_encoder_name = 'text_encoder' + if key_list[0] == 'transformer': + layer_name = 'transformer' + layer_index = key_list[2] + paras = key_list[3:] + new_para_name = convert_translayer(paras) + new_name = '.'.join([ + text_encoder_name, layer_name, layer_index, new_para_name + ]) + elif key_list[0] in [ + 'positional_embedding', 'text_projection', 'bg_embed', + 'attn_mask', 'logit_scale', 'token_embedding', 'ln_final' + ]: + new_name = 'text_encoder.' + k + else: + print(f'pop parameter: {k}') + continue + new_ckpt[new_name] = v + + return new_ckpt + + +def convert_tensor(ckpt): + cls_token = ckpt['image_encoder.cls_token'] + new_cls_token = cls_token.unsqueeze(0).unsqueeze(0) + ckpt['image_encoder.cls_token'] = new_cls_token + pos_embed = ckpt['image_encoder.pos_embed'] + new_pos_embed = pos_embed.unsqueeze(0) + ckpt['image_encoder.pos_embed'] = new_pos_embed + proj_weight = ckpt['decode_head.rec_with_attnbias.proj.weight'] + new_proj_weight = proj_weight.transpose(1, 0) + ckpt['decode_head.rec_with_attnbias.proj.weight'] = new_proj_weight + return ckpt + + +def main(): + parser = argparse.ArgumentParser( + description='Convert keys in timm pretrained vit models to ' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path or url') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + args = parser.parse_args() + + if any([s in args.src for s in ['B-16', 'b16', 'base_patch16']]): + visual_split = 9 + elif any([s in args.src for s in ['L-14', 'l14', 'large_patch14']]): + visual_split = 18 + else: + print('Make sure the clip model is ViT-B/16 or ViT-L/14!') + visual_split = -1 + checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu') + if isinstance(checkpoint, torch.jit.RecursiveScriptModule): + state_dict = checkpoint.state_dict() + else: + if 'state_dict' in checkpoint: + # timm checkpoint + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + # deit checkpoint + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + weight = convert_key_name(state_dict, visual_split) + weight = convert_tensor(weight) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(weight, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/mit2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/mit2mmseg.py new file mode 100644 index 0000000..f10cbbf --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/mit2mmseg.py @@ -0,0 +1,82 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from collections import OrderedDict + +import mmengine +import torch +from mmengine.runner import CheckpointLoader + + +def convert_mit(ckpt): + new_ckpt = OrderedDict() + # Process the concat between q linear weights and kv linear weights + for k, v in ckpt.items(): + if k.startswith('head'): + continue + # patch embedding conversion + elif k.startswith('patch_embed'): + stage_i = int(k.split('.')[0].replace('patch_embed', '')) + new_k = k.replace(f'patch_embed{stage_i}', f'layers.{stage_i-1}.0') + new_v = v + if 'proj.' in new_k: + new_k = new_k.replace('proj.', 'projection.') + # transformer encoder layer conversion + elif k.startswith('block'): + stage_i = int(k.split('.')[0].replace('block', '')) + new_k = k.replace(f'block{stage_i}', f'layers.{stage_i-1}.1') + new_v = v + if 'attn.q.' in new_k: + sub_item_k = k.replace('q.', 'kv.') + new_k = new_k.replace('q.', 'attn.in_proj_') + new_v = torch.cat([v, ckpt[sub_item_k]], dim=0) + elif 'attn.kv.' in new_k: + continue + elif 'attn.proj.' in new_k: + new_k = new_k.replace('proj.', 'attn.out_proj.') + elif 'attn.sr.' in new_k: + new_k = new_k.replace('sr.', 'sr.') + elif 'mlp.' in new_k: + string = f'{new_k}-' + new_k = new_k.replace('mlp.', 'ffn.layers.') + if 'fc1.weight' in new_k or 'fc2.weight' in new_k: + new_v = v.reshape((*v.shape, 1, 1)) + new_k = new_k.replace('fc1.', '0.') + new_k = new_k.replace('dwconv.dwconv.', '1.') + new_k = new_k.replace('fc2.', '4.') + string += f'{new_k} {v.shape}-{new_v.shape}' + # norm layer conversion + elif k.startswith('norm'): + stage_i = int(k.split('.')[0].replace('norm', '')) + new_k = k.replace(f'norm{stage_i}', f'layers.{stage_i-1}.2') + new_v = v + else: + new_k = k + new_v = v + new_ckpt[new_k] = new_v + return new_ckpt + + +def main(): + parser = argparse.ArgumentParser( + description='Convert keys in official pretrained segformer to ' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path or url') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + args = parser.parse_args() + + checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu') + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + weight = convert_mit(state_dict) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(weight, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/san2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/san2mmseg.py new file mode 100644 index 0000000..301a466 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/san2mmseg.py @@ -0,0 +1,220 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from collections import OrderedDict + +import mmengine +import torch +from mmengine.runner import CheckpointLoader + + +def convert_key_name(ckpt): + new_ckpt = OrderedDict() + + for k, v in ckpt.items(): + key_list = k.split('.') + if key_list[0] == 'clip_visual_extractor': + new_transform_name = 'image_encoder' + if key_list[1] == 'class_embedding': + new_name = '.'.join([new_transform_name, 'cls_token']) + elif key_list[1] == 'positional_embedding': + new_name = '.'.join([new_transform_name, 'pos_embed']) + elif key_list[1] == 'conv1': + new_name = '.'.join([ + new_transform_name, 'patch_embed.projection', key_list[2] + ]) + elif key_list[1] == 'ln_pre': + new_name = '.'.join( + [new_transform_name, key_list[1], key_list[2]]) + elif key_list[1] == 'resblocks': + new_layer_name = 'layers' + layer_index = key_list[2] + paras = key_list[3:] + if paras[0] == 'ln_1': + new_para_name = '.'.join(['ln1'] + key_list[4:]) + elif paras[0] == 'attn': + new_para_name = '.'.join(['attn.attn'] + key_list[4:]) + elif paras[0] == 'ln_2': + new_para_name = '.'.join(['ln2'] + key_list[4:]) + elif paras[0] == 'mlp': + if paras[1] == 'c_fc': + new_para_name = '.'.join(['ffn.layers.0.0'] + + key_list[-1:]) + else: + new_para_name = '.'.join(['ffn.layers.1'] + + key_list[-1:]) + new_name = '.'.join([ + new_transform_name, new_layer_name, layer_index, + new_para_name + ]) + elif key_list[0] == 'side_adapter_network': + decode_head_name = 'decode_head' + module_name = 'side_adapter_network' + if key_list[1] == 'vit_model': + if key_list[2] == 'blocks': + layer_name = 'encode_layers' + layer_index = key_list[3] + paras = key_list[4:] + if paras[0] == 'norm1': + new_para_name = '.'.join(['ln1'] + key_list[5:]) + elif paras[0] == 'attn': + new_para_name = '.'.join(key_list[4:]) + new_para_name = new_para_name.replace( + 'attn.qkv.', 'attn.attn.in_proj_') + new_para_name = new_para_name.replace( + 'attn.proj', 'attn.attn.out_proj') + elif paras[0] == 'norm2': + new_para_name = '.'.join(['ln2'] + key_list[5:]) + elif paras[0] == 'mlp': + new_para_name = '.'.join(['ffn'] + key_list[5:]) + new_para_name = new_para_name.replace( + 'fc1', 'layers.0.0') + new_para_name = new_para_name.replace( + 'fc2', 'layers.1') + else: + print(f'Wrong for {k}') + new_name = '.'.join([ + decode_head_name, module_name, layer_name, layer_index, + new_para_name + ]) + elif key_list[2] == 'pos_embed': + new_name = '.'.join( + [decode_head_name, module_name, 'pos_embed']) + elif key_list[2] == 'patch_embed': + new_name = '.'.join([ + decode_head_name, module_name, 'patch_embed', + 'projection', key_list[4] + ]) + else: + print(f'Wrong for {k}') + elif key_list[1] == 'query_embed' or key_list[ + 1] == 'query_pos_embed': + new_name = '.'.join( + [decode_head_name, module_name, key_list[1]]) + elif key_list[1] == 'fusion_layers': + layer_name = 'conv_clips' + layer_index = key_list[2][-1] + paras = '.'.join(key_list[3:]) + new_para_name = paras.replace('input_proj.0', '0') + new_para_name = new_para_name.replace('input_proj.1', '1.conv') + new_name = '.'.join([ + decode_head_name, module_name, layer_name, layer_index, + new_para_name + ]) + elif key_list[1] == 'mask_decoder': + new_name = 'decode_head.' + k + else: + print(f'Wrong for {k}') + elif key_list[0] == 'clip_rec_head': + module_name = 'rec_with_attnbias' + if key_list[1] == 'proj': + new_name = '.'.join( + [decode_head_name, module_name, 'proj.weight']) + elif key_list[1] == 'ln_post': + new_name = '.'.join( + [decode_head_name, module_name, 'ln_post', key_list[2]]) + elif key_list[1] == 'resblocks': + new_layer_name = 'layers' + layer_index = key_list[2] + paras = key_list[3:] + if paras[0] == 'ln_1': + new_para_name = '.'.join(['norms.0'] + paras[1:]) + elif paras[0] == 'attn': + new_para_name = '.'.join(['attentions.0.attn'] + paras[1:]) + elif paras[0] == 'ln_2': + new_para_name = '.'.join(['norms.1'] + paras[1:]) + elif paras[0] == 'mlp': + if paras[1] == 'c_fc': + new_para_name = '.'.join(['ffns.0.layers.0.0'] + + paras[2:]) + elif paras[1] == 'c_proj': + new_para_name = '.'.join(['ffns.0.layers.1'] + + paras[2:]) + else: + print(f'Wrong for {k}') + new_name = '.'.join([ + decode_head_name, module_name, new_layer_name, layer_index, + new_para_name + ]) + else: + print(f'Wrong for {k}') + elif key_list[0] == 'ov_classifier': + text_encoder_name = 'text_encoder' + if key_list[1] == 'transformer': + layer_name = 'transformer' + layer_index = key_list[3] + paras = key_list[4:] + if paras[0] == 'attn': + new_para_name = '.'.join(['attentions.0.attn'] + paras[1:]) + elif paras[0] == 'ln_1': + new_para_name = '.'.join(['norms.0'] + paras[1:]) + elif paras[0] == 'ln_2': + new_para_name = '.'.join(['norms.1'] + paras[1:]) + elif paras[0] == 'mlp': + if paras[1] == 'c_fc': + new_para_name = '.'.join(['ffns.0.layers.0.0'] + + paras[2:]) + elif paras[1] == 'c_proj': + new_para_name = '.'.join(['ffns.0.layers.1'] + + paras[2:]) + else: + print(f'Wrong for {k}') + else: + print(f'Wrong for {k}') + new_name = '.'.join([ + text_encoder_name, layer_name, layer_index, new_para_name + ]) + elif key_list[1] in [ + 'positional_embedding', 'text_projection', 'bg_embed', + 'attn_mask', 'logit_scale', 'token_embedding', 'ln_final' + ]: + new_name = k.replace('ov_classifier', 'text_encoder') + else: + print(f'Wrong for {k}') + elif key_list[0] == 'criterion': + new_name = k + else: + print(f'Wrong for {k}') + new_ckpt[new_name] = v + return new_ckpt + + +def convert_tensor(ckpt): + cls_token = ckpt['image_encoder.cls_token'] + new_cls_token = cls_token.unsqueeze(0).unsqueeze(0) + ckpt['image_encoder.cls_token'] = new_cls_token + pos_embed = ckpt['image_encoder.pos_embed'] + new_pos_embed = pos_embed.unsqueeze(0) + ckpt['image_encoder.pos_embed'] = new_pos_embed + proj_weight = ckpt['decode_head.rec_with_attnbias.proj.weight'] + new_proj_weight = proj_weight.transpose(1, 0) + ckpt['decode_head.rec_with_attnbias.proj.weight'] = new_proj_weight + return ckpt + + +def main(): + parser = argparse.ArgumentParser( + description='Convert keys in timm pretrained vit models to ' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path or url') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + args = parser.parse_args() + + checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu') + if 'state_dict' in checkpoint: + # timm checkpoint + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + # deit checkpoint + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + weight = convert_key_name(state_dict) + weight = convert_tensor(weight) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(weight, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/stdc2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/stdc2mmseg.py new file mode 100644 index 0000000..6ea3b83 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/stdc2mmseg.py @@ -0,0 +1,71 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp + +import mmengine +import torch +from mmengine.runner import CheckpointLoader + + +def convert_stdc(ckpt, stdc_type): + new_state_dict = {} + if stdc_type == 'STDC1': + stage_lst = ['0', '1', '2.0', '2.1', '3.0', '3.1', '4.0', '4.1'] + else: + stage_lst = [ + '0', '1', '2.0', '2.1', '2.2', '2.3', '3.0', '3.1', '3.2', '3.3', + '3.4', '4.0', '4.1', '4.2' + ] + for k, v in ckpt.items(): + ori_k = k + flag = False + if 'cp.' in k: + k = k.replace('cp.', '') + if 'features.' in k: + num_layer = int(k.split('.')[1]) + feature_key_lst = 'features.' + str(num_layer) + '.' + stages_key_lst = 'stages.' + stage_lst[num_layer] + '.' + k = k.replace(feature_key_lst, stages_key_lst) + flag = True + if 'conv_list' in k: + k = k.replace('conv_list', 'layers') + flag = True + if 'avd_layer.' in k: + if 'avd_layer.0' in k: + k = k.replace('avd_layer.0', 'downsample.conv') + elif 'avd_layer.1' in k: + k = k.replace('avd_layer.1', 'downsample.bn') + flag = True + if flag: + new_state_dict[k] = ckpt[ori_k] + + return new_state_dict + + +def main(): + parser = argparse.ArgumentParser( + description='Convert keys in official pretrained STDC1/2 to ' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + parser.add_argument('type', help='model type: STDC1 or STDC2') + args = parser.parse_args() + + checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu') + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + + assert args.type in ['STDC1', + 'STDC2'], 'STD type should be STDC1 or STDC2!' + weight = convert_stdc(state_dict, args.type) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(weight, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/swin2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/swin2mmseg.py new file mode 100644 index 0000000..d434f94 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/swin2mmseg.py @@ -0,0 +1,87 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from collections import OrderedDict + +import mmengine +import torch +from mmengine.runner import CheckpointLoader + + +def convert_swin(ckpt): + new_ckpt = OrderedDict() + + def correct_unfold_reduction_order(x): + out_channel, in_channel = x.shape + x = x.reshape(out_channel, 4, in_channel // 4) + x = x[:, [0, 2, 1, 3], :].transpose(1, + 2).reshape(out_channel, in_channel) + return x + + def correct_unfold_norm_order(x): + in_channel = x.shape[0] + x = x.reshape(4, in_channel // 4) + x = x[[0, 2, 1, 3], :].transpose(0, 1).reshape(in_channel) + return x + + for k, v in ckpt.items(): + if k.startswith('head'): + continue + elif k.startswith('layers'): + new_v = v + if 'attn.' in k: + new_k = k.replace('attn.', 'attn.w_msa.') + elif 'mlp.' in k: + if 'mlp.fc1.' in k: + new_k = k.replace('mlp.fc1.', 'ffn.layers.0.0.') + elif 'mlp.fc2.' in k: + new_k = k.replace('mlp.fc2.', 'ffn.layers.1.') + else: + new_k = k.replace('mlp.', 'ffn.') + elif 'downsample' in k: + new_k = k + if 'reduction.' in k: + new_v = correct_unfold_reduction_order(v) + elif 'norm.' in k: + new_v = correct_unfold_norm_order(v) + else: + new_k = k + new_k = new_k.replace('layers', 'stages', 1) + elif k.startswith('patch_embed'): + new_v = v + if 'proj' in k: + new_k = k.replace('proj', 'projection') + else: + new_k = k + else: + new_v = v + new_k = k + + new_ckpt[new_k] = new_v + + return new_ckpt + + +def main(): + parser = argparse.ArgumentParser( + description='Convert keys in official pretrained swin models to' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path or url') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + args = parser.parse_args() + + checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu') + if 'state_dict' in checkpoint: + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + weight = convert_swin(state_dict) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(weight, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/twins2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/twins2mmseg.py new file mode 100644 index 0000000..647d417 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/twins2mmseg.py @@ -0,0 +1,87 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from collections import OrderedDict + +import mmengine +import torch +from mmengine.runner import CheckpointLoader + + +def convert_twins(args, ckpt): + + new_ckpt = OrderedDict() + + for k, v in list(ckpt.items()): + new_v = v + if k.startswith('head'): + continue + elif k.startswith('patch_embeds'): + if 'proj.' in k: + new_k = k.replace('proj.', 'projection.') + else: + new_k = k + elif k.startswith('blocks'): + # Union + if 'attn.q.' in k: + new_k = k.replace('q.', 'attn.in_proj_') + new_v = torch.cat([v, ckpt[k.replace('attn.q.', 'attn.kv.')]], + dim=0) + elif 'mlp.fc1' in k: + new_k = k.replace('mlp.fc1', 'ffn.layers.0.0') + elif 'mlp.fc2' in k: + new_k = k.replace('mlp.fc2', 'ffn.layers.1') + # Only pcpvt + elif args.model == 'pcpvt': + if 'attn.proj.' in k: + new_k = k.replace('proj.', 'attn.out_proj.') + else: + new_k = k + + # Only svt + else: + if 'attn.proj.' in k: + k_lst = k.split('.') + if int(k_lst[2]) % 2 == 1: + new_k = k.replace('proj.', 'attn.out_proj.') + else: + new_k = k + else: + new_k = k + new_k = new_k.replace('blocks.', 'layers.') + elif k.startswith('pos_block'): + new_k = k.replace('pos_block', 'position_encodings') + if 'proj.0.' in new_k: + new_k = new_k.replace('proj.0.', 'proj.') + else: + new_k = k + if 'attn.kv.' not in k: + new_ckpt[new_k] = new_v + return new_ckpt + + +def main(): + parser = argparse.ArgumentParser( + description='Convert keys in timm pretrained vit models to ' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path or url') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + parser.add_argument('model', help='model: pcpvt or svt') + args = parser.parse_args() + + checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu') + + if 'state_dict' in checkpoint: + # timm checkpoint + state_dict = checkpoint['state_dict'] + else: + state_dict = checkpoint + + weight = convert_twins(args, state_dict) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(weight, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/vit2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/vit2mmseg.py new file mode 100644 index 0000000..1d1f8a4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/vit2mmseg.py @@ -0,0 +1,70 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp +from collections import OrderedDict + +import mmengine +import torch +from mmengine.runner import CheckpointLoader + + +def convert_vit(ckpt): + + new_ckpt = OrderedDict() + + for k, v in ckpt.items(): + if k.startswith('head'): + continue + if k.startswith('norm'): + new_k = k.replace('norm.', 'ln1.') + elif k.startswith('patch_embed'): + if 'proj' in k: + new_k = k.replace('proj', 'projection') + else: + new_k = k + elif k.startswith('blocks'): + if 'norm' in k: + new_k = k.replace('norm', 'ln') + elif 'mlp.fc1' in k: + new_k = k.replace('mlp.fc1', 'ffn.layers.0.0') + elif 'mlp.fc2' in k: + new_k = k.replace('mlp.fc2', 'ffn.layers.1') + elif 'attn.qkv' in k: + new_k = k.replace('attn.qkv.', 'attn.attn.in_proj_') + elif 'attn.proj' in k: + new_k = k.replace('attn.proj', 'attn.attn.out_proj') + else: + new_k = k + new_k = new_k.replace('blocks.', 'layers.') + else: + new_k = k + new_ckpt[new_k] = v + + return new_ckpt + + +def main(): + parser = argparse.ArgumentParser( + description='Convert keys in timm pretrained vit models to ' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path or url') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + args = parser.parse_args() + + checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu') + if 'state_dict' in checkpoint: + # timm checkpoint + state_dict = checkpoint['state_dict'] + elif 'model' in checkpoint: + # deit checkpoint + state_dict = checkpoint['model'] + else: + state_dict = checkpoint + weight = convert_vit(state_dict) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(weight, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/model_converters/vitjax2mmseg.py b/Seg_All_In_One_MMSeg/tools/model_converters/vitjax2mmseg.py new file mode 100644 index 0000000..81bc2ea --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/model_converters/vitjax2mmseg.py @@ -0,0 +1,123 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os.path as osp + +import mmengine +import numpy as np +import torch + + +def vit_jax_to_torch(jax_weights, num_layer=12): + torch_weights = dict() + + # patch embedding + conv_filters = jax_weights['embedding/kernel'] + conv_filters = conv_filters.permute(3, 2, 0, 1) + torch_weights['patch_embed.projection.weight'] = conv_filters + torch_weights['patch_embed.projection.bias'] = jax_weights[ + 'embedding/bias'] + + # pos embedding + torch_weights['pos_embed'] = jax_weights[ + 'Transformer/posembed_input/pos_embedding'] + + # cls token + torch_weights['cls_token'] = jax_weights['cls'] + + # head + torch_weights['ln1.weight'] = jax_weights['Transformer/encoder_norm/scale'] + torch_weights['ln1.bias'] = jax_weights['Transformer/encoder_norm/bias'] + + # transformer blocks + for i in range(num_layer): + jax_block = f'Transformer/encoderblock_{i}' + torch_block = f'layers.{i}' + + # attention norm + torch_weights[f'{torch_block}.ln1.weight'] = jax_weights[ + f'{jax_block}/LayerNorm_0/scale'] + torch_weights[f'{torch_block}.ln1.bias'] = jax_weights[ + f'{jax_block}/LayerNorm_0/bias'] + + # attention + query_weight = jax_weights[ + f'{jax_block}/MultiHeadDotProductAttention_1/query/kernel'] + query_bias = jax_weights[ + f'{jax_block}/MultiHeadDotProductAttention_1/query/bias'] + key_weight = jax_weights[ + f'{jax_block}/MultiHeadDotProductAttention_1/key/kernel'] + key_bias = jax_weights[ + f'{jax_block}/MultiHeadDotProductAttention_1/key/bias'] + value_weight = jax_weights[ + f'{jax_block}/MultiHeadDotProductAttention_1/value/kernel'] + value_bias = jax_weights[ + f'{jax_block}/MultiHeadDotProductAttention_1/value/bias'] + + qkv_weight = torch.from_numpy( + np.stack((query_weight, key_weight, value_weight), 1)) + qkv_weight = torch.flatten(qkv_weight, start_dim=1) + qkv_bias = torch.from_numpy( + np.stack((query_bias, key_bias, value_bias), 0)) + qkv_bias = torch.flatten(qkv_bias, start_dim=0) + + torch_weights[f'{torch_block}.attn.attn.in_proj_weight'] = qkv_weight + torch_weights[f'{torch_block}.attn.attn.in_proj_bias'] = qkv_bias + to_out_weight = jax_weights[ + f'{jax_block}/MultiHeadDotProductAttention_1/out/kernel'] + to_out_weight = torch.flatten(to_out_weight, start_dim=0, end_dim=1) + torch_weights[ + f'{torch_block}.attn.attn.out_proj.weight'] = to_out_weight + torch_weights[f'{torch_block}.attn.attn.out_proj.bias'] = jax_weights[ + f'{jax_block}/MultiHeadDotProductAttention_1/out/bias'] + + # mlp norm + torch_weights[f'{torch_block}.ln2.weight'] = jax_weights[ + f'{jax_block}/LayerNorm_2/scale'] + torch_weights[f'{torch_block}.ln2.bias'] = jax_weights[ + f'{jax_block}/LayerNorm_2/bias'] + + # mlp + torch_weights[f'{torch_block}.ffn.layers.0.0.weight'] = jax_weights[ + f'{jax_block}/MlpBlock_3/Dense_0/kernel'] + torch_weights[f'{torch_block}.ffn.layers.0.0.bias'] = jax_weights[ + f'{jax_block}/MlpBlock_3/Dense_0/bias'] + torch_weights[f'{torch_block}.ffn.layers.1.weight'] = jax_weights[ + f'{jax_block}/MlpBlock_3/Dense_1/kernel'] + torch_weights[f'{torch_block}.ffn.layers.1.bias'] = jax_weights[ + f'{jax_block}/MlpBlock_3/Dense_1/bias'] + + # transpose weights + for k, v in torch_weights.items(): + if 'weight' in k and 'patch_embed' not in k and 'ln' not in k: + v = v.permute(1, 0) + torch_weights[k] = v + + return torch_weights + + +def main(): + # stole refactoring code from Robin Strudel, thanks + parser = argparse.ArgumentParser( + description='Convert keys from jax official pretrained vit models to ' + 'MMSegmentation style.') + parser.add_argument('src', help='src model path or url') + # The dst path must be a full path of the new checkpoint. + parser.add_argument('dst', help='save path') + args = parser.parse_args() + + jax_weights = np.load(args.src) + jax_weights_tensor = {} + for key in jax_weights.files: + value = torch.from_numpy(jax_weights[key]) + jax_weights_tensor[key] = value + if 'L_16-i21k' in args.src: + num_layer = 24 + else: + num_layer = 12 + torch_weights = vit_jax_to_torch(jax_weights_tensor, num_layer) + mmengine.mkdir_or_exist(osp.dirname(args.dst)) + torch.save(torch_weights, args.dst) + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/slurm_test.sh b/Seg_All_In_One_MMSeg/tools/slurm_test.sh new file mode 100644 index 0000000..4e6f7bf --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/slurm_test.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -x + +PARTITION=$1 +JOB_NAME=$2 +CONFIG=$3 +CHECKPOINT=$4 +GPUS=${GPUS:-4} +GPUS_PER_NODE=${GPUS_PER_NODE:-4} +CPUS_PER_TASK=${CPUS_PER_TASK:-5} +PY_ARGS=${@:5} +SRUN_ARGS=${SRUN_ARGS:-""} + +PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \ +srun -p ${PARTITION} \ + --job-name=${JOB_NAME} \ + --gres=gpu:${GPUS_PER_NODE} \ + --ntasks=${GPUS} \ + --ntasks-per-node=${GPUS_PER_NODE} \ + --cpus-per-task=${CPUS_PER_TASK} \ + --kill-on-bad-exit=1 \ + ${SRUN_ARGS} \ + python -u tools/test.py ${CONFIG} ${CHECKPOINT} --launcher="slurm" ${PY_ARGS} diff --git a/Seg_All_In_One_MMSeg/tools/slurm_train.sh b/Seg_All_In_One_MMSeg/tools/slurm_train.sh new file mode 100644 index 0000000..ab23210 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/slurm_train.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -x + +PARTITION=$1 +JOB_NAME=$2 +CONFIG=$3 +GPUS=${GPUS:-4} +GPUS_PER_NODE=${GPUS_PER_NODE:-4} +CPUS_PER_TASK=${CPUS_PER_TASK:-5} +SRUN_ARGS=${SRUN_ARGS:-""} +PY_ARGS=${@:4} + +PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \ +srun -p ${PARTITION} \ + --job-name=${JOB_NAME} \ + --gres=gpu:${GPUS_PER_NODE} \ + --ntasks=${GPUS} \ + --ntasks-per-node=${GPUS_PER_NODE} \ + --cpus-per-task=${CPUS_PER_TASK} \ + --kill-on-bad-exit=1 \ + ${SRUN_ARGS} \ + python -u tools/train.py ${CONFIG} --launcher="slurm" ${PY_ARGS} diff --git a/Seg_All_In_One_MMSeg/tools/test.py b/Seg_All_In_One_MMSeg/tools/test.py new file mode 100644 index 0000000..0d7f39b --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/test.py @@ -0,0 +1,123 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import os +import os.path as osp + +from mmengine.config import Config, DictAction +from mmengine.runner import Runner + + +# TODO: support fuse_conv_bn, visualization, and format_only +def parse_args(): + parser = argparse.ArgumentParser( + description='MMSeg test (and eval) a model') + parser.add_argument('config', help='train config file path') + parser.add_argument('checkpoint', help='checkpoint file') + parser.add_argument( + '--work-dir', + help=('if specified, the evaluation metric results will be dumped' + 'into the directory as json')) + parser.add_argument( + '--out', + type=str, + help='The directory to save output prediction for offline evaluation') + parser.add_argument( + '--show', action='store_true', help='show prediction results') + parser.add_argument( + '--show-dir', + help='directory where painted images will be saved. ' + 'If specified, it will be automatically saved ' + 'to the work_dir/timestamp/show_dir') + parser.add_argument( + '--wait-time', type=float, default=2, help='the interval of show (s)') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + parser.add_argument( + '--launcher', + choices=['none', 'pytorch', 'slurm', 'mpi'], + default='none', + help='job launcher') + parser.add_argument( + '--tta', action='store_true', help='Test time augmentation') + # When using PyTorch version >= 2.0.0, the `torch.distributed.launch` + # will pass the `--local-rank` parameter to `tools/train.py` instead + # of `--local_rank`. + parser.add_argument('--local_rank', '--local-rank', type=int, default=0) + args = parser.parse_args() + if 'LOCAL_RANK' not in os.environ: + os.environ['LOCAL_RANK'] = str(args.local_rank) + + return args + + +def trigger_visualization_hook(cfg, args): + default_hooks = cfg.default_hooks + if 'visualization' in default_hooks: + visualization_hook = default_hooks['visualization'] + # Turn on visualization + visualization_hook['draw'] = True + if args.show: + visualization_hook['show'] = True + visualization_hook['wait_time'] = args.wait_time + if args.show_dir: + visualizer = cfg.visualizer + visualizer['save_dir'] = args.show_dir + else: + raise RuntimeError( + 'VisualizationHook must be included in default_hooks.' + 'refer to usage ' + '"visualization=dict(type=\'VisualizationHook\')"') + + return cfg + + +def main(): + args = parse_args() + + # load config + cfg = Config.fromfile(args.config) + cfg.launcher = args.launcher + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + # work_dir is determined in this priority: CLI > segment in file > filename + if args.work_dir is not None: + # update configs according to CLI args if args.work_dir is not None + cfg.work_dir = args.work_dir + elif cfg.get('work_dir', None) is None: + # use config filename as default work_dir if cfg.work_dir is None + cfg.work_dir = osp.join('./work_dirs', + osp.splitext(osp.basename(args.config))[0]) + + cfg.load_from = args.checkpoint + + if args.show or args.show_dir: + cfg = trigger_visualization_hook(cfg, args) + + if args.tta: + cfg.test_dataloader.dataset.pipeline = cfg.tta_pipeline + cfg.tta_model.module = cfg.model + cfg.model = cfg.tta_model + + # add output_dir in metric + if args.out is not None: + cfg.test_evaluator['output_dir'] = args.out + cfg.test_evaluator['keep_results'] = True + + # build the runner from config + runner = Runner.from_cfg(cfg) + + # start testing + runner.test() + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/tools/torchserve/mmseg2torchserve.py b/Seg_All_In_One_MMSeg/tools/torchserve/mmseg2torchserve.py new file mode 100644 index 0000000..23f9963 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/torchserve/mmseg2torchserve.py @@ -0,0 +1,112 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from argparse import ArgumentParser, Namespace +from pathlib import Path +from tempfile import TemporaryDirectory + +from mmengine import Config +from mmengine.utils import mkdir_or_exist + +try: + from model_archiver.model_packaging import package_model + from model_archiver.model_packaging_utils import ModelExportUtils +except ImportError: + package_model = None + + +def mmseg2torchserve( + config_file: str, + checkpoint_file: str, + output_folder: str, + model_name: str, + model_version: str = '1.0', + force: bool = False, +): + """Converts mmsegmentation model (config + checkpoint) to TorchServe + `.mar`. + + Args: + config_file: + In MMSegmentation config format. + The contents vary for each task repository. + checkpoint_file: + In MMSegmentation checkpoint format. + The contents vary for each task repository. + output_folder: + Folder where `{model_name}.mar` will be created. + The file created will be in TorchServe archive format. + model_name: + If not None, used for naming the `{model_name}.mar` file + that will be created under `output_folder`. + If None, `{Path(checkpoint_file).stem}` will be used. + model_version: + Model's version. + force: + If True, if there is an existing `{model_name}.mar` + file under `output_folder` it will be overwritten. + """ + mkdir_or_exist(output_folder) + + config = Config.fromfile(config_file) + + with TemporaryDirectory() as tmpdir: + config.dump(f'{tmpdir}/config.py') + + args = Namespace( + **{ + 'model_file': f'{tmpdir}/config.py', + 'serialized_file': checkpoint_file, + 'handler': f'{Path(__file__).parent}/mmseg_handler.py', + 'model_name': model_name or Path(checkpoint_file).stem, + 'version': model_version, + 'export_path': output_folder, + 'force': force, + 'requirements_file': None, + 'extra_files': None, + 'runtime': 'python', + 'archive_format': 'default' + }) + manifest = ModelExportUtils.generate_manifest_json(args) + package_model(args, manifest) + + +def parse_args(): + parser = ArgumentParser( + description='Convert mmseg models to TorchServe `.mar` format.') + parser.add_argument('config', type=str, help='config file path') + parser.add_argument('checkpoint', type=str, help='checkpoint file path') + parser.add_argument( + '--output-folder', + type=str, + required=True, + help='Folder where `{model_name}.mar` will be created.') + parser.add_argument( + '--model-name', + type=str, + default=None, + help='If not None, used for naming the `{model_name}.mar`' + 'file that will be created under `output_folder`.' + 'If None, `{Path(checkpoint_file).stem}` will be used.') + parser.add_argument( + '--model-version', + type=str, + default='1.0', + help='Number used for versioning.') + parser.add_argument( + '-f', + '--force', + action='store_true', + help='overwrite the existing `{model_name}.mar`') + args = parser.parse_args() + + return args + + +if __name__ == '__main__': + args = parse_args() + + if package_model is None: + raise ImportError('`torch-model-archiver` is required.' + 'Try: pip install torch-model-archiver') + + mmseg2torchserve(args.config, args.checkpoint, args.output_folder, + args.model_name, args.model_version, args.force) diff --git a/Seg_All_In_One_MMSeg/tools/torchserve/mmseg_handler.py b/Seg_All_In_One_MMSeg/tools/torchserve/mmseg_handler.py new file mode 100644 index 0000000..dbe5ded --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/torchserve/mmseg_handler.py @@ -0,0 +1,56 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import base64 +import os + +import cv2 +import mmcv +import torch +from mmengine.model.utils import revert_sync_batchnorm +from ts.torch_handler.base_handler import BaseHandler + +from mmseg.apis import inference_model, init_model + + +class MMsegHandler(BaseHandler): + + def initialize(self, context): + properties = context.system_properties + self.map_location = 'cuda' if torch.cuda.is_available() else 'cpu' + self.device = torch.device(self.map_location + ':' + + str(properties.get('gpu_id')) if torch.cuda. + is_available() else self.map_location) + self.manifest = context.manifest + + model_dir = properties.get('model_dir') + serialized_file = self.manifest['model']['serializedFile'] + checkpoint = os.path.join(model_dir, serialized_file) + self.config_file = os.path.join(model_dir, 'config.py') + + self.model = init_model(self.config_file, checkpoint, self.device) + self.model = revert_sync_batchnorm(self.model) + self.initialized = True + + def preprocess(self, data): + images = [] + + for row in data: + image = row.get('data') or row.get('body') + if isinstance(image, str): + image = base64.b64decode(image) + image = mmcv.imfrombytes(image) + images.append(image) + + return images + + def inference(self, data, *args, **kwargs): + results = [inference_model(self.model, img) for img in data] + return results + + def postprocess(self, data): + output = [] + + for image_result in data: + _, buffer = cv2.imencode('.png', image_result[0].astype('uint8')) + content = buffer.tobytes() + output.append(content) + return output diff --git a/Seg_All_In_One_MMSeg/tools/torchserve/test_torchserve.py b/Seg_All_In_One_MMSeg/tools/torchserve/test_torchserve.py new file mode 100644 index 0000000..b015b66 --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/torchserve/test_torchserve.py @@ -0,0 +1,58 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from argparse import ArgumentParser +from io import BytesIO + +import matplotlib.pyplot as plt +import mmcv +import requests + +from mmseg.apis import inference_model, init_model + + +def parse_args(): + parser = ArgumentParser( + description='Compare result of torchserve and pytorch,' + 'and visualize them.') + parser.add_argument('img', help='Image file') + parser.add_argument('config', help='Config file') + parser.add_argument('checkpoint', help='Checkpoint file') + parser.add_argument('model_name', help='The model name in the server') + parser.add_argument( + '--inference-addr', + default='127.0.0.1:8080', + help='Address and port of the inference server') + parser.add_argument( + '--result-image', + type=str, + default=None, + help='save server output in result-image') + parser.add_argument( + '--device', default='cuda:0', help='Device used for inference') + + args = parser.parse_args() + return args + + +def main(args): + url = 'http://' + args.inference_addr + '/predictions/' + args.model_name + with open(args.img, 'rb') as image: + tmp_res = requests.post(url, image) + content = tmp_res.content + if args.result_image: + with open(args.result_image, 'wb') as out_image: + out_image.write(content) + plt.imshow(mmcv.imread(args.result_image, 'grayscale')) + plt.show() + else: + plt.imshow(plt.imread(BytesIO(content))) + plt.show() + model = init_model(args.config, args.checkpoint, args.device) + image = mmcv.imread(args.img) + result = inference_model(model, image) + plt.imshow(result[0]) + plt.show() + + +if __name__ == '__main__': + args = parse_args() + main(args) diff --git a/Seg_All_In_One_MMSeg/tools/train.py b/Seg_All_In_One_MMSeg/tools/train.py new file mode 100644 index 0000000..be1069f --- /dev/null +++ b/Seg_All_In_One_MMSeg/tools/train.py @@ -0,0 +1,105 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import logging +import os +import os.path as osp + +from mmengine.config import Config, DictAction +from mmengine.logging import print_log +from mmengine.runner import Runner + +from mmseg.registry import RUNNERS + +os.environ['CUDA_LAUNCH_BLOCKING'] = '1' + +def parse_args(): + parser = argparse.ArgumentParser(description='Train a segmentor') + parser.add_argument('config', help='train config file path') + parser.add_argument('--work-dir', help='the dir to save logs and models') + parser.add_argument( + '--resume', + action='store_true', + default=False, + help='resume from the latest checkpoint in the work_dir automatically') + parser.add_argument( + '--amp', + action='store_true', + default=False, + help='enable automatic-mixed-precision training') + parser.add_argument( + '--cfg-options', + nargs='+', + action=DictAction, + help='override some settings in the used config, the key-value pair ' + 'in xxx=yyy format will be merged into config file. If the value to ' + 'be overwritten is a list, it should be like key="[a,b]" or key=a,b ' + 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" ' + 'Note that the quotation marks are necessary and that no white space ' + 'is allowed.') + parser.add_argument( + '--launcher', + choices=['none', 'pytorch', 'slurm', 'mpi'], + default='none', + help='job launcher') + # When using PyTorch version >= 2.0.0, the `torch.distributed.launch` + # will pass the `--local-rank` parameter to `tools/train.py` instead + # of `--local_rank`. + parser.add_argument('--local_rank', '--local-rank', type=int, default=0) + args = parser.parse_args() + if 'LOCAL_RANK' not in os.environ: + os.environ['LOCAL_RANK'] = str(args.local_rank) + + return args + + +def main(): + args = parse_args() + + # load config + cfg = Config.fromfile(args.config) + cfg.launcher = args.launcher + if args.cfg_options is not None: + cfg.merge_from_dict(args.cfg_options) + + # work_dir is determined in this priority: CLI > segment in file > filename + if args.work_dir is not None: + # update configs according to CLI args if args.work_dir is not None + cfg.work_dir = args.work_dir + elif cfg.get('work_dir', None) is None: + # use config filename as default work_dir if cfg.work_dir is None + cfg.work_dir = osp.join('./work_dirs', + osp.splitext(osp.basename(args.config))[0]) + + # enable automatic-mixed-precision training + if args.amp is True: + optim_wrapper = cfg.optim_wrapper.type + if optim_wrapper == 'AmpOptimWrapper': + print_log( + 'AMP training is already enabled in your config.', + logger='current', + level=logging.WARNING) + else: + assert optim_wrapper == 'OptimWrapper', ( + '`--amp` is only supported when the optimizer wrapper type is ' + f'`OptimWrapper` but got {optim_wrapper}.') + cfg.optim_wrapper.type = 'AmpOptimWrapper' + cfg.optim_wrapper.loss_scale = 'dynamic' + + # resume training + cfg.resume = args.resume + + # build the runner from config + if 'runner_type' not in cfg: + # build the default runner + runner = Runner.from_cfg(cfg) + else: + # build customized runner from the registry + # if 'runner_type' is set in the cfg + runner = RUNNERS.build(cfg) + + # start training + runner.train() + + +if __name__ == '__main__': + main() diff --git a/Seg_All_In_One_MMSeg/※使用手册/1_MedSAM环境配置.txt b/Seg_All_In_One_MMSeg/※使用手册/1_MedSAM环境配置.txt new file mode 100644 index 0000000..1b1567e --- /dev/null +++ b/Seg_All_In_One_MMSeg/※使用手册/1_MedSAM环境配置.txt @@ -0,0 +1,20 @@ +################## MedSAM相关 ################## +conda create -n medsam python=3.10 -y # conda remove -n medsam --all +conda activate medsam +git clone https://github.com/bowang-lab/MedSAM +cd MedSAM +pip install -e . +pip install pydicom opencv-python + +进行推理(给定函数)(使用默认数据集): +python MedSAM_Inference.py -i ./data/Test -o ./data/Result -chk work_dir/MedSAM/medsam_20230423_vit_b_0.0.1.pth + +################## 自建数据集相关 ################## + +my_tools/Generate_data_sets_from_dicom.py # 将dicom变为训练文件 -> Annotate_pics.py + +my_tools/Annotate_pics.py # 实现了对于影像的标注 + +################## 自建数据集相关 ################## +Test_ALL_In_One.sh # 实现自动测试 -> Generate_data_sets_from_dicom.py、MedSAM_Inference.py +使用方法:bash Test_ALL_In_One.sh -i -r -s diff --git a/Seg_All_In_One_MMSeg/※使用手册/1_环境配置 b/Seg_All_In_One_MMSeg/※使用手册/1_环境配置 new file mode 100644 index 0000000..edea437 --- /dev/null +++ b/Seg_All_In_One_MMSeg/※使用手册/1_环境配置 @@ -0,0 +1,76 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# 一、环境安装 +# 参考:https://github.com/open-mmlab/mmsegmentation/blob/main/docs/en/get_started.md#installation +conda create --name openmmlab python=3.8 -y +conda activate openmmlab +sudo apt-get install jq # bash解析json工具 +pip install ftfy regex + +# 查看是否有nvidia-smi、Cuda版本,没有的话,安装cuda12.1 +如果没有nvidia-smi需要重新安装驱动 +nvcc --version +下载地址:https://developer.nvidia.com/cuda-12-1-0-download-archive?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=20.04&target_type=runfile_local +sudo sh cuda_12.1.0_530.30.02_linux.run + +# 安装pytorch 安装12.1版本 +官网:https://pytorch.org/get-started/locally/官网:https://pytorch.org/get-started/locally/ +# 检查是否安装成功: +import torch # 如果pytorch安装成功即可导入 +print(torch.cuda.is_available()) # 查看CUDA是否可用 +print(torch.cuda.device_count()) # 查看可用的CUDA数量 +print(torch.version.cuda) # 查看CUDA的版本号 + +# Install MMCV using MIM. +pip install -U openmim +mim install mmengine +pip install mmcv==2.2.0 # -f https://download.openmmlab.com/mmcv/dist/cu121/torch2.4/index.html + +# Install MMSegmentation. +git clone -b main https://github.com/open-mmlab/mmsegmentation.git +cd mmsegmentation +pip install -v -e . + +# Verify the installation +mim download mmsegmentation --config pspnet_r50-d8_4xb2-40k_cityscapes-512x1024 --dest . +python demo/image_demo.py demo/demo.png configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth --device cuda:0 --out-file result.jpg + +# 查看是否有输出,并且删除相关输出内容 +ls | grep result.jpg +rm pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth result.jpg + +### 额外下载 #### +mim install mmdet +sudo apt-get update && sudo apt-get install libgl1 + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# 二、修改内容 +# 1. 实现使用test.py时每隔1个输出 +1.修改tools/test.py中的visualization_hook +在trigger_visualization_hook函数中加入"visualization_hook['interval'] = 1" +_____________________________________________________ +# 2. 修改数据集,训练自己的数据 +1.新增dataset(my_dataset)数据集基本信息 +mmseg/datasets/__init__.py 中新增数据集 +新建 mmseg/datasets/my_dataset.py 可参考stare.py进行构造 +python setup.py install # 进行链接 +2.新增dataset(my_dataset)数据集信息 +新建 configs/_base_/datasets/my_dataset.py 可参考stare.py进行构造 +_____________________________________________________ +# 3. 通过mmseg/apis/inference.py 中的 init_model, inference_model, show_result_pyplot 输出分割图片 +1.创建 tools/save_test_pics.py 程序,在--cfg-options中增加控制参数 + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# 三、附: +# 1.tmux配置 +sudo apt install tmux +echo '# Display color +set -g default-terminal "screen-256color"' | tee ~/.tmux.conf + +2.conda环境重命名 +# 克隆conda环境 +conda create -n openmmlab_old --clone openmmlab +# 删除conda环境 +conda remove -n openmmlab --all diff --git a/Seg_All_In_One_MMSeg/※使用手册/2_报错与解决 b/Seg_All_In_One_MMSeg/※使用手册/2_报错与解决 new file mode 100644 index 0000000..34a4218 --- /dev/null +++ b/Seg_All_In_One_MMSeg/※使用手册/2_报错与解决 @@ -0,0 +1,14 @@ +0.自定义数据集参考: +mmseg/datasets/stare.py +configs/_base_/datasets/stare.py + +1.自定义数据集构建相关问题: +提示’MYDataset is not in the dataset registry’ +python setup.py install # 运行此命令即可解决 +# 参考网站:https://blog.csdn.net/qq_43199876/article/details/128000202 + +2.RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation: [torch.cuda.FloatTensor [2, 512, 27, 27]], which is output 0 of ReluBackward0, is at version 1; expected version 0 instead. +Turns out the uper_head.py code in mmseg/models/decode_heads will throw an error on line 104 due to the +=/-= operation. +# 由于/mmseg/models/XXX有+=/-=操作 +重新运行:python setup.py install # 运行此命令即可解决 +# 参考网站:https://github.com/SwinTransformer/Swin-Transformer-Semantic-Segmentation/issues/60 \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/※使用手册/3_操作手册 b/Seg_All_In_One_MMSeg/※使用手册/3_操作手册 new file mode 100644 index 0000000..ba56c03 --- /dev/null +++ b/Seg_All_In_One_MMSeg/※使用手册/3_操作手册 @@ -0,0 +1,2 @@ +cd Seg_new +bash Train_All_in_one.sh diff --git a/Seg_All_In_One_MMSeg/※使用手册/4_算法相关信息 b/Seg_All_In_One_MMSeg/※使用手册/4_算法相关信息 new file mode 100644 index 0000000..36ef66f --- /dev/null +++ b/Seg_All_In_One_MMSeg/※使用手册/4_算法相关信息 @@ -0,0 +1,8 @@ +# optim_wrapper +# AMP +optim_wrapper = dict( + _delete_=True, + type='AmpOptimWrapper', + optimizer=dict(type='SGD', lr=0.05, momentum=0.9, weight_decay=0.0005), + loss_scale=512.) +bX(e.g.b4/b8)是batchsize \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/※使用手册/MMseg_操作手册.txt b/Seg_All_In_One_MMSeg/※使用手册/MMseg_操作手册.txt new file mode 100644 index 0000000..ef9c092 --- /dev/null +++ b/Seg_All_In_One_MMSeg/※使用手册/MMseg_操作手册.txt @@ -0,0 +1,94 @@ +# 克隆、装环境 +git clone git@github.com:open-mmlab/mmsegmentation.git +cd mmsegmentation +pip install -v -e . + +# 进行测试 +mim download mmsegmentation --config pspnet_r50-d8_512x1024_40k_cityscapes --dest . +python demo/image_demo.py demo/demo.png configs/pspnet/pspnet_r50-d8_512x1024_40k_cityscapes.py pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth --device cuda:0 --out-file result.jpg +# 查看是否可以生成result.jpg +rm pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth result.jpg + +# 激活环境 +conda activate openmmlab + +# 修改各类文件 +文件修改结果请见"configs"、"mmseg" + + +# 训练方式 +tmux new -t train +# 单卡 +python tools/train.py configs/danet/my_danet_r50-d8_512x512_40k_voc12aug.py --work-dir work_dirs/my_danet_r50-d8_512x512_40k_voc12aug_class_2/ +# 多卡 +alg="my_danet_r50-d8_512x512_40k_voc12aug" +tag="_class_3" +mul="_mul" +mkdir ./work_dirs_mul/$alg$tag/ +bash tools/dist_train.sh configs/danet/$alg.py 3 --work-dir ./work_dirs$mul/$alg$tag/ --deterministic + + +# 传输图片 +# 背景为255 +rsync -avtP -e "ssh -p 1005" /home/zub/Desktop/Seg/Pics/Label_Generate_1/* root@temp.2018xjtu.tk:/root/Seg_new/data/my_dataset/ann_dir/validation +rsync -avtP -e "ssh -p 1005" /home/zub/Desktop/Seg/Pics/Label_Generate_1/* root@temp.2018xjtu.tk:/root/Seg_new/data/my_dataset/ann_dir/training +# 背景为0 +rsync -avtP -e "ssh -p 1005" /home/zub/Desktop/Seg/important_Pics/Label_Generate_1/* root@temp.2018xjtu.tk:/root/Seg_new/data/my_dataset/ann_dir/validation +rsync -avtP -e "ssh -p 1005" /home/zub/Desktop/Seg/important_Pics/Label_Generate_1/* root@temp.2018xjtu.tk:/root/Seg_new/data/my_dataset/ann_dir/training +# 原始图片 +rsync -avtP -e "ssh -p 1005" /home/zub/Desktop/Seg/Pics/Ori/* root@temp.2018xjtu.tk:/root/Seg_new/data/my_dataset/img_dir/validation +rsync -avtP -e "ssh -p 1005" /home/zub/Desktop/Seg/Pics/Ori/* root@temp.2018xjtu.tk:/root/Seg_new/data/my_dataset/img_dir/training + +# 测试方式 +# 单卡 +XXX=iter_4000 +best=best_mIoU_iter_12000 +mul="" # 多卡为"_mul" +# 测试某一个模型结果 +rm data/my_dataset/result/$XXX/* +mkdir -p data/my_dataset/result/$XXX/ +python tools/test.py configs/danet/my_danet_r50-d8_512x512_40k_voc12aug.py \ + work_dirs$mul/my_danet_r50-d8_512x512_40k_voc12aug_class_2/$XXX.pth \ + --show-dir data/my_dataset/result/$XXX/ + # --out data/my_dataset/result +# 测试最佳模型结果 +rm data/my_dataset/result/$best/* +mkdir -p data/my_dataset/result/$best/ +python tools/test.py configs/danet/my_danet_r50-d8_512x512_40k_voc12aug.py \ + work_dirs$mul/my_danet_r50-d8_512x512_40k_voc12aug_class_2/$best.pth \ + --show-dir data/my_dataset/result/$best/ + # --out data/my_dataset/result + +# 多卡 +XXX="iter_10000" +alg="my_danet_r50-d8_512x512_40k_voc12aug" +tag="_class_3" +mul="_mul" + +rm data/my_dataset/result/$alg/$XXX$tag/* +mkdir -p data/my_dataset/result/$alg/$XXX$tag/ +python tools/test.py configs/danet/$alg.py \ + work_dirs$mul/$alg/$XXX.pth \ + --show-dir data/my_dataset/result/$alg/$XXX$tag/ --opacity 1 + +XXX="iter_40000" +tag_of_file="_train_199_test_Max" # _test_MAX +alg="my_danet_r50-d8_512x512_40k_voc12aug" +tag="_class_3" +mul="_mul" +# rm data/my_dataset/result/$XXX$tag_of_file/* +mkdir -p data/my_dataset/result/$XXX$tag_of_file/ +python tools/test.py configs/danet/my_danet_r50-d8_512x512_40k_voc12aug.py \ + work_dirs$mul/$alg$tag/$XXX.pth \ + --show-dir data/my_dataset/result/$XXX$tag_of_file/ --opacity 1 + # --out data/my_dataset/result + +tag_of_file="_train_Max_test_Max" # _test_MAX +mkdir -p data/my_dataset/result/my_danet_r50-d8_512x512_40k_voc12aug_class_309_13__02_26_$tag_of_file/ +python tools/test.py configs/danet/my_danet_r50-d8_512x512_40k_voc12aug.py \ + work_dirs_mul/my_danet_r50-d8_512x512_40k_voc12aug_class_309_13__02_26/iter_40000.pth \ + --show-dir data/my_dataset/result/my_danet_r50-d8_512x512_40k_voc12aug_class_309_13__02_26_$tag_of_file/ --opacity 1 + # --out data/my_dataset/result + + + diff --git a/Seg_All_In_One_MMSeg/※使用手册/mmseg数据集生成相关 b/Seg_All_In_One_MMSeg/※使用手册/mmseg数据集生成相关 new file mode 100644 index 0000000..0bdcfc4 --- /dev/null +++ b/Seg_All_In_One_MMSeg/※使用手册/mmseg数据集生成相关 @@ -0,0 +1,65 @@ +_base_ = [ + '../_base_/models/vars_file.alg_base_dir', + '../_base_/datasets/vars_file.dataset_file_name', #换成自己定义的数据集 + '../_base_/default_runtime.py', + '../_base_/schedules/schedule_vars_file.schedule_k_timesk.py' +] +crop_size = (vars_file.crop_size_w, vars_file.crop_size_h) +data_preprocessor = dict(size=crop_size) +model = dict( + pretrained='open-mmlab://vars_file.pretrained_model', + backbone=dict(depth=vars_file.pretrained_depth), + data_preprocessor=data_preprocessor, + decode_head=dict( + num_classes=vars_file.class_num, # TODO 设置不同分类种类 + loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0), # TODO 设置不同分类种类,它根据预测结果和真实标签的重叠区域来度量相似性 + # align_corners=True, + # align_corners=False, # 在不用slide时 + ), + auxiliary_head=dict( + num_classes=vars_file.class_num, # TODO 设置不同分类种类 + loss_decode=dict(type='DiceLoss', use_sigmoid=False, loss_weight=1.0), # TODO 设置不同分类种类,它根据预测结果和真实标签的重叠区域来度量相似性 + # align_corners=True, + # align_corners=False, # 在不用slide时 + ), + # test_cfg=dict(mode='slide', crop_size=(vars_file.crop_size_w, vars_file.crop_size_h), stride=(vars_file.crop_size_w, vars_file.crop_size_w)) +) + +# optimizer(优化器设计)TODO +optim_wrapper = dict( + type='OptimWrapper', + _delete_=True, + optimizer=dict(type='AdamW', lr=0.0001, weight_decay=0.0005), + clip_grad=dict(max_norm=1, norm_type=2)) + +param_scheduler = [ + dict( + type='LinearLR', start_factor=1e-6, by_epoch=False, begin=0, end=1500), + dict( + type='PolyLR', + power=1.0, + begin=1500, + end=160000, + eta_min=0.0, + by_epoch=False, + ) +] + +# optim_wrapper = dict( +# _delete_=True, +# type='OptimWrapper', +# optimizer=dict(type='AdamW', lr=0.0005, weight_decay=0.05), +# clip_grad=dict(max_norm=1, norm_type=2)) +# # learning policy +# param_scheduler = [ +# dict( +# type='LinearLR', start_factor=0.001, by_epoch=False, begin=0, +# end=1000), +# dict( +# type='MultiStepLR', +# begin=1000, +# end=80000, +# by_epoch=False, +# milestones=[60000, 72000], +# ) +# ] \ No newline at end of file diff --git a/Seg_All_In_One_MMSeg/※使用手册/※2025_9_23_MMSeg使用手册 b/Seg_All_In_One_MMSeg/※使用手册/※2025_9_23_MMSeg使用手册 new file mode 100644 index 0000000..e1d4a3c --- /dev/null +++ b/Seg_All_In_One_MMSeg/※使用手册/※2025_9_23_MMSeg使用手册 @@ -0,0 +1,115 @@ +############## A. 创建conda环境 ############## +# 参考:https://github.com/open-mmlab/mmsegmentation/blob/main/docs/en/get_started.md#installation +# 1. 可选 +conda create --name openmmlab python=3.8 -y +conda activate openmmlab + +# 2. 安装必备组件 +sudo apt-get install jq # bash解析json工具 +pip install ftfy regex tensorboard wandb seaborn fvcore +pip install weave -no-compile +wandb login + +# 3. 查看是否有nvidia-smi、Cuda版本,没有的话,安装cuda12.X +如果没有nvidia-smi需要重新安装驱动 +nvcc --version + +# 4. 安装和CUDA 版本对应的 pytorch +官网:https://pytorch.org/get-started/locally/官网:https://pytorch.org/get-started/locally/ +检查是否安装成功: +python +import torch # 如果pytorch安装成功即可导入 +print(torch.cuda.is_available()) # 查看CUDA是否可用 +print(torch.cuda.device_count()) # 查看可用的CUDA数量 +print(torch.version.cuda) # 查看CUDA的版本号 + +# 5. 使用 MIN 安装 MMCV. +pip install -U openmim +mim install mmengine +pip install mmcv==2.2.0 # -f https://download.openmmlab.com/mmcv/dist/cu121/torch2.4/index.html + +# 6. 安装 MMSegmentation. +# git clone -b main https://github.com/open-mmlab/mmsegmentation.git # 不需要下载了 +cd mmsegmentation 或 cd Seg_All_In_One_MMSeg +pip install -v -e . +# V1. 安装无cuda算子的mmcv如果发现mmcv版本超了,需要mmcv<2.2.0 +pip uninstall mmcv-full 或 pip uninstall mmcv +pip install mmcv==2.1.0 +# V2. 安装带有cuda算子的mmcv-full # 参考:https://mmcv.readthedocs.io/en/latest/get_started/build.html +git clone https://github.com/open-mmlab/mmcv.git +cd mmcv +# 可选 +git checkout tags/v2.1.0 # 切换到 2.1.0 版本 +MMCV_WITH_OPS=1 pip install -r requirements/optional.txt # 它告诉编译脚本要构建 mmcv-full 的版本 +pip install -e . -v +python .dev_scripts/check_installation.py # 检查安装是否成功 +### 额外下载 #### +mim install mmdet +sudo apt-get update && sudo apt-get install libgl1 + +# 7. 检查安装是否正确 +# 下载测试数据及代码 +mim download mmsegmentation --config pspnet_r50-d8_4xb2-40k_cityscapes-512x1024 --dest . +python demo/image_demo.py demo/demo.png configs/pspnet/pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth --device cuda:0 --out-file result.jpg +# 查看是否有输出,并且删除相关输出内容 +ls | grep result.jpg +rm pspnet_r50-d8_4xb2-40k_cityscapes-512x1024.py pspnet_r50-d8_512x1024_40k_cityscapes_20200605_003338-2966598c.pth result.jpg + +############## B.0. 定义 Train 训练程序 ############## +1. 观察 ./configs/Alg_name 目录下的算法配置文件,查看算法构成,查看是否有预训练模型 +观察 ./configs/_base_/models/Alg_name.py 的模型文件 +2. 有预训练模型则修改 ./My_All_In_One/0_Initial_Save_All_Model_locally.py 加入下载; +修改 ./My_All_In_One/Initial_Alg_Program/Initial_Alg_Select_Tool.py 加入选项; +3. 参考 ./My_All_In_One/2_Alg_Program 生成属于自己的 my_Alg_name.py 文件 + +############## B.1. 定义 Alg 训练算法 ############## +1. 观察需要修改的是哪个部分./mmseg/models/backbones 或 decode_heads 或 losses 或 necks 等 +2. 将需改后的模型放入文件夹中 +3. 修改其中的 __init__.py 文件,加入对应类名 +4. 在 ./configs/_base_/models 中修改对应算法基础配置 e.g. my_bisenetv2_A1.py +5. 在 ./configs/Alg_name 中修改对应算法配置 e.g. my_bisenetv2_A1_XXX.py +6. python setup.py install # 注册数据集 + +############## B. Train 训练程序 ############## +# 0. 下载必要模型权重 +cd Seg_All_In_One_MMSeg +python 0_Initial_Save_All_Model_locally.py + +# 1. 准备数据集(使用脚本) +cd Seg_All_In_One_MMSeg/1_Data_Parameter +参考重新定义脚本:my_dataset_model.json # 定义脚本 +cd Seg_All_In_One_MMSeg +python 1_Initial_Data_All_data_from_1_Data_Parameter-V2.py +python setup.py install # 注册数据集 +# 1. 自定义数据集(不推荐) +参考:mmseg/datasets/stare.py && configs/_base_/datasets/stare.py +参考:mmseg/datasets/my_dataset_model.py && configs/_base_/datasets/my_dataset_model.py + +# 2. 初始化算法(使用脚本) +python ./My_All_In_One/2_Initial_Alg_All_data_from_2_Alg_Program-V2.py # 会自动输出训练程序 +# 2. 自定义算法(人工定义) +训练执行代码:configs/alg_name/alg_name_XXX.py +底层骨架代码:mmseg/models/backbones/alg_name.py && mmseg/models/backbones/__init__.py +底层schedules代码:mmseg/configs/_base_/schedules +# 新建算法、新建数据集后重新设置索引 +python setup.py install # 运行此命令即可解决 + +# 3. 训练后删除不符合标准的pth(节省空间) +# 修改其中的 target_directory 、 运行两次 +python ./My_All_In_One/3_Find_And_Delete_Special_Epoch.py + +# 4. 训练后移动算法 +bash ./My_All_In_One/3_Tool_Copy_Result_To_Hardisk.sh + +############## C. Predict 推理程序 ############## +# 1. 预测模型 参数量、FLOPs、FPS +CUDA_VISIBLE_DEVICES=0 python My_All_In_One/4_1_predict_params_FLOPs_FPS_V2.py +# 2. 预测模型 指标 +CUDA_VISIBLE_DEVICES=0 python My_All_In_One/4_2_predict_matrics_from_log_V2.py +# 3. 生成模型结果 +CUDA_VISIBLE_DEVICES=0 python My_All_In_One/4_3_predict_draw_pictures_and_tabels.py +# 4. 训练损失摘录 +CUDA_VISIBLE_DEVICES=3 python My_All_In_One/4_4_extract_loss_and_best_miou.py + +############## D. 其他注意事项 ############## +1. 将vis_backends、visualizer相关hook定义放在./My_All_In_One/Initial_Alg_Program/Initial_Alg_Gen_Tool.py 的write_config_to_file函数中了# 批量将参数内容写入文件 # V2 加入训练过程可视化 TODO \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/1_predict.py b/Seg_All_In_One_SegModel/1_predict.py new file mode 100644 index 0000000..e2008ca --- /dev/null +++ b/Seg_All_In_One_SegModel/1_predict.py @@ -0,0 +1,564 @@ +import logging, argparse, sys, utils +import shutil, tempfile +from pathlib import Path +from typing import Dict, List, Tuple + +import albumentations as A +import cv2 +import matplotlib.pyplot as plt +import numpy as np +import segmentation_models_pytorch as smp +import torch +from albumentations.pytorch import ToTensorV2 +from scipy.special import softmax +from sklearn.metrics import (auc, average_precision_score, + precision_recall_curve, roc_curve) +from tqdm import tqdm + +# 本地应用/库的导入 +import config +from utils import log_metrics_to_csv, get_preprocessing_transform, setup_directories + +# --- 日志设置 --- +# 配置日志记录器 +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler()] # 输出日志到控制台 +) +# 为 matplotlib 设置中文字体,请根据您的系统环境选择合适的字体 +plt.rcParams['font.sans-serif'] = ['Noto Sans CJK JP', 'SimHei', 'Microsoft YaHei'] # 例如:SimHei, Microsoft YaHei +plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 + +# --- 核心工具函数 --- + +# 将单通道的类别索引掩码转换为 RGB 彩色掩码 +def colorize_mask(mask: np.ndarray, class_rgb_values: List[Tuple[int, int, int]]) -> np.ndarray: + """将单通道的类别索引掩码转换为 RGB 彩色掩码。""" + color_mask = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8) + # 一个通用的调色板,可以轻松扩展 + palette = [ + (0, 0, 0), (220, 20, 60), (0, 255, 0), (0, 0, 255), + (255, 255, 0), (255, 0, 255), (0, 255, 255), (244, 164, 96), + ] + for class_idx, color in enumerate(palette): + if class_idx < len(class_rgb_values): + # 假设掩码中的像素值 [0, 1, 2...] 对应类别的索引 + pixels_to_color = (mask == class_idx) + color_mask[pixels_to_color] = color + return color_mask + +# --- 分析与可视化函数 --- + +def generate_and_save_curves(y_true: np.ndarray, y_probs: np.ndarray, class_names: List[str], output_dir: Path) -> None: + """ + 根据整个测试集的聚合预测结果,为每个类别生成并保存 ROC 和 PR 曲线。 + """ + logging.info("正在生成 ROC 和 PR 曲线...") + num_classes = len(class_names) + plt.style.use('seaborn-v0_8-whitegrid') + + # --- 准备画布 --- + fig_roc, ax_roc = plt.subplots(figsize=(10, 8)) + ax_roc.plot([0, 1], [0, 1], 'k--', label='随机猜测') + fig_pr, ax_pr = plt.subplots(figsize=(10, 8)) + + # --- 为每个类别(跳过背景)生成曲线 --- + for i in range(1, num_classes): + class_name = class_names[i] + y_true_binary = (y_true == i).astype(int) + y_class_probs = y_probs[:, i] + + # ROC 曲线 + fpr, tpr, _ = roc_curve(y_true_binary, y_class_probs) + roc_auc = auc(fpr, tpr) + ax_roc.plot(fpr, tpr, label=f'类别 {class_name} (AUC = {roc_auc:.4f})') + + # PR 曲线 + precision, recall, _ = precision_recall_curve(y_true_binary, y_class_probs) + avg_precision = average_precision_score(y_true_binary, y_class_probs) + ax_pr.plot(recall, precision, label=f'类别 {class_name} (AP = {avg_precision:.4f})') + + # --- 美化并保存 ROC 曲线图 --- + ax_roc.set(xlabel='False positive rate (FPR)', ylabel='True positive rate (TPR)', title='Receiver operating characteristic (ROC) curve') + ax_roc.legend(loc='lower right') + ax_roc.set_aspect('equal', adjustable='box') + fig_roc.tight_layout() + roc_save_path = output_dir / "ROC_Curves.png" + fig_roc.savefig(roc_save_path) + plt.close(fig_roc) + logging.info(f"-> ROC 曲线图已保存至: {roc_save_path}") + + # --- 美化并保存 PR 曲线图 --- + ax_pr.set(xlabel='Recall', ylabel='Precision', title='Precision-Recall (PR) Curve') + ax_pr.legend(loc='lower left') + ax_pr.set_aspect('equal', adjustable='box') + fig_pr.tight_layout() + pr_save_path = output_dir / "Precision_Recall_Curves.png" + fig_pr.savefig(pr_save_path) + plt.close(fig_pr) + logging.info(f"-> PR 曲线图已保存至: {pr_save_path}") + +def save_visual_comparison( + image_name: str, original_image: np.ndarray, pred_mask: np.ndarray, + gt_mask: np.ndarray = None, stats: Dict = None, stats_text: str = "", + save_dir: str = "" +) -> None: + """保存原始图像、预测掩码以及可选的真实掩码的对比图。""" + pred_color_mask = colorize_mask(pred_mask, config.CLASS_RGB_VALUES) + + if gt_mask is not None and stats is not None: + num_plots = 3 + figsize = (22, 8) + gt_color_mask = colorize_mask(gt_mask, config.CLASS_RGB_VALUES) + save_name = f"{Path(image_name).stem}-IOU_{stats['iou']:.4f}-ACC_{stats['acc']:.4f}.png" + else: + num_plots = 2 + figsize = (14, 6) + save_name = f"{Path(image_name).stem}.png" + + fig, axes = plt.subplots(1, num_plots, figsize=figsize) + fig.suptitle(f"Predict: {image_name}", fontsize=16) + + axes[0].imshow(original_image) + axes[0].set_title("ORI_IMG") + axes[0].axis('off') + + pred_title = "Predicted Mask" + if stats: + pred_title += f"\nIoU: {stats['iou']:.4f} | Acc: {stats['acc']:.4f}" + axes[1].imshow(pred_color_mask) + axes[1].set_title(pred_title) + axes[1].axis('off') + + if num_plots == 3: + axes[2].imshow(gt_color_mask) + axes[2].set_title("Ground Truth") + axes[2].axis('off') + plt.figtext(0.5, 0.01, stats_text, ha="center", fontsize=10, + bbox={"facecolor":"white", "alpha":0.7, "pad":5}, family='monospace') + fig.subplots_adjust(bottom=0.3) + + plt.tight_layout() + plt.savefig(save_dir / save_name) + plt.close(fig) + +# --- 主要逻辑函数 --- + +def load_model(model_architecture: str, model_save_path: Path) -> torch.nn.Module: + """ + 加载已训练的分割模型。 + 此函数经过优化,可以智能处理由 torch.nn.DataParallel 保存的 + (带有 'module.' 前缀) 和常规保存的模型权重。 + """ + logging.info(f"正在从以下路径加载模型: {model_save_path}") + num_classes = len(config.CLASSES) + + # 1. 【修改】根据 config 动态创建基础模型架构 + logging.info(f"正在重建模型架构: '{model_architecture}'") + try: + model_params = config.ALL_MODEL_CONFIGS[model_architecture].copy() # + model_class = getattr(smp, model_architecture) + except AttributeError: + logging.error(f"模型 '{model_architecture}' 在 segmentation_models_pytorch 库中不存在!") + raise + + # 2. 【修改】准备参数,与训练时保持一致 + # 注意:加载权重时,我们不希望再次下载预训练权重,所以设为 None + params = model_params + params['in_channels'] = 3 + params['classes'] = num_classes + params['encoder_weights'] = None # 非常重要!避免在预测时重新下载ImageNet权重 + + # ======================== 【新增代码段开始】 ======================== # + # 自动检测 encoder_name 是否包含 'vit' + encoder_name = model_params.get('encoder_name', '') + if 'vit' in encoder_name.lower(): + params['dynamic_img_size'] = True + logging.info(f"检测到 ViT 编码器 ('{encoder_name}')。自动设置 dynamic_img_size=True。") + # ======================== 【新增代码段结束】 ======================== # + + # 3. 【修改】使用 **kwargs 创建模型实例 + model = model_class(**params).to(config.DEVICE) + + # 4. 从文件加载 state_dict + state_dict = torch.load(model_save_path, map_location=torch.device(config.DEVICE)) + + # 3. 检查是否存在 'module.' 前缀 (判断是否为 DataParallel 保存的权重) + # 我们通过检查字典中任何一个键是否以 'module.' 开头来判断 + is_dataparallel = any(key.startswith('module.') for key in state_dict.keys()) + + if is_dataparallel: + logging.info("检测到模型由 DataParallel 保存,将移除 'module.' 前缀。") + # 创建一个新的、不带前缀的 state_dict + new_state_dict = {key.replace('module.', ''): value for key, value in state_dict.items()} + model.load_state_dict(new_state_dict) + else: + # 如果没有 'module.' 前缀,直接加载 + logging.info("直接加载标准模型权重。") + model.load_state_dict(state_dict) + + model.eval() + return model + +def calculate_and_save_final_metrics(stats_list: List[Dict], num_classes: int, save_dir: str) -> None: + """聚合每张图像的统计数据,计算总体指标,并保存到 CSV 文件。""" + if not stats_list: + logging.warning("未收集到统计数据,跳过最终指标计算。") + return + + logging.info("\n正在聚合结果以计算最终指标...") + # 堆叠所有统计张量并按批次维度求和 + tp = torch.cat([s['tp'] for s in stats_list], dim=0).sum(dim=0) # (N×B, C) -> (C,) + fp = torch.cat([s['fp'] for s in stats_list], dim=0).sum(dim=0) # (N×B, C) -> (C,) + fn = torch.cat([s['fn'] for s in stats_list], dim=0).sum(dim=0) # (N×B, C) -> (C,) + tn = torch.cat([s['tn'] for s in stats_list], dim=0).sum(dim=0) # (N×B, C) -> (C,) + + # ======================== 【新增代码段开始】 ======================== # + # 根据 config.py 中的设置,筛选出用于计算宏观指标的类别 + ignore_indices = [config.CLASSES.index(cls) for cls in config.EVALUATION_CLASSES_TO_IGNORE if cls in config.CLASSES] + + keep_mask = torch.ones(num_classes, dtype=torch.bool, device=tp.device) + if ignore_indices: + keep_mask[ignore_indices] = False + logging.info(f"最终评估时将忽略类别: {config.EVALUATION_CLASSES_TO_IGNORE}") + + tp_filtered = tp[keep_mask] + fp_filtered = fp[keep_mask] + fn_filtered = fn[keep_mask] + tn_filtered = tn[keep_mask] + # ======================== 【新增代码段结束】 ======================== # + + + # ======================== 【修改代码段开始】 ======================== # + # 使用过滤后的统计数据计算总体指标 + metrics = { + "iou_score": smp.metrics.iou_score(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "f1_score": smp.metrics.f1_score(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "accuracy": smp.metrics.accuracy(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "recall": smp.metrics.recall(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "precision": smp.metrics.precision(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + } + # ======================== 【修改代码段结束】 ======================== # + + # 添加每个类别的原始统计数据(使用未经过滤的数据) + for i in range(num_classes): + metrics[f'tp_class_{i}'] = tp[i].item() + metrics[f'tn_class_{i}'] = tn[i].item() + metrics[f'fp_class_{i}'] = fp[i].item() + metrics[f'fn_class_{i}'] = fn[i].item() + + csv_save_path = save_dir / "test_set_metrics.csv" + log_metrics_to_csv(metrics, str(csv_save_path)) + logging.info(f"-> 整体测试集指标已保存至: {csv_save_path}") + +# 2. 【新增】一个帮助函数,用于交互式选择模型目录 +def select_trained_model_path(base_dir: Path, model_architecture: str) -> Path: + """ + 查找指定架构的所有训练运行目录,并让用户选择一个。 + """ + logging.info(f"正在 '{base_dir}' 中搜索模型 '{model_architecture}' 的训练记录...") + + # 查找所有以架构名开头的文件夹 + run_dirs = sorted([d for d in base_dir.iterdir() if d.is_dir() and d.name.startswith(model_architecture+'_')]) + + if not run_dirs: + logging.error(f"未找到任何 '{model_architecture}' 模型的训练记录。请先运行 train.py。") + return None + + print("\n请选择要用于预测的模型:") + for i, dir_path in enumerate(run_dirs): + print(f" [{i+1}] {dir_path.name}") + + while True: + try: + choice = input(f"请输入选项编号 (1-{len(run_dirs)}) 或按 Enter 退出: ") + if not choice: + return None + choice_idx = int(choice) - 1 + if 0 <= choice_idx < len(run_dirs): + selected_dir = run_dirs[choice_idx] + logging.info(f"已选择模型: {selected_dir}") + return selected_dir + else: + print("无效的选项,请重新输入。") + except (ValueError, IndexError): + print("无效的输入,请输入一个数字。") + +# 【修改】函数签名和内部逻辑,不再接收一个列表,而是直接接收聚合后的统计数据 +def calculate_and_save_final_metrics(tp, fp, fn, tn, num_classes: int, save_dir: Path) -> None: + """根据聚合后的统计数据,计算总体指标,并保存到 CSV 文件。""" + logging.info("\n正在计算最终的整体指标...") + + # 根据 config.py 中的设置,筛选出用于计算宏观指标的类别 + ignore_indices = [config.CLASSES.index(cls) for cls in config.EVALUATION_CLASSES_TO_IGNORE if cls in config.CLASSES] + + keep_mask = torch.ones(num_classes, dtype=torch.bool, device=tp.device) + if ignore_indices: + keep_mask[ignore_indices] = False + logging.info(f"最终评估时将忽略类别: {config.EVALUATION_CLASSES_TO_IGNORE}") + + tp_filtered = tp[keep_mask] + fp_filtered = fp[keep_mask] + fn_filtered = fn[keep_mask] + tn_filtered = tn[keep_mask] + + # 使用过滤后的统计数据计算总体指标 + metrics = { + "iou_score": smp.metrics.iou_score(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "f1_score": smp.metrics.f1_score(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "accuracy": smp.metrics.accuracy(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "recall": smp.metrics.recall(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "precision": smp.metrics.precision(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + } + + # 添加每个类别的原始统计数据(使用未经过滤的数据) + for i in range(num_classes): + metrics[f'tp_class_{i}'] = tp[i].item() + metrics[f'tn_class_{i}'] = tn[i].item() + metrics[f'fp_class_{i}'] = fp[i].item() + metrics[f'fn_class_{i}'] = fn[i].item() + + csv_save_path = save_dir / "test_set_metrics.csv" + log_metrics_to_csv(metrics, str(csv_save_path)) + logging.info(f"-> 整体测试集指标已保存至: {csv_save_path}") + +def main(model_architecture: str) -> None: + """ + 主函数,执行模型加载、图像预测、评估,并生成分析结果。 + """ + # --- 1. 【修改】选择模型并动态设置路径 --- + selected_run_dir = select_trained_model_path(Path(config.PREDICT_BEST_MODEL_DIR), model_architecture) + + if not selected_run_dir: + logging.info("未选择任何模型,程序退出。") + sys.exit() + + # 【重要】动态覆写 config 中的路径,使其指向所选模型的目录 + # 这样,后续所有代码都会自动在正确的文件夹中加载模型和保存结果 + best_model_save_path = selected_run_dir / "best_model.pth" + raw_mask_dir = selected_run_dir / "predicted_raw_masks" + analysis_results_dir = selected_run_dir / "prediction_analysis" + + if not best_model_save_path.exists(): + logging.error(f"错误: 在 '{selected_run_dir}' 中未找到 'best_model.pth' 文件!") + sys.exit() + + # setup_directories 函数现在用于创建预测结果的子目录 + utils.setup_directories(raw_mask_dir) + utils.setup_directories(analysis_results_dir) + + model = load_model(model_architecture, best_model_save_path) # 1. 加载模型 + + # ======================== 【新增/修改的代码段开始】 ======================== # + # --- 2. 探测模型以确定正确的图像尺寸 --- + logging.info("正在探测模型以确定预测时所需的输入尺寸...") # + target_height, target_width = config.IMAGE_HEIGHT, config.IMAGE_WIDTH # + + try: + # 检查编码器是否需要固定输入尺寸 + if model.encoder.is_fixed_input_size: + required_size = model.encoder.input_size + target_height = required_size[1] + target_width = required_size[2] + logging.warning( + f"模型 '{model_architecture}' 的编码器需要固定的输入尺寸 " + f"({target_height}x{target_width})。" + f"将忽略 config.py 中的尺寸设置进行预测。" + ) + else: + logging.info(f"模型 '{model_architecture}' 支持动态输入尺寸。将使用 config.py 中定义的尺寸 ({target_height}x{target_width})。") + except AttributeError: + logging.info("无法自动检测模型尺寸要求。将使用 config.py 中定义的尺寸。") + + # --- 3. 获取用于预测的图像预处理流程 --- + # 使用探测到的或配置中指定的目标尺寸 + transform = get_preprocessing_transform(target_height, target_width) # + # ======================== 【新增/修改的代码段结束】 ======================== # + + # --- 4. 获取图像列表并判断模式 --- + image_filenames = sorted(list(config.TEST_IMAGE_DIR.iterdir())) + evaluate_mode = config.TEST_MASK_DIR.is_dir() + + if evaluate_mode: + logging.info("正在以 [评估模式] 运行。将使用真实掩码进行评估。") + else: + logging.info("正在以 [仅预测模式] 运行。未找到真实掩码。") + + # --- 3. 初始化用于增量计算的变量 --- + # 【修改】不再使用 list 来累积,而是直接累加 TP, FP, FN, TN + num_classes = len(config.CLASSES) + total_tp = torch.zeros(num_classes, dtype=torch.long, device=config.DEVICE) + total_fp = torch.zeros(num_classes, dtype=torch.long, device=config.DEVICE) + total_fn = torch.zeros(num_classes, dtype=torch.long, device=config.DEVICE) + total_tn = torch.zeros(num_classes, dtype=torch.long, device=config.DEVICE) + + # 【新增】为 ROC/PR 曲线创建临时文件,用磁盘空间换内存 + # 使用 tempfile 模块来安全地创建临时文件 + temp_gt_file = tempfile.NamedTemporaryFile(delete=False, suffix='.npy') + temp_probs_file = tempfile.NamedTemporaryFile(delete=False, suffix='.npy') + logging.info(f"为ROC/PR曲线创建临时文件: {temp_gt_file.name}, {temp_probs_file.name}") + + # --- 4. 主推理循环 - 预测图片 --- + for image_path in tqdm(image_filenames, desc="正在处理测试图像"): + # --- 4.1. 读取图片并预处理 --- + original_image = cv2.cvtColor(cv2.imread(str(image_path)), cv2.COLOR_BGR2RGB) + original_h, original_w = original_image.shape[:2] + image_tensor = transform(image=original_image)['image'].unsqueeze(0).to(config.DEVICE) + + # --- 4.2. 图片推理并保存 --- + with torch.no_grad(): + pred_logits_tensor = model(image_tensor) + pred_mask_tensor = torch.argmax(pred_logits_tensor, dim=1) + pred_mask_numpy = pred_mask_tensor.squeeze().cpu().numpy().astype(np.uint8) + pred_mask_resized_raw = cv2.resize(pred_mask_numpy, (original_w, original_h), interpolation=cv2.INTER_NEAREST) + + # 保存原始(单通道)的预测掩码 + cv2.imwrite(str(raw_mask_dir / f"{image_path.stem}.png"), pred_mask_resized_raw) + + # --- 4.3. 评估与可视化 --- + gt_mask_numpy, per_image_stats, stats_text_display, gt_mask_raw = None, None, "", None + if evaluate_mode: + mask_path = config.TEST_MASK_DIR / image_path.name + if mask_path.exists(): + gt_mask_raw = cv2.imread(str(mask_path), cv2.IMREAD_GRAYSCALE) + gt_mask_numpy = cv2.resize( + gt_mask_raw, (target_width, target_height), + interpolation=cv2.INTER_NEAREST + ) + gt_mask_tensor = torch.from_numpy(gt_mask_numpy).long().to(config.DEVICE).unsqueeze(0) + + # ======================== 【修复代码段开始】 ======================== # + + # --- 1. [修复] 计算单张图像的统计数据 --- + # 这个代码块是必需的,它为下面的 per_image_stats 和 stats_text_display 定义了 tp, fp, fn, tn + tp, fp, fn, tn = smp.metrics.get_stats( + pred_mask_tensor, gt_mask_tensor, + mode=config.SEG_MODE, num_classes=num_classes + ) + + # --- 2. 累加到总数,用于最终的整体评估 --- + total_tp += tp.squeeze(0).to(config.DEVICE) + total_fp += fp.squeeze(0).to(config.DEVICE) + total_fn += fn.squeeze(0).to(config.DEVICE) + total_tn += tn.squeeze(0).to(config.DEVICE) + + # --- 3. 将曲线数据写入临时文件 (这部分逻辑正确,无需修改) --- + probs_tensor = torch.softmax(pred_logits_tensor, dim=1) + gt_flat = gt_mask_tensor.cpu().numpy().flatten() + probs_flat = probs_tensor.permute(0, 2, 3, 1).reshape(-1, num_classes).cpu().numpy() + with open(temp_gt_file.name, 'ab') as f_gt, open(temp_probs_file.name, 'ab') as f_probs: + np.save(f_gt, gt_flat) + np.save(f_probs, probs_flat) + + # --- 4. [修复] 现在可以安全地计算单图指标用于可视化 --- + # 因为上面的代码块已经定义了 tp, fp, fn, tn,所以这里不再报错 + per_image_stats = { + 'iou': smp.metrics.iou_score(tp, fp, fn, tn, reduction='micro').item(), + 'acc': smp.metrics.accuracy(tp, fp, fn, tn, reduction='micro').item() + } + + # --- 5. [修复] 修正生成统计文本时的 Bug --- + # `get_stats` 返回的 tp 张量形状是 (1, num_classes), + # 因此我们应该遍历第1维度 (类别),并使用 [0, i] 进行索引 + stats_text_display = "Each Class TP / FP / FN / TN:\n" + "-"*35 + "\n" + for i in range(1, tp.shape[1]): # 修正: 遍历类别维度 tp.shape[1] + if i < len(config.CLASSES): + class_name = config.CLASSES[i] + stats_text_display += ( + f" {class_name:<10}: " + # 修正: 使用正确的索引 tp[0, i] + f"{tp[0, i]:>5} / {fp[0, i]:>5} / {fn[0, i]:>5} / {tn[0, i]:>7}\n" + ) + + save_visual_comparison( + image_name=image_path.name, + original_image=original_image, + pred_mask=pred_mask_resized_raw, + gt_mask=gt_mask_raw, + stats=per_image_stats, + stats_text=stats_text_display, + save_dir = analysis_results_dir + ) + + logging.info("\n预测流程已完成!") + logging.info(f"原始预测掩码已保存至: {raw_mask_dir}") + logging.info(f"分析图像已保存至: {analysis_results_dir}") + + # --- 5. 最终分析(循环结束后) --- + if evaluate_mode: + # calculate_and_save_final_metrics(all_stats_for_metrics, len(config.CLASSES), save_dir = analysis_results_dir) + calculate_and_save_final_metrics(total_tp.cpu(), total_fp.cpu(), total_fn.cpu(), total_tn.cpu(), num_classes, save_dir=analysis_results_dir) + + # if all_gt_for_curves: + # logging.info("\n正在聚合结果以生成 ROC 和 PR 曲线...") + # full_gt = torch.cat(all_gt_for_curves).numpy().flatten() + # full_logits = torch.cat(all_logits_for_curves) + + # # 调整维度并计算概率 + # num_classes = len(config.CLASSES) + # full_probs = softmax( + # full_logits.permute(0, 2, 3, 1).reshape(-1, num_classes).numpy(), + # axis=1 + # ) + + # generate_and_save_curves( + # y_true=full_gt, y_probs=full_probs, + # class_names=config.CLASSES, output_dir=analysis_results_dir + # ) + + # 【新增】从临时文件中分块读取数据来生成曲线 + try: + logging.info("\n正在从临时文件加载数据以生成 ROC 和 PR 曲线...") + temp_gt_file.close() + temp_probs_file.close() + + all_gt_parts, all_probs_parts = [], [] + with open(temp_gt_file.name, 'rb') as f_gt, open(temp_probs_file.name, 'rb') as f_probs: + while True: + try: + all_gt_parts.append(np.load(f_gt)) + all_probs_parts.append(np.load(f_probs)) + # 【修复】将 EOFError 添加到要捕获的异常列表中 + except (IOError, ValueError, EOFError): + break # 文件读取完毕 + + if all_gt_parts: + full_gt = np.concatenate(all_gt_parts) + full_probs = np.concatenate(all_probs_parts) + + generate_and_save_curves( + y_true=full_gt, y_probs=full_probs, + class_names=config.CLASSES, output_dir=analysis_results_dir + ) + else: + logging.warning("临时文件中没有数据,跳过 ROC/PR 曲线生成。") + + finally: + # 【新增】确保在最后删除临时文件 + import os + os.remove(temp_gt_file.name) + os.remove(temp_probs_file.name) + logging.info("已清理临时文件。") + +if __name__ == "__main__": + # 创建一个参数解析器 + parser = argparse.ArgumentParser(description="训练图像分割模型。") + + # 添加 --architecture 参数 + # - `choices` 会自动从你的 config 文件中获取所有可用的模型名称 + # - `default` 设置了在不提供参数时的默认模型 + parser.add_argument( + "-a", "--architecture", + type=str, + # default='Unet', + choices=list(config.ALL_MODEL_CONFIGS.keys()), + required=True, + help="选择要训练的模型架构。" + ) + + # 解析命令行传入的参数 + args = parser.parse_args() + + # 将解析出的架构名称传入 main 函数并执行 + main(model_architecture=args.architecture) \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/1_predict_raw_masks_check.py b/Seg_All_In_One_SegModel/1_predict_raw_masks_check.py new file mode 100644 index 0000000..977c152 --- /dev/null +++ b/Seg_All_In_One_SegModel/1_predict_raw_masks_check.py @@ -0,0 +1,137 @@ +# predict_check.py + +import sys +from pathlib import Path +from typing import Set, Tuple + +try: + import config +except ImportError: + print("错误:无法导入 'config.py'。请确保此脚本与 config.py 在同一目录下。") + sys.exit(1) + +def get_file_info(directory: Path) -> Tuple[Set[str], Set[str]]: + """ + 安全地获取目录中所有文件的基本名(stem)和后缀(suffix)。 + + 返回: + 一个包含 stems 集合和 suffixes 集合的元组。 + """ + if not directory.is_dir(): + return set(), set() + + stems = set() + suffixes = set() + for item in directory.iterdir(): + if item.is_file(): + stems.add(item.stem) + suffixes.add(item.suffix) + return stems, suffixes + +def main(): + """ + 主函数,用于详细检查源测试数据集和所有预测输出目录之间的 + 文件数量、文件名和后缀一致性。 + """ + print("--- 开始详细检查预测输出 ---") + + # 1. 从 config.py 获取源目录并解析文件信息 + source_dir = config.TEST_IMAGE_DIR + if not source_dir.exists() or not source_dir.is_dir(): + print(f"错误:在 '{source_dir}' 找不到源测试图片目录。") + sys.exit(1) + + source_stems, _ = get_file_info(source_dir) + source_file_count = len(source_stems) + + if source_file_count == 0: + print(f"警告:源目录 '{source_dir}' 为空或不包含任何文件。检查中止。") + sys.exit(0) + + print(f"源数据集: 在 '{source_dir}' 中找到 {source_file_count} 张图片。") + print("-" * 50) + + # 2. 从 config.py 获取预测结果的基础目录 + predictions_base_dir = Path(config.PREDICT_BEST_MODEL_DIR) + if not predictions_base_dir.exists() or not predictions_base_dir.is_dir(): + print(f"错误:在 '{predictions_base_dir}' 找不到预测结果的基础目录。") + sys.exit(1) + + # 3. 遍历每个模型的运行目录并进行详细检查 + found_predictions = False + global_mismatch_found = False + + run_dirs = sorted([d for d in predictions_base_dir.iterdir() if d.is_dir()]) + + if not run_dirs: + print("信息:在基础目录中没有找到任何模型的运行记录文件夹。") + sys.exit(0) + + for run_dir in run_dirs: + target_dir = run_dir / "predicted_raw_masks" + + # 如果目标目录不存在,则跳过 + if not (target_dir.exists() and target_dir.is_dir()): + continue + + print(f"正在检查: '{target_dir}'...") + found_predictions = True + is_current_dir_ok = True + + predicted_stems, predicted_suffixes = get_file_info(target_dir) + predicted_file_count = len(predicted_stems) + + # --- 检查 1: 文件数量 --- + if source_file_count != predicted_file_count: + print(f" [✗ 数量不匹配] 源目录有 {source_file_count} 个文件,但此目录有 {predicted_file_count} 个。") + is_current_dir_ok = False + + # --- 检查 2: 文件名 --- + missing_files = source_stems - predicted_stems + if missing_files: + # 只显示最多3个示例文件名以保持简洁 + examples = list(missing_files)[:3] + print(f" [✗ 文件名缺失] 预测结果中缺少 {len(missing_files)} 个文件。例如: {examples}...") + is_current_dir_ok = False + + extra_files = predicted_stems - source_stems + if extra_files: + examples = list(extra_files)[:3] + print(f" [✗ 文件名多余] 预测结果中多出 {len(extra_files)} 个文件。例如: {examples}...") + is_current_dir_ok = False + + # --- 检查 3: 后缀一致性 --- + # 只有在目录不为空的情况下,后缀多于1个才算问题 + if predicted_file_count > 0 and len(predicted_suffixes) > 1: + print(f" [✗ 后缀不一致] 预测目录中存在多种文件后缀: {sorted(list(predicted_suffixes))}") + is_current_dir_ok = False + + # --- 当前目录的检查总结 --- + if is_current_dir_ok: + # 再次检查目录为空的特殊情况 + if predicted_file_count == 0 and source_file_count > 0: + print(" [✗ 目录为空] 预测目录是空的,但源目录包含文件。") + global_mismatch_found = True + else: + suffix_info = list(predicted_suffixes)[0] if len(predicted_suffixes) == 1 else 'N/A' + print(f" [✓ OK] 所有检查通过 (数量: {predicted_file_count}, 后缀: {suffix_info})") + else: + global_mismatch_found = True + + print("-" * 25) + + # 4. 输出最终的全局检查摘要 + print("\n--- 检查摘要 ---") + if not found_predictions: + print("结果: 没有找到任何 'predicted_raw_masks' 目录进行检查。") + elif global_mismatch_found: + print("结论: 检查不通过。发现至少一个预测目录未通过所有检查,请查看上方日志。") + sys.exit(1) + else: + print("结论: 检查通过!所有已找到的预测目录均通过了数量、文件名和后缀一致性检查。") + + print("--- 检查完成 ---") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V1.py b/Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V1.py new file mode 100644 index 0000000..4cb4824 --- /dev/null +++ b/Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V1.py @@ -0,0 +1,261 @@ +import os +import glob +import logging +import argparse +import csv +from typing import Dict, Optional, Tuple, List + +import torch +from fvcore.nn import FlopCountAnalysis + +from train import initialize_components +import config # Assumes config.py is in the same directory or accessible + +import time +import numpy as np +from pathlib import Path +import sys + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# TODO 使用方法 TODO +# 交互模式(推荐) +# python 2_predict_params_and_FLOPs.py +# 非交互模式(用于脚本) +# python 2_predict_params_and_FLOPs.py --input_dir '../BestMode_Predict_Results_DataSet_Public' +# python 2_predict_params_and_FLOPs.py --shape 512 512 # --shape 1080 1920 + +def get_flops_and_params_smp(architecture: str, shape: Tuple[int, int]) -> Optional[Dict[str, str]]: + """ + Calculates FLOPs and parameters for a given SMP model architecture using fvcore. + + Args: + architecture (str): The name of the model architecture. + shape (Tuple[int, int]): The input image shape (H, W). + + Returns: + Optional[Dict[str, str]]: A dictionary with 'params' and 'flops', or None on failure. + """ + logging.info(f"Analyzing model: '{architecture}' with input shape {shape}...") + + num_classes = len(config.CLASSES) + model, _, _, _ = initialize_components(architecture, num_classes, config.SEG_MODE) + + if model is None: + return None + + model.to(config.DEVICE) + model.eval() + + dummy_input = torch.randn(1, 3, shape[0], shape[1]).to(config.DEVICE) + + try: + # Calculate FLOPs using fvcore + flops_analyzer = FlopCountAnalysis(model, dummy_input) + total_flops = flops_analyzer.total() + gflops = total_flops / 1e9 + + # Calculate Parameters + total_params = sum(p.numel() for p in model.parameters()) + m_params = total_params / 1e6 + + logging.info(f"✅ Analysis successful for '{architecture}': {gflops:.2f} GFLOPs, {m_params:.2f} M Params") + return { + 'flops': f"{gflops:.2f} G", + 'params': f"{m_params:.2f} M" + } + + except Exception as e: + logging.error(f"❌ Failed to analyze FLOPs for '{architecture}'. Error: {e}") + return None + +def get_shape_from_path(path: str) -> Optional[Tuple[int, int]]: + """ + Extracts resolution (WxH) from a directory path using regex. + + Args: + path (str): The path to the dataset directory. + + Returns: + Optional[Tuple[int, int]]: A tuple of (Height, Width), or None if not found. + """ + # V1. 采用图片本身的尺寸作为输入 + # import re + # match = re.search(r'(\d+)x(\d+)', os.path.basename(path)) + # if match: + # width, height = int(match.group(1)), int(match.group(2)) + # return (height, width) # Return as H, W + # return None + + # V2. 采用文件夹config中的尺寸作为输入 + width, height = config.IMAGE_WIDTH, config.IMAGE_HEIGHT + if width > 0 and height > 0: + return (height, width) # Return as H, W + else: + return None + +# --- 主函数 --- +def main(args): + """ + Main script entry point for the automated analysis workflow. + """ + input_root = args.input_dir + + if not os.path.isdir(input_root): + logging.error(f"Input directory does not exist: {input_root}") + return + + # 1. Find all valid dataset directories (ending with '-SegModel') + existing_dataset_dirs = sorted([ + d for d in glob.glob(os.path.join(input_root, '*_outputs-SegModel')) if os.path.isdir(d) + ]) + + if not existing_dataset_dirs: + logging.error(f"No valid dataset folders (e.g., '*_outputs-SegModel') found in {input_root}.") + return + + # 2. First-level menu: Select dataset + dataset_map = {str(i + 1): path for i, path in enumerate(existing_dataset_dirs)} + + print("\n" + "="*50) + print("--- Step 1: Please select the dataset to process ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice1 = input("Enter the dataset number and press Enter: ").strip() + + model_archs_to_process = [] + selected_dataset_dir = None + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"You have selected the dataset: [{os.path.basename(selected_dataset_dir)}]") + + # 3. Find all algorithm subdirectories and extract base architecture names + alg_dirs_full_path = sorted([d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d)]) + + # CORRECTED LOGIC: Create a list of valid base architecture names found in the directory + valid_alg_archs = [] + for path in alg_dirs_full_path: + # Extract base name, e.g., 'Unet' from 'Unet_2025-09-24_14-24-05' + base_name = os.path.basename(path).split('_')[0] + if base_name in config.ALL_MODEL_CONFIGS and base_name not in valid_alg_archs: + valid_alg_archs.append(base_name) + + if not valid_alg_archs: + logging.warning(f"No valid algorithm subfolders found in {os.path.basename(selected_dataset_dir)}. Please check folder names match keys in config.ALL_MODEL_CONFIGS.") + else: + # 4. Second-level menu: Select algorithm using the base names + alg_map = {str(i + 1): name for i, name in enumerate(valid_alg_archs)} + print("\n" + "="*50) + print("--- Step 2: Please select the algorithm to process ---") + print("0: Process [ALL] algorithms under the current dataset") + for key, name in alg_map.items(): + print(f"{key}: {name}") + print("="*50) + + choice2 = input("Enter the algorithm number (or '0' for all) and press Enter: ").strip() + + # 5. Determine the final list of architectures to process + if choice2 == '0': + model_archs_to_process = valid_alg_archs + logging.info(f"You have chosen to process all {len(model_archs_to_process)} algorithms.") + elif choice2 in alg_map: + model_archs_to_process = [alg_map[choice2]] + logging.info(f"You have chosen to process a single algorithm: {model_archs_to_process[0]}") + else: + logging.error("Invalid algorithm selection. Exiting.") + else: + logging.error("Invalid dataset selection. Exiting.") + + if not model_archs_to_process or not selected_dataset_dir: + return + + # --- Processing logic --- + results: List[Dict[str, str]] = [] + + # Determine input shape for analysis + input_shape = None + if args.shape: + # 1. Prioritize user-provided shape from command line + input_shape = (args.shape[0], args.shape[1]) + logging.info(f"Using user-provided shape (H, W): {input_shape} for calculation.") + else: + # 2. Fallback to detecting shape from folder name + logging.info("No --shape argument provided. Attempting to detect from folder name...") + input_shape = get_shape_from_path(selected_dataset_dir) + if not input_shape: + # 3. Fallback to config.py defaults if detection fails + logging.warning(f"Could not automatically detect resolution from folder '{os.path.basename(selected_dataset_dir)}'.") + logging.info(f"Using default shape from config.py: ({config.IMAGE_HEIGHT}, {config.IMAGE_WIDTH})") + input_shape = (config.IMAGE_HEIGHT, config.IMAGE_WIDTH) + else: + logging.info(f"Detected input shape (H, W): {input_shape} from folder name.") + + for architecture in model_archs_to_process: + logging.info(f"--- Processing model: {architecture} ---") + + # ==================== SOLUTION CODE BLOCK START ==================== + # For Transformer models like DPT, ensure input shape is divisible by the patch size. + analysis_shape = input_shape + if architecture == 'DPT': + patch_size = 16 # From the encoder name '...patch16...' + h, w = analysis_shape + + if h % patch_size != 0 or w % patch_size != 0: + # Calculate the new, compatible dimensions by rounding up to the nearest multiple of patch_size + new_h = ((h + patch_size - 1) // patch_size) * patch_size + new_w = ((w + patch_size - 1) // patch_size) * patch_size + analysis_shape = (new_h, new_w) + logging.warning(f"DPT requires dimensions divisible by {patch_size}. " + f"Adjusting analysis shape from {input_shape} to {analysis_shape}.") + # ===================== SOLUTION CODE BLOCK END ===================== + + stats = get_flops_and_params_smp(architecture, analysis_shape) + if stats: + results.append({ + 'Model': architecture, + 'Params': stats['params'], + 'FLOPs': stats['flops'], + 'Input_Shape (HxW)': f"{analysis_shape[0]}x{analysis_shape[1]}" + }) + else: + logging.warning(f"Failed to get stats for model {architecture}.") + + # --- Write results to CSV file --- + if not results: + logging.info("No statistics were successfully generated, CSV file will not be created.") + return + + dataset_name = os.path.basename(selected_dataset_dir).replace('_outputs-SegModel', '') + output_csv_path = os.path.join(selected_dataset_dir, f'{dataset_name}_flops_params_summary.csv') + + try: + with open(output_csv_path, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = ['Model', 'Params', 'FLOPs', 'Input_Shape (HxW)'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(results) + + logging.info(f"=== All processing complete! Results saved to: {output_csv_path} ===") + except IOError as e: + logging.error(f"Could not write to CSV file: {output_csv_path}. Error: {e}") + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="SMP Model Automation and Evaluation Script") + parser.add_argument( + '--input_dir', + type=str, + default=str(config.PREDICT_ALL_BEST_MODELS_DIR), + help="Root directory containing trained model folders (e.g., 'DATASET_outputs-SegModel')." + ) + parser.add_argument( + '--shape', + type=int, + nargs=2, + metavar=('HEIGHT', 'WIDTH'), + help="Specify input shape (height width) for analysis, overriding automatic detection." + ) + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V2.py b/Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V2.py new file mode 100644 index 0000000..45ad057 --- /dev/null +++ b/Seg_All_In_One_SegModel/2_predict_params_and_FLOPs_V2.py @@ -0,0 +1,325 @@ +import os +import glob +import logging +import argparse +import csv +from typing import Dict, Optional, Tuple, List +import time +import numpy as np +from pathlib import Path +import sys + +import torch +from fvcore.nn import FlopCountAnalysis +import segmentation_models_pytorch as smp +import config # Assumes config.py is in the same directory or accessible + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# TODO 使用方法 TODO +# 交互模式(推荐) +# python 2_predict_params_and_FLOPs.py +# 非交互模式(用于脚本) +# python 2_predict_params_and_FLOPs_V2.py --input_dir '../BestMode_Predict_Results_DataSet_Public' +# python 2_predict_params_and_FLOPs_V2.py --shape 512 512 # --shape 1080 1920 + +def load_model(model_architecture: str, checkpoint_path: Optional[Path]) -> Optional[torch.nn.Module]: + """ + 根据 config.py 直接重建模型架构,并从 checkpoint 路径加载权重。 + 此函数不依赖 train.py。 + """ + # 1. 根据 config 动态创建基础模型架构 + logging.info(f"Rebuilding model architecture: '{model_architecture}'") + try: + model_params = config.ALL_MODEL_CONFIGS[model_architecture].copy() + model_class = getattr(smp, model_architecture) + except (AttributeError, KeyError): + logging.error(f"Model '{model_architecture}' not found in smp library or config.py!") + return None + + # 2. 准备初始化参数 + num_classes = len(config.CLASSES) + params = model_params + params['classes'] = num_classes + # 加载权重时,不应再次下载预训练权重 + params['encoder_weights'] = None + + # 自动检测 ViT 编码器以设置特殊参数 + encoder_name = model_params.get('encoder_name', '') + if 'vit' in encoder_name.lower(): + params['dynamic_img_size'] = True + logging.info(f"ViT encoder ('{encoder_name}') detected. Setting dynamic_img_size=True.") + + # 3. 创建模型实例 + model = model_class(**params).to(config.DEVICE) + + # 4. 如果提供了有效的 checkpoint 路径,则加载权重 + if checkpoint_path and checkpoint_path.exists(): + logging.info(f"Loading checkpoint: {checkpoint_path}") + try: + state_dict = torch.load(checkpoint_path, map_location=torch.device(config.DEVICE)) + # 处理 DataParallel 保存的权重 + is_dataparallel = any(key.startswith('module.') for key in state_dict.keys()) + if is_dataparallel: + logging.info("DataParallel weights detected, removing 'module.' prefix.") + new_state_dict = {key.replace('module.', ''): value for key, value in state_dict.items()} + model.load_state_dict(new_state_dict) + else: + model.load_state_dict(state_dict) + logging.info("Checkpoint loaded successfully.") + except Exception as e: + logging.error(f"Failed to load checkpoint. Using model with random weights. Error: {e}") + else: + logging.info("No valid checkpoint provided. Using model with random weights.") + + model.eval() + return model + +# --- 核心功能函数 --- +def find_latest_model_run_path(base_dir: Path, model_architecture: str) -> Optional[Path]: + """ + 非交互式地查找并返回给定架构的最新模型运行目录。 + """ + logging.info(f"Searching for latest '{model_architecture}' model run in '{base_dir}'...") + + # 查找所有以架构名开头的文件夹 + run_dirs = sorted([d for d in base_dir.iterdir() if d.is_dir() and d.name.startswith(model_architecture + '_')]) + + if not run_dirs: + logging.info(f"No trained model folder found for '{model_architecture}'.") + return None + + # 排序后的最后一个即为最新的 + latest_run_dir = run_dirs[-1] + logging.info(f"Automatically selected latest model run: {latest_run_dir.name}") + return latest_run_dir + +def calculate_flops_params(model: torch.nn.Module, shape: Tuple[int, int]) -> Optional[Dict[str, str]]: + """ + 为已初始化的模型计算FLOPs和参数量。 + """ + logging.info(f"Analyzing model Params/FLOPs with input shape {shape}...") + model.to(config.DEVICE) + model.eval() + dummy_input = torch.randn(1, 3, shape[0], shape[1]).to(config.DEVICE) + try: + flops_analyzer = FlopCountAnalysis(model, dummy_input) + gflops = flops_analyzer.total() / 1e9 + m_params = sum(p.numel() for p in model.parameters()) / 1e6 + logging.info(f"✅ Params/FLOPs Analysis successful: {gflops:.2f} GFLOPs, {m_params:.2f} M Params") + return {'flops': f"{gflops:.2f} G", 'params': f"{m_params:.2f} M"} + except Exception as e: + logging.error(f"❌ Failed to analyze FLOPs/Params. Error: {e}") + return None + +def benchmark_model(model: torch.nn.Module, shape: Tuple[int, int]) -> Optional[Dict[str, float]]: + """ + 对加载了权重的模型进行FPS基准测试。 + """ + logging.info("Starting FPS benchmark...") + model.to(config.DEVICE) + model.eval() + dummy_input = torch.randn(1, 3, shape[0], shape[1]).to(config.DEVICE) + + overall_fps_list = [] + repeat_times = 3 + + with torch.no_grad(): + for run_index in range(repeat_times): + num_warmup = 5 + pure_inf_time = 0 + total_iters = 100 + + for i in range(total_iters + num_warmup): + if torch.cuda.is_available(): + torch.cuda.synchronize() + start_time = time.perf_counter() + _ = model(dummy_input) + if torch.cuda.is_available(): + torch.cuda.synchronize() + elapsed = time.perf_counter() - start_time + if i >= num_warmup: + pure_inf_time += elapsed + + if pure_inf_time == 0: + pure_inf_time = 1e-6 + + overall_fps = total_iters / pure_inf_time + overall_fps_list.append(overall_fps) + print(f" Run {run_index + 1}/{repeat_times}, FPS: {overall_fps:.2f} img/s") + + if not overall_fps_list: + return None + + avg_fps = np.mean(overall_fps_list) + fps_variance = np.var(overall_fps_list) + logging.info(f"✅ FPS Benchmark successful: Average FPS: {avg_fps:.2f} img/s") + return {'avg_fps': avg_fps, 'fps_variance': fps_variance} + +def get_shape_from_path(path: str) -> Optional[Tuple[int, int]]: + """ + 从文件夹路径中提取分辨率。 + """ + # V1. 采用图片本身的尺寸作为输入 + import re + match = re.search(r'(\d+)x(\d+)', os.path.basename(path)) + if match: + width, height = int(match.group(1)), int(match.group(2)) + return (height, width) # Return as H, W + return None + + # V2. 采用文件夹config中的尺寸作为输入 + # width, height = config.IMAGE_WIDTH, config.IMAGE_HEIGHT + + if width > 0 and height > 0: + return (height, width) + else: + return None + +# --- 主函数 --- +def main(args): + """ + 脚本主入口。 + """ + input_root = args.input_dir + if not os.path.isdir(input_root): + logging.error(f"Input directory does not exist: {input_root}") + return + + # 1. 交互式选择数据集和算法 + existing_dataset_dirs = sorted([d for d in glob.glob(os.path.join(input_root, '*_outputs-SegModel')) if os.path.isdir(d)]) + if not existing_dataset_dirs: + logging.error(f"No valid dataset folders found in {input_root}.") + return + + dataset_map = {str(i + 1): path for i, path in enumerate(existing_dataset_dirs)} + print("\n" + "="*50) + print("--- Step 1: Please select the dataset to process ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + choice1 = input("Enter the dataset number and press Enter: ").strip() + + model_archs_to_process = [] + selected_dataset_dir = None + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"You have selected the dataset: [{os.path.basename(selected_dataset_dir)}]") + + alg_dirs_full_path = sorted([d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d)]) + valid_alg_archs = [] + for path in alg_dirs_full_path: + base_name = os.path.basename(path).split('_')[0] + if base_name in config.ALL_MODEL_CONFIGS and base_name not in valid_alg_archs: + valid_alg_archs.append(base_name) + + if valid_alg_archs: + alg_map = {str(i + 1): name for i, name in enumerate(valid_alg_archs)} + print("\n" + "="*50) + print("--- Step 2: Please select the algorithm to process ---") + print("0: Process [ALL] algorithms under the current dataset") + for key, name in alg_map.items(): + print(f"{key}: {name}") + print("="*50) + choice2 = input("Enter the algorithm number (or '0' for all) and press Enter: ").strip() + if choice2 == '0': + model_archs_to_process = valid_alg_archs + elif choice2 in alg_map: + model_archs_to_process = [alg_map[choice2]] + else: + logging.warning(f"No valid algorithm subfolders found in {os.path.basename(selected_dataset_dir)}.") + else: + logging.error("Invalid dataset selection. Exiting.") + return + + if not model_archs_to_process or not selected_dataset_dir: + return + + # 2. 确定输入尺寸 + if args.shape: + analysis_shape = (args.shape[0], args.shape[1]) + logging.info(f"Using user-provided shape (H, W): {analysis_shape} for calculation.") + else: + analysis_shape = get_shape_from_path(selected_dataset_dir) or (config.IMAGE_HEIGHT, config.IMAGE_WIDTH) + logging.info(f"Using detected/default shape (H, W): {analysis_shape} for calculation.") + + # 3. 主处理循环 + ori_analysis_shape = analysis_shape + results: List[Dict[str, str]] = [] + for architecture in model_archs_to_process: + logging.info(f"--- Processing model: {architecture} ---") + + # 特殊处理 DPT 模型的输入尺寸要求 + analysis_shape = ori_analysis_shape + patch_size = 16 # From the encoder name '...patch16...' + h, w = analysis_shape + if h % patch_size != 0 or w % patch_size != 0: + # Calculate the new, compatible dimensions by rounding up to the nearest multiple of patch_size + new_h = ((h + patch_size - 1) // patch_size) * patch_size + new_w = ((w + patch_size - 1) // patch_size) * patch_size + analysis_shape = (new_h, new_w) + logging.warning(f"DPT requires dimensions divisible by {patch_size}. " + f"Adjusting analysis shape from {ori_analysis_shape} to {analysis_shape}.") + + # 步骤 3.1: 查找最新的模型运行路径和权重文件路径 + latest_run_dir = find_latest_model_run_path(Path(selected_dataset_dir), architecture) + checkpoint_path = None + if latest_run_dir: + # 权重文件路径,即用户期望的 best_model_save_path + checkpoint_path = latest_run_dir / config.BEST_MODEL_SAVE_NAME + + # 步骤 3.2: 使用新函数加载模型(一行代码完成初始化和权重加载) + model = load_model(architecture, checkpoint_path) + + if model is None: + logging.warning(f"Skipping all analysis for {architecture} due to model initialization failure.") + continue + + # 步骤 3.3: 在同一个模型实例上执行所有计算 + stats_flops_params = calculate_flops_params(model, analysis_shape) + stats_fps = benchmark_model(model, analysis_shape) + + # 3.4 整合结果 + results.append({ + 'Model': architecture, + 'Params': stats_flops_params['params'] if stats_flops_params else "N/A", + 'FLOPs': stats_flops_params['flops'] if stats_flops_params else "N/A", + 'Input_Shape (HxW)': f"{ori_analysis_shape[0]}x{ori_analysis_shape[1]}", + 'Average_FPS': f"{stats_fps['avg_fps']:.2f}" if stats_fps else "N/A", + 'FPS_Variance': f"{stats_fps['fps_variance']:.4f}" if stats_fps else "N/A" + }) + + # 4. 写入CSV文件 + if results: + dataset_name = os.path.basename(selected_dataset_dir).replace('_outputs-SegModel', '') + output_csv_path = os.path.join(selected_dataset_dir, f'{dataset_name}_flops_params_fps_summary.csv') + + try: + with open(output_csv_path, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = ['Model', 'Params', 'FLOPs', 'Input_Shape (HxW)', 'Average_FPS', 'FPS_Variance'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(results) + logging.info(f"=== All processing complete! Results saved to: {output_csv_path} ===") + except IOError as e: + logging.error(f"Could not write to CSV file: {output_csv_path}. Error: {e}") + else: + logging.info("No statistics were successfully generated, CSV file will not be created.") + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="SMP Model Automation and Evaluation Script") + parser.add_argument( + '--input_dir', + type=str, + default=str(config.PREDICT_ALL_BEST_MODELS_DIR), + help="Root directory containing trained model folders." + ) + parser.add_argument( + '--shape', + type=int, + nargs=2, + metavar=('HEIGHT', 'WIDTH'), + help="Specify input shape (height width), overriding automatic detection." + ) + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/3_predict_matrics_from_log.py b/Seg_All_In_One_SegModel/3_predict_matrics_from_log.py new file mode 100644 index 0000000..875b62b --- /dev/null +++ b/Seg_All_In_One_SegModel/3_predict_matrics_from_log.py @@ -0,0 +1,271 @@ +import os +import glob +import logging +import argparse +import re +import csv +import numpy as np +from typing import Dict, Optional, List + +# --- Configure Logging --- +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# --- Helper Functions (No changes in this section) --- + +def find_smp_csv_file(algorithm_dir: str) -> Optional[str]: + """ + Finds the 'training_metrics.csv' file in a given algorithm directory. + """ + csv_path = os.path.join(algorithm_dir, 'training_metrics.csv') + if os.path.exists(csv_path): + return csv_path + else: + logging.warning(f"Could not find 'training_metrics.csv' in {algorithm_dir}") + return None + +def parse_smp_metrics(csv_path: str) -> Optional[Dict]: + """ + Parses 'training_metrics.csv' to find the best epoch based on the highest + IoU score and extracts all relevant metrics. + """ + try: + with open(csv_path, 'r', encoding='utf-8') as f: + reader = csv.DictReader(f) + rows = list(reader) + except (IOError, StopIteration) as e: + logging.error(f"Cannot read or parse CSV file: {csv_path}. Error: {e}") + return None + + if not rows: + logging.warning(f"❌ CSV file is empty: {os.path.basename(csv_path)}") + return None + + try: + best_row = max(rows, key=lambda row: float(row.get('iou_score', 0))) + best_iou = float(best_row.get('iou_score', 0)) + if best_iou == 0: + logging.warning(f"❌ Best 'iou_score' is 0 in {os.path.basename(csv_path)}. Check training.") + return None + except (ValueError, TypeError) as e: + logging.error(f"Could not find a valid 'iou_score' in {csv_path}. Error: {e}") + return None + + results = { + 'epoch': best_row.get('epoch', 'N/A'), + 'summary': { + 'mIoU': best_row.get('iou_score', 'N/A'), + 'mAcc': 'N/A', + 'aAcc': best_row.get('accuracy', 'N/A') + }, + 'class_wise': [] + } + + per_class_accuracies = [] + num_classes = 0 + class_keys = [key for key in best_row if key.startswith('tp_class_')] + if class_keys: + num_classes = max([int(k.split('_')[-1]) for k in class_keys]) + 1 + + for i in range(num_classes): + try: + tp = float(best_row.get(f'tp_class_{i}', 0)) + fp = float(best_row.get(f'fp_class_{i}', 0)) + fn = float(best_row.get(f'fn_class_{i}', 0)) + + class_iou = tp / (tp + fp + fn + 1e-6) + class_acc_str = best_row.get(f'cpa_class_{i}', '0') + per_class_accuracies.append(float(class_acc_str)) + + class_name = '背景' if str(i) == '0' else str(i) + + results['class_wise'].append({ + 'Class': class_name, + 'IoU': f"{class_iou:.4f}", + 'Acc': class_acc_str + }) + except (ValueError, TypeError) as e: + logging.warning(f"Could not process metrics for class {i}. Error: {e}") + continue + + if per_class_accuracies: + results['summary']['mAcc'] = f"{np.mean(per_class_accuracies):.4f}" + + if results['class_wise']: + logging.info(f"✅ Successfully parsed metrics for epoch '{results['epoch']}' from {os.path.basename(csv_path)}.") + return results + else: + logging.warning(f"❌ Could not parse any per-class metrics from {os.path.basename(csv_path)}.") + return None + + +# --- Main Function --- +def main(args): + """ + Main script entry point to orchestrate the analysis workflow. + """ + input_root = args.input_dir + output_root = args.output_dir + + if not os.path.isdir(input_root): + logging.error(f"Input directory does not exist: {input_root}") + return + + # --- Interactive Menu --- + all_dataset_dirs = sorted([ + d for d in glob.glob(os.path.join(input_root, '*_outputs-SegModel')) if os.path.isdir(d) + ]) + + if not all_dataset_dirs: + logging.error(f"No valid dataset folders ending in '_outputs-SegModel' found in {input_root}.") + return + + dataset_map = {str(i + 1): path for i, path in enumerate(all_dataset_dirs)} + + print("\n" + "="*50) + print("--- Step 1: Please select a dataset to process ---") + for key, path in dataset_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice1 = input("Enter the dataset number and press Enter: ").strip() + + model_dirs = [] + selected_dataset_dir = None + + if choice1 in dataset_map: + selected_dataset_dir = dataset_map[choice1] + logging.info(f"You have selected the dataset: [{os.path.basename(selected_dataset_dir)}]") + + alg_dirs = sorted([ + d for d in glob.glob(os.path.join(selected_dataset_dir, '*')) if os.path.isdir(d) + ]) + + if not alg_dirs: + logging.warning(f"No algorithm subfolders found in {os.path.basename(selected_dataset_dir)}.") + return + + alg_map = {str(i + 1): path for i, path in enumerate(alg_dirs)} + print("\n" + "="*50) + print("--- Step 2: Please select the algorithm(s) to process ---") + print("0: Process [ALL] algorithms under the current dataset") + for key, path in alg_map.items(): + print(f"{key}: {os.path.basename(path)}") + print("="*50) + + choice2 = input("Enter the algorithm number (or '0' for all) and press Enter: ").strip() + + if choice2 == '0': + model_dirs = alg_dirs + logging.info(f"You have chosen to batch process all {len(model_dirs)} algorithms.") + elif choice2 in alg_map: + model_dirs = [alg_map[choice2]] + logging.info(f"You have chosen to process a single algorithm: {os.path.basename(model_dirs[0])}") + else: + logging.error("Invalid algorithm selection. The program will exit.") + return + else: + logging.error("Invalid dataset selection. The program will exit.") + return + + # --- Start processing the selected algorithms --- + csv_rows = [] + + for model_dir in model_dirs: + model_name_full = os.path.basename(model_dir) + logging.info(f"\n--- Processing algorithm: {model_name_full} ---") + + csv_file_path = find_smp_csv_file(model_dir) + + if not csv_file_path: + logging.warning(f"Skipping {model_name_full} as no 'training_metrics.csv' was found.") + continue + + metrics = parse_smp_metrics(csv_file_path) + + if not metrics: + logging.warning(f"❌❌❌ Skipping {model_name_full} due to a failure in parsing metrics. ❌❌❌") + continue + + summary = metrics['summary'] + short_model_name = model_name_full.split('_')[0] + + row_data = { + 'Algorithm': short_model_name, + 'Epoch': metrics['epoch'], + 'mIoU': f"{round(float(summary['mIoU']), 4)*100:.2f}", + 'mAcc': f"{round(float(summary['mAcc']), 4)*100:.2f}", + 'aAcc': f"{round(float(summary['aAcc']), 4)*100:.2f}" + } + + for class_data in metrics['class_wise']: + class_name = class_data['Class'] + row_data[f'{class_name}_IoU'] = f"{round(float(class_data['IoU']), 4)*100:.2f}" + row_data[f'{class_name}_Acc'] = f"{round(float(class_data['Acc']), 4)*100:.2f}" + + csv_rows.append(row_data) + + # --- Write results to the final CSV file --- + if not csv_rows: + logging.info("No data was successfully collected. No CSV file will be generated.") + return + + # --- NEW: DYNAMICALLY GENERATE FIELDNAMES --- + base_fieldnames = ['Algorithm', 'Epoch', 'mIoU', 'mAcc', 'aAcc'] + + # Collect all unique per-class fieldnames from the data + extra_fieldnames = set() + for row in csv_rows: + for key in row.keys(): + if key not in base_fieldnames: + extra_fieldnames.add(key) + + # Define a sort key for natural sorting (e.g., '1', '2', '10' instead of '1', '10', '2') + def natural_sort_key(key_name): + parts = key_name.split('_') + class_part = parts[0] + metric_part = parts[1] if len(parts) > 1 else '' + if class_part == '背景': + return (-1, metric_part) # Put '背景' (background) first + try: + return (int(class_part), metric_part) + except ValueError: + return (float('inf'), key_name) # Put any non-numeric names last + + # Combine base fields with sorted extra fields + final_fieldnames = base_fieldnames + sorted(list(extra_fieldnames), key=natural_sort_key) + + # Build the output path + output_dataset_folder = os.path.basename(selected_dataset_dir) + final_output_dir = os.path.join(output_root, output_dataset_folder) + os.makedirs(final_output_dir, exist_ok=True) + + dataset_name = output_dataset_folder.replace('_outputs-SegModel', '') + output_csv_path = os.path.join(final_output_dir, f'{dataset_name}_metrics_summary_wide.csv') + + try: + with open(output_csv_path, 'w', newline='', encoding='utf-8-sig') as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=final_fieldnames) + writer.writeheader() + writer.writerows(csv_rows) + + logging.info(f"\n=== All processing is complete! Results have been saved to: {output_csv_path} ===") + except IOError as e: + logging.error(f"Failed to write to CSV file: {output_csv_path}. Error: {e}") + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="SMP (Segmentation Models Pytorch) Final Metrics Extraction Script") + parser.add_argument( + '--input_dir', + type=str, + default='../Hardisk', + help="The root directory containing dataset output folders (e.g., '..._outputs-SegModel')." + ) + parser.add_argument( + '--output_dir', + type=str, + default='../BestMode_Predict_Results_DataSet_Public', + help="The root directory where analysis results will be stored." + ) + + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/Tool_Copy_Best_Model.sh b/Seg_All_In_One_SegModel/Tool_Copy_Best_Model.sh new file mode 100644 index 0000000..e0c91f2 --- /dev/null +++ b/Seg_All_In_One_SegModel/Tool_Copy_Best_Model.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +# +----------------------------------------------------------------------------+ +# | 脚本: 将 best_model.pth 从 Hardisk 复制到 BestMode_Predict_Results_DataSet_Public | +# | 说明: 此版本优化了复制逻辑,仅在文件不存在或内容不一致时才复制。 | +# | 同时包含一个可视化的进度条。 (已修正语法错误) | +# | 用法: ./copy_models.sh | +# +----------------------------------------------------------------------------+ + +# 设置源目录和目标目录的基础路径 +# realpath确保我们获得的是绝对路径,避免相对路径可能带来的问题 +SOURCE_BASE_DIR=$(realpath "../Hardisk") +DEST_BASE_DIR=$(realpath "../BestMode_Predict_Results_DataSet_Public") + +# 检查源目录是否存在 +if [ ! -d "$SOURCE_BASE_DIR" ]; then + echo "错误: 源目录 '$SOURCE_BASE_DIR' 不存在。" + exit 1 +fi + +# 确保目标基础目录存在 +mkdir -p "$DEST_BASE_DIR" + +echo "正在准备操作..." + +# --- 第1步: 查找所有文件并存储到数组中 --- +# 使用 find -print0 和 readarray -d '' 的组合,可以安全处理任何包含特殊字符的文件名 +# 这样只需查找一次,提高了效率和健壮性 +echo "正在查找所有 'best_model.pth' 文件..." +readarray -d '' files_to_process < <(find "$SOURCE_BASE_DIR" -path "*-SegModel/*" -name "best_model.pth" -type f -print0 2>/dev/null) +TOTAL_FILES=${#files_to_process[@]} + + +if [ "$TOTAL_FILES" -eq 0 ]; then + echo "在源目录中没有找到任何 'best_model.pth' 文件。脚本退出。" + exit 0 +fi + +echo "总共找到 $TOTAL_FILES 个模型文件需要处理。" +echo "--------------------------------------------------" + +# --- 进度条函数 --- +# 参数1: 当前文件数 +# 参数2: 文件总数 +print_progress() { + local current=$1 + local total=$2 + # 减去更多的空间以容纳状态信息 + local term_width=$(tput cols) + local bar_width=$((term_width - 30)) + + # 确保 bar_width 不为负 + if [ "$bar_width" -lt 10 ]; then + bar_width=10 + fi + + # 计算百分比 + local percent=$((current * 100 / total)) + + # 计算已完成部分的长度 + local filled_len=$((bar_width * percent / 100)) + + # 准备进度条的显示字符 + local bar="" + for ((i=0; i Path: + """ + 查找指定架构的所有训练运行目录,并让用户选择一个。 + """ + print(f"\nSearching for '{model_architecture}' model runs in '{base_dir}'...") + + # 查找所有以架构名开头的文件夹 + run_dirs = sorted([d for d in base_dir.iterdir() if d.is_dir() and d.name.startswith(model_architecture+'_')]) + + if not run_dirs: + print(f"ERROR: No trained models found for architecture '{model_architecture}'. Please run train_smp.py first.") + return None + + print("\nPlease select a trained model to benchmark:") + for i, dir_path in enumerate(run_dirs): + print(f" [{i+1}] {dir_path.name}") + + while True: + try: + choice = input(f"Enter selection (1-{len(run_dirs)}) or press Enter to cancel: ") + if not choice: + return None + choice_idx = int(choice) - 1 + if 0 <= choice_idx < len(run_dirs): + selected_dir = run_dirs[choice_idx] + print(f"Selected model: {selected_dir.name}") + return selected_dir + else: + print("Invalid selection. Please try again.") + except (ValueError, IndexError): + print("Invalid input. Please enter a number.") + +def parse_args(): + parser = argparse.ArgumentParser(description='Benchmark a segmentation model from SMP') + parser.add_argument( + '-a', '--architecture', + type=str, + required=True, + choices=list(config.ALL_MODEL_CONFIGS.keys()), + help="The model architecture to benchmark." + ) + parser.add_argument( + '-c', '--checkpoint', + type=str, + required=False, # <-- Changed to False + help='(Optional) Path to the checkpoint file. If omitted, an interactive selection will be shown.' + ) + parser.add_argument( + '--shape', + type=int, + nargs='+', + default=[512, 512], + help='Input image size for benchmarking, e.g., --shape 512 512' + ) + parser.add_argument( + '--log-interval', + type=int, + default=50, + help='Interval of logging.' + ) + parser.add_argument( + '--repeat-times', + type=int, + default=3, + help='Number of times to repeat the benchmark for averaging.' + ) + args = parser.parse_args() + return args + +def main(): + args = parse_args() + + # --- New logic to select checkpoint --- + checkpoint_path = None + if args.checkpoint: + checkpoint_path = Path(args.checkpoint) + else: + # Assumes your outputs are saved in the directory specified in config.py + # This is consistent with train_smp.py and predict.py + selected_run_dir = select_trained_model_path(Path(config.PREDICT_BEST_MODEL_DIR), args.architecture) + if not selected_run_dir: + print("No model selected. Exiting.") + sys.exit() + + checkpoint_path = selected_run_dir / config.BEST_MODEL_SAVE_NAME + + if not checkpoint_path.exists(): + print(f"ERROR: Checkpoint file not found at '{checkpoint_path}'") + sys.exit() + # --- End of new logic --- + + if len(args.shape) == 1: + h, w = args.shape[0], args.shape[0] + elif len(args.shape) == 2: + h, w = args.shape[0], args.shape[1] + else: + raise ValueError('Invalid input shape. Use one or two integers.') + + print(f"\n--- Model Benchmarking ---") + print(f"Architecture: {args.architecture}") + print(f"Checkpoint: {checkpoint_path}") + print(f"Input Shape: (1, 3, {h}, {w})") + print(f"Device: {config.DEVICE}") + print("-" * 28) + + # 1. 初始化模型 + num_classes = len(config.CLASSES) + model, _, _, _ = initialize_components(args.architecture, num_classes, config.SEG_MODE) + + # 2. 加载权重 + print("Loading checkpoint...") + state_dict = torch.load(checkpoint_path, map_location=config.DEVICE) + + # Handle DataParallel saved models by removing 'module.' prefix + is_dataparallel = any(key.startswith('module.') for key in state_dict.keys()) + if is_dataparallel: + print("DataParallel model detected, removing 'module.' prefix.") + new_state_dict = {key.replace('module.', ''): value for key, value in state_dict.items()} + model.load_state_dict(new_state_dict) + else: + model.load_state_dict(state_dict) + + model.to(config.DEVICE) + model.eval() + + # 3. 准备伪造输入数据 + dummy_input = torch.randn(1, 3, h, w).to(config.DEVICE) + + # 4. 开始基准测试 + overall_fps_list = [] + for run_index in range(args.repeat_times): + print(f"\n>>> Running benchmark iteration {run_index + 1}/{args.repeat_times}...") + + num_warmup = 5 + pure_inf_time = 0 + total_iters = 200 + + with torch.no_grad(): + for i in range(total_iters + num_warmup): + if torch.cuda.is_available(): + torch.cuda.synchronize() + start_time = time.perf_counter() + + _ = model(dummy_input) + + if torch.cuda.is_available(): + torch.cuda.synchronize() + elapsed = time.perf_counter() - start_time + + if i >= num_warmup: + pure_inf_time += elapsed + if (i + 1) % args.log_interval == 0: + fps = (i + 1 - num_warmup) / pure_inf_time + print(f'Done image [{i + 1 - num_warmup:<4}/ {total_iters}], ' + f'current FPS: {fps:.2f} img/s') + + overall_fps = total_iters / pure_inf_time + overall_fps_list.append(overall_fps) + print(f'Overall FPS for this run: {overall_fps:.2f} img/s') + + # 5. 总结结果 + print("\n--- Benchmark Summary ---") + avg_fps = np.mean(overall_fps_list) + var_fps = np.var(overall_fps_list) + print(f'Average FPS of {args.repeat_times} runs: {avg_fps:.2f} img/s') + print(f'FPS Variance of {args.repeat_times} runs: {var_fps:.4f}') + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/Tool_get_params_and_FLOPs.py b/Seg_All_In_One_SegModel/Tool_get_params_and_FLOPs.py new file mode 100644 index 0000000..0d02ddd --- /dev/null +++ b/Seg_All_In_One_SegModel/Tool_get_params_and_FLOPs.py @@ -0,0 +1,89 @@ +# get_flops_smp.py +import torch +import argparse +from fvcore.nn import FlopCountAnalysis, flop_count_table + +# 同样,需要能够导入 train_smp.py 中的函数 +from train import initialize_components +import config + +# TODO 使用方法 TODO +# python Tool_get_params_and_FLOPs.py -a Unet --shape 512 512 + +def parse_args(): + parser = argparse.ArgumentParser(description='Get FLOPs and Params of an SMP model') + parser.add_argument( + '-a', '--architecture', + type=str, + required=True, + choices=list(config.ALL_MODEL_CONFIGS.keys()), + help="The model architecture to analyze." + ) + parser.add_argument( + '--shape', + type=int, + nargs='+', + default=[512, 512], + help='Input image size for analysis, e.g., --shape 512 512' + ) + args = parser.parse_args() + return args + +def main(): + args = parse_args() + + if len(args.shape) == 1: + h, w = args.shape[0], args.shape[0] + elif len(args.shape) == 2: + h, w = args.shape[0], args.shape[1] + else: + raise ValueError('Invalid input shape. Use one or two integers.') + + # 1. 初始化模型 + print(f"Initializing model: '{args.architecture}'...") + num_classes = len(config.CLASSES) + model, _, _, _ = initialize_components(args.architecture, num_classes, config.SEG_MODE) + + # 如果模型在训练时使用了 DataParallel,需要移除 + if isinstance(model, torch.nn.DataParallel): + model = model.module + + model.to(config.DEVICE) + model.eval() + + # 2. 创建伪造输入 + dummy_input = torch.randn(1, 3, h, w).to(config.DEVICE) + + # 3. 计算参数量 + # 分别计算总参数和可训练参数 + total_params = sum(p.numel() for p in model.parameters()) + trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) + + # 4. 使用 fvcore 计算 FLOPs + print("Analyzing model FLOPs... (This may take a moment)") + flops = FlopCountAnalysis(model, dummy_input) + + # 5. 打印结果 + split_line = '=' * 30 + print(f"\n{split_line}") + print(f"Model: {args.architecture}") + print(f"Input shape: (1, 3, {h}, {w})") + print(f"{split_line}") + + # 使用 fvcore 的表格打印工具,非常清晰 + print(flop_count_table(flops, max_depth=4)) + + # 打印总结 + gflops = flops.total() / 1e9 + total_params_m = total_params / 1e6 + trainable_params_m = trainable_params / 1e6 + + print(f"{split_line}") + print(f"Total FLOPs: {gflops:.2f} GFLOPs") + print(f"Total Parameters: {total_params_m:.2f} M") + print(f"Trainable Parameters: {trainable_params_m:.2f} M") + print(f"{split_line}") + print('!!! Please be cautious: FLOPs computation may not be perfectly accurate for all custom ops.') + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/config.py b/Seg_All_In_One_SegModel/config.py new file mode 100644 index 0000000..36caec5 --- /dev/null +++ b/Seg_All_In_One_SegModel/config.py @@ -0,0 +1,153 @@ +import torch +from pathlib import Path + +# --- 1. 核心目录设置 (Core Directories) --- +# 使用 pathlib 进行路径管理,更现代、更健壮 +HARDISK_DIR = Path('../Hardisk') +DATA_SETS_DIR = Path('../DataSet_Public_outputs') # 模型保存位置 +PREDICT_ALL_BEST_MODELS_DIR = Path('../BestMode_Predict_Results_DataSet_Public') # 预测模型存储位置 + +# V1: 1_CholecSeg8k-13Type-1920x1080 +# DATA_DIR = Path('../DataSet_Public/1_CholecSeg8k-13Type-1920x1080') # Path(__file__).parent.parent【有中文放弃了】 # 项目根目录 (假设 config.py 在 src/ 下) +# OUTPUTS_DIR = DATA_SETS_DIR / "1_CholecSeg8k-13Type-1920x1080_outputs-SegModel" # 所有输出文件的根目录 # train 中 Path(config.OUTPUTS_DIR / architecture) +# PREDICT_BEST_MODEL_DIR = PREDICT_ALL_BEST_MODELS_DIR / "1_CholecSeg8k-13Type-1920x1080_outputs-SegModel" # 最优模型位置 # train 中 Path(config.PREDICT_BEST_MODEL_DIR / architecture) +# # V2:2_AutoLaparo-10Type-1920x1080 +# DATA_DIR = Path('../DataSet_Public/2_AutoLaparo-10Type-1920x1080') # Path(__file__).parent.parent【有中文放弃了】 # 项目根目录 (假设 config.py 在 src/ 下) +# OUTPUTS_DIR = DATA_SETS_DIR / "2_AutoLaparo-10Type-1920x1080_outputs-SegModel" # 所有输出文件的根目录 # train 中 Path(config.OUTPUTS_DIR / architecture) +# PREDICT_BEST_MODEL_DIR = PREDICT_ALL_BEST_MODELS_DIR / "2_AutoLaparo-10Type-1920x1080_outputs-SegModel" # 最优模型位置 # train 中 Path(config.PREDICT_BEST_MODEL_DIR / architecture) +# # V3:3_1_Endovis_2017-8Type-512x512 +# DATA_DIR = Path('../DataSet_Public/3_1_Endovis_2017-8Type-512x512') # Path(__file__).parent.parent【有中文放弃了】 # 项目根目录 (假设 config.py 在 src/ 下) +# OUTPUTS_DIR = DATA_SETS_DIR / "3_1_Endovis_2017-8Type-512x512_outputs-SegModel" # 所有输出文件的根目录 # train 中 Path(config.OUTPUTS_DIR / architecture) +# PREDICT_BEST_MODEL_DIR = PREDICT_ALL_BEST_MODELS_DIR / "3_1_Endovis_2017-8Type-512x512_outputs-SegModel" # 最优模型位置 # train 中 Path(config.PREDICT_BEST_MODEL_DIR / architecture) +# # V4:3_2_Endovis_2018-8Type-512x512 +# DATA_DIR = Path('../DataSet_Public/3_2_Endovis_2018-8Type-512x512') # Path(__file__).parent.parent【有中文放弃了】 # 项目根目录 (假设 config.py 在 src/ 下) +# OUTPUTS_DIR = DATA_SETS_DIR / "3_2_Endovis_2018-8Type-512x512_outputs-SegModel" # 所有输出文件的根目录 # train 中 Path(config.OUTPUTS_DIR / architecture) +# PREDICT_BEST_MODEL_DIR = PREDICT_ALL_BEST_MODELS_DIR / "3_2_Endovis_2018-8Type-512x512_outputs-SegModel" # 最优模型位置 # train 中 Path(config.PREDICT_BEST_MODEL_DIR / architecture) +# # V5:4_Dresden-11Type-512x512 +# DATA_DIR = Path('../DataSet_Public/4_Dresden-11Type-512x512') # Path(__file__).parent.parent【有中文放弃了】 # 项目根目录 (假设 config.py 在 src/ 下) +# OUTPUTS_DIR = DATA_SETS_DIR / "4_Dresden-11Type-512x512_outputs-SegModel" # 所有输出文件的根目录 # train 中 Path(config.OUTPUTS_DIR / architecture) +# PREDICT_BEST_MODEL_DIR = PREDICT_ALL_BEST_MODELS_DIR / "4_Dresden-11Type-512x512_outputs-SegModel" # 最优模型位置 # train 中 Path(config.PREDICT_BEST_MODEL_DIR / architecture) +# # Test_V1:5_Predict_Video +DATA_DIR = Path('../DataSet_Public/5_Predict_Video/LC_Video_1') # Path(__file__).parent.parent【有中文放弃了】 # 项目根目录 (假设 config.py 在 src/ 下) +OUTPUTS_DIR = None # 所有输出文件的根目录 # train 中 Path(config.OUTPUTS_DIR / architecture) +PREDICT_BEST_MODEL_DIR = PREDICT_ALL_BEST_MODELS_DIR / "LC_Video_1_outputs-SegModel" # 最优模型位置 # train 中 Path(config.PREDICT_BEST_MODEL_DIR / architecture) + +# --- 2. 训练与验证数据路径 (Training & Validation Paths for train.py) --- +# 在 train.py 中已使用 +TRAIN_IMAGE_DIR = DATA_DIR / "images" / "train" +TRAIN_MASK_DIR = DATA_DIR / "labels_GT" / "train" +VAL_IMAGE_DIR = DATA_DIR / "images" / "val" # TODO "val_images" +VAL_MASK_DIR = DATA_DIR / "labels_GT" / "val" # TODO "val_masks" + +# --- 3. 预测数据路径 (Prediction Paths for predict.py) --- +TEST_IMAGE_DIR = DATA_DIR / "images" / "val" # 测试图像目录 # TODO "test_images" +TEST_MASK_DIR = DATA_DIR / "labels_GT" / "val" # 测试掩码目录 (用于评估) # TODO "test_masks" + +# --- 4. 输出文件与目录路径 (Output Files & Directories) --- +RAW_MASK_FOLDER = "predicted_raw_masks" # 存放预测出的单通道原始掩码 +ANALYSIS_RESULTS_FOLDER = "prediction_analysis" # 存放对比图、曲线和指标CSV + +# 训练过程中的输出文件 +BEST_MODEL_SAVE_NAME = "best_model.pth" +METRICS_CSV_NAME = "training_metrics.csv" + +# --- 5. 模型与数据参数 (Model & Data Parameters) --- +DEVICE = "cuda" if torch.cuda.is_available() else "cpu" + +# --- 5.1. 【新增】模型选择与参数化配置 --- +# ※ 不定义训练用模型,在train、predict中定义 ※ + +# 这是一个“配置库”,存放了所有模型的参数设置 # 具体请参考: https://smp.readthedocs.io/en/latest/models.html +ALL_MODEL_CONFIGS = { + 'Unet': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + 'decoder_channels': (256, 128, 64, 32, 16), + 'decoder_attention_type': None, # 可选 'scse' + }, + 'UnetPlusPlus': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + 'decoder_channels': (256, 128, 64, 32, 16), + 'decoder_attention_type': None, # 可选 'scse' + }, + 'FPN': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + 'PSPNet': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + 'DeepLabV3': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + 'DeepLabV3Plus': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + + 'Linknet': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + 'MAnet': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + 'PAN': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + 'UPerNet': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + 'Segformer': { + 'encoder_name': 'resnet34', + 'encoder_weights': 'imagenet', + }, + 'DPT': { + 'encoder_name': 'tu-vit_base_patch16_224.augreg_in21k', + 'encoder_weights': 'imagenet', + } +} + +SEG_MODE = "multiclass" # 分割模式:'multiclass' 或 'multilabel' + +# TODO 评估参数排除项:在计算评估指标 (如 IoU, F1-score) 时要忽略的类别列表。 TODO +EVALUATION_CLASSES_TO_IGNORE = [] # 如果列表为空 [], 则评估所有类别。 +# EVALUATION_CLASSES_TO_IGNORE = ['background'] # 例如,设置为 ['background'] 将在计算总体指标时排除背景类。 +IGNORE_INDEX = -100 # 当不包含背景时,掩码中背景像素的值,损失函数会忽略它 + +# V1:1_CholecSeg8k-13Type-1920x1080 +CLASSES = ['background', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] # 根据您的数据集修改 +CLASS_RGB_VALUES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] # 根据您的数据集修改 +# V2:2_AutoLaparo-10Type-1920x1080 +# CLASSES = ['background', '1', '2', '3', '4', '5', '6', '7', '8', '9'] # 根据您的数据集修改 +# CLASS_RGB_VALUES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 根据您的数据集修改 +# V3:3_1_Endovis_2017-8Type-512x512 +# CLASSES = ['background', '1', '2', '3', '4', '5', '6', '7'] # 根据您的数据集修改 +# CLASS_RGB_VALUES = [0, 1, 2, 3, 4, 5, 6, 7] # 根据您的数据集修改 +# V4:3_2_Endovis_2018-8Type-512x512 +# CLASSES = ['background', '1', '2', '3', '4', '5', '6', '7'] # 根据您的数据集修改 +# CLASS_RGB_VALUES = [0, 1, 2, 3, 4, 5, 6, 7] # 根据您的数据集修改 +# V5:4_Dresden-11Type-512x512 +# CLASSES = ['background', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] # 根据您的数据集修改 +# CLASS_RGB_VALUES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 根据您的数据集修改 + +# 图像尺寸 +IMAGE_HEIGHT = 256 # 512 +IMAGE_WIDTH = 256 # 512 + +# --- 6. 训练超参数 (Training Hyperparameters) --- +BATCH_SIZE = 16 +NUM_WORKERS = 8 +EPOCHS = 300 +LEARNING_RATE = 1e-4 # 1e-3 # MANet_降低学习率至 1e-4 +WEIGHT_DECAY = 1e-4 # L2 正则化权重衰减 +PIN_MEMORY = True # 是否将数据加载到锁页内存中以加速传输到 GPU +FBETA_BETA = 1.0 # F-beta score 的 beta 值。beta=1 等同于 F1-score +EARLY_STOPPING_PATIENCE = 100 # 早停机制的耐心值 +CKPT_SAVE_INTERVAL = 10 # 每隔多少轮保存一次检查点模型 \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/dataset.py b/Seg_All_In_One_SegModel/dataset.py new file mode 100644 index 0000000..4a3b8df --- /dev/null +++ b/Seg_All_In_One_SegModel/dataset.py @@ -0,0 +1,99 @@ +import os +import cv2 +import numpy as np +import torch +from torch.utils.data import Dataset + +class SegmentationDataset(Dataset): + """ + 自定义图像分割数据集。 + 根据指定的 seg_mode,可以生成适用于 'multiclass' 或 'multilabel' 任务的掩码。 + """ + def __init__(self, image_dir, mask_dir, classes, class_rgb_values, seg_mode, augmentation=None): + """ + Args: + image_dir (str): 图像文件目录。 + mask_dir (str): 掩码文件目录。 + classes (list): 类别名称列表。 + class_rgb_values (list): 每个类别在灰度掩码中对应的像素值。 + augmentation (albumentations.Compose, optional): 数据增强流程。 + seg_mode (str): 分割模式, 'multiclass' 或 'multilabel'。 + """ + self.image_dir = image_dir + self.mask_dir = mask_dir + self.image_filenames = sorted(os.listdir(image_dir)) + self.augmentation = augmentation + self.classes = classes + self.class_rgb_values = class_rgb_values + + # 【新增】存储分割模式并进行验证 + self.seg_mode = seg_mode + if self.seg_mode not in ['multiclass', 'multilabel']: + raise ValueError(f"seg_mode must be 'multiclass' or 'multilabel', but got {self.seg_mode}") + + print(f"Found {len(self.image_filenames)} images in {image_dir}. Dataset mode: '{self.seg_mode}'") + + def __len__(self): + return len(self.image_filenames) + + def __getitem__(self, idx): + img_path = os.path.join(self.image_dir, self.image_filenames[idx]) + image = cv2.imread(img_path) + image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + + mask_path = os.path.join(self.mask_dir, self.image_filenames[idx]) + # 读取灰度图 + mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) + + if mask is None: + raise FileNotFoundError(f"Mask file not found or could not be read: {mask_path}") + + # 【核心修改】根据 seg_mode 选择掩码的处理方式 + if self.seg_mode == 'multilabel': + # 为 'multilabel' 模式创建 one-hot 编码掩码 + processed_mask = self._create_one_hot_mask(mask) + else: # 'multiclass' + # 为 'multiclass' 模式创建类索引掩码 + processed_mask = self._create_class_index_mask(mask) + + if self.augmentation: + # Albumentations 可以同时处理 (H,W,C) 和 (H,W) 格式的掩码 + sample = self.augmentation(image=image, mask=processed_mask) + image = sample['image'] + mask = sample['mask'] + else: + mask = processed_mask + + # 【核心修改】根据 seg_mode 对最终的掩码张量进行处理 + if self.seg_mode == 'multilabel': + # 调整维度顺序 (H, W, C) -> (C, H, W) 并转换为 float + mask = mask.permute(2, 0, 1).float() + else: # 'multiclass' + # 直接转换为 long 类型,不需要调整维度 + mask = mask.long() + + return image, mask + + def _create_one_hot_mask(self, mask): + """ + 将单通道的灰度掩码 (H, W) 转换为 one-hot 编码的掩码 (H, W, C)。 + 这是为 'multilabel' 模式准备的。 + 返回一个 NumPy 数组。 + """ + semantic_map = np.zeros((mask.shape[0], mask.shape[1], len(self.class_rgb_values)), dtype=np.uint8) + for i, value in enumerate(self.class_rgb_values): + semantic_map[:, :, i] = (mask == value).astype(np.uint8) + return semantic_map + + def _create_class_index_mask(self, mask): + """ + 【新增函数】 + 将单通道的灰度掩码 (H, W) 转换为类索引掩码 (H, W)。 + 这是为 'multiclass' 模式准备的。 + 返回一个 NumPy 数组。 + """ + class_index_map = np.zeros(mask.shape, dtype=np.uint8) + for i, value in enumerate(self.class_rgb_values): + # 将灰度值为 value 的像素,其类别索引设置为 i + class_index_map[mask == value] = i + return class_index_map \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/loss.py b/Seg_All_In_One_SegModel/loss.py new file mode 100644 index 0000000..91301cc --- /dev/null +++ b/Seg_All_In_One_SegModel/loss.py @@ -0,0 +1,86 @@ +# In utils.py or a new losses.py +import config +import torch.nn as nn +import segmentation_models_pytorch as smp + +class UNetPlusPlusLoss(nn.Module): + """ + 仿照 UNet++ 官方实现,结合了 BCE 和 Dice Loss。 + 这个损失函数适用于 segmentation-models-pytorch 库的输出。 + + Args: + mode (str): DiceLoss 的模式, e.g., 'multilabel', 'multiclass'. + bce_weight (float): BCE Loss 在总损失中所占的权重。 + dice_weight (float): Dice Loss 在总损失中所占的权重。 + """ + def __init__(self, mode=config.SEG_MODE, bce_weight=0.5, dice_weight=0.5): + super(UNetPlusPlusLoss, self).__init__() + + # 使用数值更稳定的 BCEWithLogitsLoss + self.bce_loss = nn.BCEWithLogitsLoss() + self.dice_loss = smp.losses.DiceLoss(mode=mode) + self.bce_weight = bce_weight + self.dice_weight = dice_weight + + def forward(self, y_pred, y_true): + """ + 计算组合损失。 + + Args: + y_pred: 模型的预测输出 (logits)。 + y_true: 真实的标签 (mask)。 + + Returns: + 组合后的损失值。 + """ + bce = self.bce_loss(y_pred, y_true) + dice = self.dice_loss(y_pred, y_true) + + # 按照权重将两种损失相加 + total_loss = self.bce_weight * bce + self.dice_weight * dice + return total_loss + +class MultiClassLoss(nn.Module): + """ + A combined loss function for multi-class segmentation. + This loss combines CrossEntropyLoss and DiceLoss, which is a common and effective + practice for semantic segmentation tasks. + + Args: + mode (str): DiceLoss mode, should be 'multiclass'. + ce_weight (float): Weight for the CrossEntropyLoss component. + dice_weight (float): Weight for the DiceLoss component. + ignore_index (int): Specifies a target value that is ignored and does not contribute to the input gradient. + """ + def __init__(self, mode='multiclass', ce_weight=0.5, dice_weight=0.5, ignore_index=-100): + super(MultiClassLoss, self).__init__() + + # CrossEntropyLoss is the standard for multi-class classification. + # It combines LogSoftmax and NLLLoss in one single class. + self.ce_loss = nn.CrossEntropyLoss(ignore_index=ignore_index) + + # DiceLoss in 'multiclass' mode works correctly with class indices. + self.dice_loss = smp.losses.DiceLoss(mode=mode) + + self.ce_weight = ce_weight + self.dice_weight = dice_weight + + def forward(self, y_pred, y_true): + """ + Calculates the combined loss. + + Args: + y_pred: Model predictions (logits), shape (N, C, H, W). + y_true: Ground truth labels (class indices), shape (N, H, W) and dtype=torch.long. + + Returns: + The combined loss value. + """ + # Ensure target tensor is of type long for CrossEntropyLoss + y_true = y_true.long() + + ce = self.ce_loss(y_pred, y_true) + dice = self.dice_loss(y_pred, y_true) + + total_loss = self.ce_weight * ce + self.dice_weight * dice + return total_loss \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/predict.sh b/Seg_All_In_One_SegModel/predict.sh new file mode 100644 index 0000000..a3c5c73 --- /dev/null +++ b/Seg_All_In_One_SegModel/predict.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# --- 1. Conda 环境设置 --- +CONDA_BASE_PATH="/home/wkmgc/miniconda3" # <--- 在这里修改为您自己的路径 +CONDA_ENV_NAME="${SEG_CONDA_ENV:-seg_smp}" # 可用 SEG_CONDA_ENV=SMP bash predict.sh 临时覆盖 + +# 初始化并激活 Conda 环境 +if [ -f "${CONDA_BASE_PATH}/etc/profile.d/conda.sh" ]; then + source "${CONDA_BASE_PATH}/etc/profile.d/conda.sh" + conda activate "${CONDA_ENV_NAME}" + if [ $? -ne 0 ]; then + echo "错误: 激活 Conda 环境 '${CONDA_ENV_NAME}' 失败!" + exit 1 + fi + echo "Conda 环境 '${CONDA_ENV_NAME}' 已成功激活。" +else + echo "错误: 找不到 conda.sh 脚本。请检查您的 CONDA_BASE_PATH 设置是否正确。" + exit 1 +fi + +# --- 2. 模型与 GPU 配置 --- +GPUS_GROUP_1="2" +GPUS_GROUP_2="3" +GPUS_GROUP_3="4" +GPUS_GROUP_4="5" + +# 注意:这里的模型架构列表应与 train.sh 保持一致,以确保能找到对应的训练好的模型 +GROUP_1_ARCHS=("PSPNet" "Unet" "UnetPlusPlus" ) +GROUP_2_ARCHS=("Linknet" "MAnet" "DeepLabV3" ) +GROUP_3_ARCHS=("UPerNet" "Segformer" "DPT" ) +GROUP_4_ARCHS=("FPN" "DeepLabV3Plus" "PAN") + +# 1. 从 config.py 中读取 PREDICT_BEST_MODEL_DIR 的值 +PREDICT_BEST_MODEL_DIR=$(python -c "from config import PREDICT_BEST_MODEL_DIR; print(PREDICT_BEST_MODEL_DIR)") +# 检查是否成功获取了 PREDICT_BEST_MODEL_DIR +if [ -z "$PREDICT_BEST_MODEL_DIR" ]; then + echo "PREDICT_BEST_MODEL_DIR: $PREDICT_BEST_MODEL_DIR" + echo "Error: Could not read PREDICT_BEST_MODEL_DIR from yolo_config.py. Exiting." + echo "Error 2: Or the directory specified by PREDICT_BEST_MODEL_DIR does not exist. Please create it first." + exit 1 +fi +# 2. 定义带有时间戳的日志目录名 +LOG_DIR_NAME="predict_logs_parallel_$(date +%Y-%m-%d_%H-%M-%S)" +# 3. 拼接成最终的完整路径 +LOG_DIR="$PREDICT_BEST_MODEL_DIR/$LOG_DIR_NAME" +mkdir -p "${LOG_DIR}" +echo "所有模型的预测日志将保存在 ./${LOG_DIR}/ 目录中。" +echo "----------------------------------------------------" + + +# --- 3. 依次启动所有预测任务 --- +# 脚本将按顺序逐一执行每个模型的预测,等待上一个完成后再开始下一个。 + +echo ">>> 准备启动第一组预测任务 (后台运行)..." +for arch in "${GROUP_1_ARCHS[@]}"; do + echo " -> 正在后台启动模型: ${arch} on GPUs: ${GPUS_GROUP_1}" + # 【修改点】: 使用 'echo "1" |' 来自动回答 1_predict.py 中的交互式提问,选择第一个找到的模型。 + echo "1" | CUDA_VISIBLE_DEVICES=${GPUS_GROUP_1} python 1_predict.py -a "${arch}" > "${LOG_DIR}/${arch}.log" 2>&1 & + echo " - 模型 ${arch} 预测已在后台启动。日志文件: ${LOG_DIR}/${arch}.log" + echo " - 等待 60 秒..." + sleep 60 +done +echo ">>> 第一组所有模型均已启动。" +echo "----------------------------------------------------" + +echo ">>> 准备启动第二组预测任务 (后台运行)..." +for arch in "${GROUP_2_ARCHS[@]}"; do + echo " -> 正在后台启动模型: ${arch} on GPUs: ${GPUS_GROUP_2}" + # 【修改点】: 自动选择第一个模型 + echo "1" | CUDA_VISIBLE_DEVICES=${GPUS_GROUP_2} python 1_predict.py -a "${arch}" > "${LOG_DIR}/${arch}.log" 2>&1 & + echo " - 模型 ${arch} 预测已在后台启动。日志文件: ${LOG_DIR}/${arch}.log" + echo " - 等待 50 秒..." + sleep 60 +done +echo ">>> 第二组所有模型均已启动。" +echo "----------------------------------------------------" + +echo ">>> 准备启动第三组预测任务 (后台运行)..." +for arch in "${GROUP_3_ARCHS[@]}"; do + echo " -> 正在后台启动模型: ${arch} on GPUs: ${GPUS_GROUP_3}" + # 【修改点】: 自动选择第一个模型 + echo "1" | CUDA_VISIBLE_DEVICES=${GPUS_GROUP_3} python 1_predict.py -a "${arch}" > "${LOG_DIR}/${arch}.log" 2>&1 & + echo " - 模型 ${arch} 预测已在后台启动。日志文件: ${LOG_DIR}/${arch}.log" + echo " - 等待 50 秒..." + sleep 60 +done +echo ">>> 第三组所有模型均已启动。" +echo "----------------------------------------------------" + +echo ">>> 准备启动第四组预测任务 (后台运行)..." +for arch in "${GROUP_4_ARCHS[@]}"; do + echo " -> 正在后台启动模型: ${arch} on GPUs: ${GPUS_GROUP_4}" + # 【修改点】: 自动选择第一个模型 + echo "1" | CUDA_VISIBLE_DEVICES=${GPUS_GROUP_4} python 1_predict.py -a "${arch}" > "${LOG_DIR}/${arch}.log" 2>&1 & + echo " - 模型 ${arch} 预测已在后台启动。日志文件: ${LOG_DIR}/${arch}.log" + echo " - 等待 50 秒..." + sleep 60 +done +echo ">>> 第四组所有模型均已启动。" +echo "----------------------------------------------------" + +# --- 4. 等待所有后台任务完成 --- +echo "" +echo "--- 所有模型均已在后台启动。现在等待所有预测任务完成... ---" +# 'wait' 命令会暂停脚本,直到所有由此脚本启动的后台任务全部执行完毕 +wait +echo "--- 所有后台预测任务已全部完成! ---" + +# 退出前取消激活环境 +conda deactivate diff --git a/Seg_All_In_One_SegModel/requirements.txt b/Seg_All_In_One_SegModel/requirements.txt new file mode 100644 index 0000000..087fe48 --- /dev/null +++ b/Seg_All_In_One_SegModel/requirements.txt @@ -0,0 +1,10 @@ +opencv-python +albumentations +matplotlib +numpy +pandas +scikit_learn +scipy +segmentation_models_pytorch==0.5.1.dev0 +torch +tqdm diff --git a/Seg_All_In_One_SegModel/train.py b/Seg_All_In_One_SegModel/train.py new file mode 100644 index 0000000..cc284b0 --- /dev/null +++ b/Seg_All_In_One_SegModel/train.py @@ -0,0 +1,312 @@ +import logging, argparse, shutil +from datetime import datetime +from pathlib import Path +from typing import Dict, Tuple +import gc + +import numpy as np +import segmentation_models_pytorch as smp +import torch +import torch.optim as optim +from torch.optim import lr_scheduler # 学习率调度器 +from torch.amp import GradScaler +from torch.utils.data import DataLoader + +# 本地应用/库的导入 +import config, os +import utils +from dataset import SegmentationDataset +from loss import MultiClassLoss, UNetPlusPlusLoss + +# --- 日志设置 --- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[ + logging.StreamHandler() # 输出日志到控制台 + ] +) + +# --- 辅助函数:移动训练结果文件夹从源输出目录移动到配置的硬盘目录 --- +def move_results_to_hardisk(project_folder: str): + """ + 将指定的训练结果文件夹从源输出目录移动到配置的硬盘目录。 + 移动操作包括复制整个文件夹树,然后在复制成功后删除原始文件夹。 + + 参数: + project_folder (str): 要移动的项目文件夹的名称。 + 该名称通常由模型架构和时间戳构成。 + """ + source_dir = Path(config.OUTPUTS_DIR) / project_folder + # 确保目标硬盘目录存在 + outputs_folder_name = Path(config.OUTPUTS_DIR).name + destination_dir = Path(config.HARDISK_DIR) / outputs_folder_name / project_folder + destination_dir.parent.mkdir(parents=True, exist_ok=True) + + logging.info(f"准备将结果从 {source_dir} 移动到 {destination_dir}...") + + try: + # 步骤 1: 复制文件夹 + shutil.copytree(source_dir, destination_dir) + logging.info(f"成功复制结果到: {destination_dir}") + + # 步骤 2: 复制成功后,删除原文件夹 + logging.info(f"正在删除原文件夹: {source_dir}") + shutil.rmtree(source_dir) + logging.info("成功删除原文件夹。") + logging.info(f"任务完成,最终结果已保存至: {destination_dir}") + + except Exception as e: + logging.error(f"移动文件夹时发生错误: {e}") + logging.warning(f"原始训练结果仍保留在: {source_dir}") + +# 1. 根据配置文件初始化模型、损失函数和优化器。 +def initialize_components(model_architecture:str, num_classes: int, seg_mode: str) -> Tuple: + """ + 根据配置文件动态初始化模型、损失函数和优化器。 + """ + # --- 初始化模型 --- + logging.info(f"正在初始化模型: '{model_architecture}'...") + + # 使用传入的 model_architecture 作为 key 来获取参数 + try: + model_params = config.ALL_MODEL_CONFIGS[model_architecture] + model_class = getattr(smp, model_architecture) + except KeyError: + logging.error(f"模型 '{model_architecture}' 的配置未在 config.py 的 ALL_MODEL_CONFIGS 中定义!") + raise + except AttributeError: + logging.error(f"模型 '{model_architecture}' 在 segmentation_models_pytorch 库中不存在!") + raise + + # 2. 准备参数字典 + # 首先复制 config 中的参数,然后添加固定的 `in_channels` 和 `classes` + params = model_params.copy() + params['in_channels'] = 3 + params['classes'] = num_classes + + # ======================== 【新增代码段开始】 ======================== # + # 自动检测 encoder_name 是否包含 'vit' + encoder_name = model_params.get('encoder_name', '') + if 'vit' in encoder_name.lower(): + params['dynamic_img_size'] = True + logging.info(f"检测到 ViT 编码器 ('{encoder_name}')。自动设置 dynamic_img_size=True。") + # ======================== 【新增代码段结束】 ======================== # + + # 3. 使用字典解包 (**) 将所有参数传递给模型构造函数 + # 这种方式非常灵活,无论你在 config 中定义了多少参数,都能正确传递 + model = model_class(**params).to(config.DEVICE) + + # --- 设置损失函数 (保持不变) --- + logging.info(f"为 '{seg_mode}' 模式设置损失函数。") + if seg_mode == 'multiclass': + loss_fn = MultiClassLoss(mode=seg_mode) + elif seg_mode == 'multilabel': + loss_fn = UNetPlusPlusLoss(mode=seg_mode) + else: + raise ValueError(f"无效的 SEG_MODE: '{seg_mode}'。必须是 'multiclass' 或 'multilabel'。") + + # --- 优化器和梯度缩放器 (保持不变) --- + optimizer = optim.Adam(model.parameters(), lr=config.LEARNING_RATE, weight_decay=config.WEIGHT_DECAY) + # --- TODO 添加学习率调度器 TODO --- + # T_max 是调度器周期的最大迭代次数,通常设置为总的 epoch 数量 + scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=config.EPOCHS, eta_min=1e-6) + # ----------------------- + scaler = GradScaler('cuda') + + return model, loss_fn, optimizer, scaler + +def main(model_architecture: str) -> None: + """ + 编排图像分割模型的主要训练流程。 + + 该函数执行以下步骤: + 1. 设置输出目录。 + 2. 准备数据增强,并为训练集和验证集创建 DataLoader 实例。 + 3. 初始化 U-Net++ 模型、损失函数、优化器和梯度缩放器。 + 4. 运行主训练循环,其中包括: + - 训练一个 epoch。 + - 在验证集上评估模型。 + - 将各项指标记录到 CSV 文件。 + - 基于验证损失实现早停机制。 + - 保存性能最佳的模型和定期的断点。 + - 绘制训练进度曲线图。 + 返回: + str: 本次训练运行的文件夹名称 (run_name)。 + """ + logging.info(f"使用设备: {config.DEVICE}") + + # --- 1.1. 使用 pathlib 进行现代化的路径管理 --- + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + run_name = f"{model_architecture}_{timestamp}" + output_dir = Path(config.OUTPUTS_DIR) / run_name # <-- 使用 / 操作符 + metrics_csv_path = output_dir / config.METRICS_CSV_NAME + best_model_path = output_dir / config.BEST_MODEL_SAVE_NAME + + utils.setup_directories(output_dir) + + # --- 1.2. 探测模型以确定最佳图像尺寸 --- + logging.info("正在探测模型以确定输入尺寸要求...") # + num_classes = len(config.CLASSES) # + + # 创建一个临时模型实例,仅用于检查其属性 + # 注意:这里的调用现在更简单,不需要传递 dynamic_img_size + probe_model, _, _, _ = initialize_components( + model_architecture, num_classes, config.SEG_MODE # + ) + + target_height, target_width = config.IMAGE_HEIGHT, config.IMAGE_WIDTH # + + try: + # 检查编码器是否需要固定输入尺寸 + if probe_model.encoder.is_fixed_input_size: + required_size = probe_model.encoder.input_size + target_height = required_size[1] + target_width = required_size[2] + logging.warning( + f"模型 '{model_architecture}' 的编码器需要固定的输入尺寸 " + f"({target_height}x{target_width})。" + f"将忽略 config.py 中的尺寸设置。" + ) + else: + logging.info(f"模型 '{model_architecture}' 支持动态输入尺寸。将使用 config.py 中定义的尺寸 ({target_height}x{target_width})。") + except AttributeError: + logging.info("无法自动检测模型尺寸要求。将使用 config.py 中定义的尺寸。") + + del probe_model # 删除临时模型,释放内存 + + # --- 1.3. 数据加载 --- + logging.info(f"正在设置数据增强和加载器,目标尺寸为 {target_height}x{target_width}...") # + # 使用探测到的或配置中指定的目标尺寸 + train_transform = utils.get_training_augmentation(target_height, target_width) # + val_transform = utils.get_validation_augmentation(target_height, target_width) # + + train_dataset = SegmentationDataset( + image_dir=Path(config.TRAIN_IMAGE_DIR), + mask_dir=Path(config.TRAIN_MASK_DIR), + classes=config.CLASSES, + class_rgb_values=config.CLASS_RGB_VALUES, + augmentation=train_transform, + seg_mode=config.SEG_MODE + ) + val_dataset = SegmentationDataset( + # 已修正路径,使用配置文件中的验证数据目录 + image_dir=Path(config.VAL_IMAGE_DIR), + mask_dir=Path(config.VAL_MASK_DIR), + classes=config.CLASSES, + class_rgb_values=config.CLASS_RGB_VALUES, + augmentation=val_transform, + seg_mode=config.SEG_MODE + ) + + train_loader = DataLoader(train_dataset, batch_size=config.BATCH_SIZE, shuffle=True, pin_memory=config.PIN_MEMORY, num_workers=config.NUM_WORKERS) + val_loader = DataLoader(val_dataset, batch_size=config.BATCH_SIZE, shuffle=False, pin_memory=config.PIN_MEMORY, num_workers=config.NUM_WORKERS) + + # --- 2. 模型、损失函数、优化器、梯度缩放器 --- + num_classes = len(config.CLASSES) + model, loss_fn, optimizer, scaler = initialize_components(model_architecture, num_classes, config.SEG_MODE) + + # --- 启用多 GPU 训练 --- + if torch.cuda.device_count() >= 1: + logging.info(f"正在使用 {torch.cuda.device_count()} 块 GPU 进行并行训练。") + model = torch.nn.DataParallel(model) + + # --- 3. 训练循环 --- + best_val_loss = float('inf') + epochs_no_improve = 0 + + for epoch in range(config.EPOCHS): + logging.info(f"\n--- 第 {epoch+1}/{config.EPOCHS} 轮 ---") + # --- 3.1. 训练集训练数据 --- + train_loss = utils.train_fn(train_loader, model, optimizer, loss_fn, scaler, config.DEVICE, model_architecture) + # --- 3.2. 验证集测试数据【获得指标】 --- + val_metrics_dict = utils.evaluate_fn(val_loader, model, loss_fn, config.DEVICE, num_classes, model_architecture, beta=config.FBETA_BETA) + + # --- 3.3. 日志记录 及 显示 --- + log_data = {'epoch': epoch + 1, 'train_loss': train_loss} + log_data.update(val_metrics_dict) + utils.log_metrics_to_csv(log_data, metrics_csv_path) + + val_loss = val_metrics_dict.get('val_loss') + iou_score = val_metrics_dict.get('iou_score', 0) + fbeta_score = val_metrics_dict.get('fbeta_score', 0) + + logging.info(f"训练集Loss: {train_loss:.4f} | 验证集Loss: {val_loss:.4f} | IoU: {iou_score:.4f} | F-beta: {fbeta_score:.4f}") + + # --- 3.4. 早停与模型检查点 --- + if val_loss < best_val_loss: + best_val_loss = val_loss + epochs_no_improve = 0 + if isinstance(model, torch.nn.DataParallel): + torch.save(model.module.state_dict(), best_model_path) + else: + torch.save(model.state_dict(), best_model_path) + logging.info(f"验证损失改善至 {best_val_loss:.4f}。正在保存最佳模型至 '{best_model_path}'。") + else: + epochs_no_improve += 1 + logging.info(f"验证损失已连续 {epochs_no_improve} 轮未改善。") + + if isinstance(config.EARLY_STOPPING_PATIENCE, int) and epochs_no_improve >= config.EARLY_STOPPING_PATIENCE: + logging.info(f"\n训练在 {epoch + 1} 轮后触发早停。") + logging.warning(f"验证损失已连续 {config.EARLY_STOPPING_PATIENCE} 轮未改善。") + break + + # --- 3.5. 检查点及曲线图保存 --- + checkpoint_path = output_dir / f"epoch_{epoch+1}.pth" + if isinstance(model, torch.nn.DataParallel): + torch.save(model.module.state_dict(), checkpoint_path) + else: + torch.save(model.state_dict(), checkpoint_path) + logging.info(f"第 {epoch+1} 轮的检查点已保存至 '{checkpoint_path}'。") + + # ---------- 清理旧检查点 ---------- + # 只在 epoch > 0 时才开始删,避免第一轮就误删 + if epoch > 0: + # 要尝试删除的 epoch 编号 = 上一轮 + last_epoch = epoch + # 如果上一轮不是“10 的整数倍”就删掉 + if last_epoch % config.CKPT_SAVE_INTERVAL != 0: + old_ckpt = output_dir / f"epoch_{last_epoch}.pth" + if old_ckpt.exists(): + old_ckpt.unlink() # 文件删除 + + utils.plot_training_progress(metrics_csv_path, output_dir) + logging.info("所有训练曲线图已更新并保存。") + + return run_name + +if __name__ == "__main__": + # 创建一个参数解析器 + parser = argparse.ArgumentParser(description="训练图像分割模型。") + + # 添加 --architecture 参数 + # - `choices` 会自动从你的 config 文件中获取所有可用的模型名称 + # - `default` 设置了在不提供参数时的默认模型 + parser.add_argument( + "-a", "--architecture", + type=str, + choices=list(config.ALL_MODEL_CONFIGS.keys()), + required=True, + help="选择要训练的模型架构。" + ) + + # 解析命令行传入的参数 + args = parser.parse_args() + + # 将解析出的架构名称传入 main 函数并执行 + project_folder = None + try: + project_folder = main(model_architecture=args.architecture) + finally: + # 强制进行垃圾回收,释放 Python 对象占用的系统内存 (RAM) + gc.collect() + logging.info("已执行垃圾回收 (gc.collect())。") + # 清理 PyTorch 在 CUDA 上缓存的显存 + torch.cuda.empty_cache() + logging.info("训练结束,已清理 CUDA 缓存。") + + # 训练和清理完成后,移动结果到硬盘 + if project_folder: + move_results_to_hardisk(project_folder) + else: + logging.error("由于训练失败或未生成项目文件夹,未执行结果移动操作。") \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/train.sh b/Seg_All_In_One_SegModel/train.sh new file mode 100644 index 0000000..cf5c796 --- /dev/null +++ b/Seg_All_In_One_SegModel/train.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# --- 1. Conda 环境设置 --- +CONDA_BASE_PATH="/home/wkmgc/miniconda3" # <--- 在这里修改为您自己的路径 +CONDA_ENV_NAME="${SEG_CONDA_ENV:-seg_smp}" # 可用 SEG_CONDA_ENV=SMP bash train.sh 临时覆盖 + +# 初始化并激活 Conda 环境 +if [ -f "${CONDA_BASE_PATH}/etc/profile.d/conda.sh" ]; then + source "${CONDA_BASE_PATH}/etc/profile.d/conda.sh" + conda activate "${CONDA_ENV_NAME}" + if [ $? -ne 0 ]; then + echo "错误: 激活 Conda 环境 '${CONDA_ENV_NAME}' 失败!" + exit 1 + fi + echo "Conda 环境 '${CONDA_ENV_NAME}' 已成功激活。" +else + echo "错误: 找不到 conda.sh 脚本。请检查您的 CONDA_BASE_PATH 设置是否正确。" + exit 1 +fi + +# --- 2. 模型与 GPU 配置 --- +GPUS_GROUP_1="3" +GPUS_GROUP_2="2" +GPUS_GROUP_3="1" +GPUS_GROUP_4="4" + +GROUP_1_ARCHS=("PSPNet" "Unet" "UnetPlusPlus" ) # G3 # "PSPNet" # "Unet" "UnetPlusPlus" "FPN" +GROUP_2_ARCHS=("Linknet" "MAnet" "DeepLabV3" ) # G3 "Linknet" "MAnet" # "DeepLabV3" # "DeepLabV3Plus" +GROUP_3_ARCHS=("UPerNet" "Segformer" "DPT" ) # G3 # "UPerNet" "Segformer" "DPT" # "PAN" +GROUP_4_ARCHS=("FPN" "DeepLabV3Plus" "PAN") + +# 1. 从 config.py 中读取 OUTPUTS_DIR 的值 +OUTPUTS_DIR=$(python -c "from config import OUTPUTS_DIR; print(OUTPUTS_DIR)") +# 检查是否成功获取了 OUTPUTS_DIR +if [ -z "$OUTPUTS_DIR" ]; then + echo "OUTPUTS_DIR: $OUTPUTS_DIR" + echo "Error 1: Could not read OUTPUTS_DIR from config.py. Exiting." + echo "Error 2: Or the directory specified by OUTPUTS_DIR does not exist. Please create it first." + exit 1 +fi +# 2. 定义带有时间戳的日志目录名 +LOG_DIR_NAME="predict_logs_parallel_$(date +%Y-%m-%d_%H-%M-%S)" +# 3. 拼接成最终的完整路径 +LOG_DIR="$OUTPUTS_DIR/$LOG_DIR_NAME" +mkdir -p "${LOG_DIR}" +echo "所有模型的日志将保存在 ./${LOG_DIR}/ 目录中。" +echo "----------------------------------------------------" + + +# --- 3. 依次启动所有训练任务 --- +# 脚本将按顺序逐一执行每个模型的训练,等待上一个完成后再开始下一个。 + +echo ">>> 准备启动第一组训练任务 (后台运行)..." +for arch in "${GROUP_1_ARCHS[@]}"; do + echo " -> 正在后台启动模型: ${arch} on GPUs: ${GPUS_GROUP_1}" + # 使用 '&' 将命令放入后台运行 + CUDA_VISIBLE_DEVICES=${GPUS_GROUP_1} python train.py -a "${arch}" > "${LOG_DIR}/${arch}.log" 2>&1 & + echo " - 模型 ${arch} 已在后台启动。日志文件: ${LOG_DIR}/${arch}.log" + echo " - 等待 50 秒..." + sleep 50 +done +echo ">>> 第一组所有模型均已启动。" +echo "----------------------------------------------------" + +echo ">>> 准备启动第二组训练任务 (后台运行)..." +for arch in "${GROUP_2_ARCHS[@]}"; do + echo " -> 正在后台启动模型: ${arch} on GPUs: ${GPUS_GROUP_2}" + CUDA_VISIBLE_DEVICES=${GPUS_GROUP_2} python train.py -a "${arch}" > "${LOG_DIR}/${arch}.log" 2>&1 & + echo " - 模型 ${arch} 已在后台启动。日志文件: ${LOG_DIR}/${arch}.log" + echo " - 等待 50 秒..." + sleep 50 +done +echo ">>> 第二组所有模型均已启动。" +echo "----------------------------------------------------" + +echo ">>> 准备启动第三组训练任务 (后台运行)..." +for arch in "${GROUP_3_ARCHS[@]}"; do + echo " -> 正在后台启动模型: ${arch} on GPUs: ${GPUS_GROUP_3}" + CUDA_VISIBLE_DEVICES=${GPUS_GROUP_3} python train.py -a "${arch}" > "${LOG_DIR}/${arch}.log" 2>&1 & + echo " - 模型 ${arch} 已在后台启动。日志文件: ${LOG_DIR}/${arch}.log" + echo " - 等待 50 秒..." + sleep 50 +done +echo ">>> 第三组所有模型均已启动。" +echo "----------------------------------------------------" + +echo ">>> 准备启动第四组训练任务 (后台运行)..." +for arch in "${GROUP_4_ARCHS[@]}"; do + echo " -> 正在后台启动模型: ${arch} on GPUs: ${GPUS_GROUP_4}" + CUDA_VISIBLE_DEVICES=${GPUS_GROUP_4} python train.py -a "${arch}" > "${LOG_DIR}/${arch}.log" 2>&1 & + echo " - 模型 ${arch} 已在后台启动。日志文件: ${LOG_DIR}/${arch}.log" + echo " - 等待 50 秒..." + sleep 50 +done +# echo ">>> 第四组所有模型均已启动。" +# echo "----------------------------------------------------" + +# --- 4. 等待所有后台任务完成 --- +echo "" +echo "--- 所有模型均已在后台启动。现在等待所有训练任务完成... ---" +# 'wait' 命令会暂停脚本,直到所有由此脚本启动的后台任务全部执行完毕 +wait +echo "--- 所有后台训练任务已全部完成! ---" + +# 退出前取消激活环境 +conda deactivate diff --git a/Seg_All_In_One_SegModel/utils.py b/Seg_All_In_One_SegModel/utils.py new file mode 100644 index 0000000..bce8513 --- /dev/null +++ b/Seg_All_In_One_SegModel/utils.py @@ -0,0 +1,361 @@ +import os, logging, shutil +import torch +import numpy as np +import pandas as pd +from tqdm import tqdm +from torch.amp import autocast +from typing import Dict, Union, List +import albumentations as A +from albumentations.pytorch import ToTensorV2 +import segmentation_models_pytorch as smp +import matplotlib.pyplot as plt +from pathlib import Path + +import config + +# ------------------- 绘图函数 (全新升级) ------------------- # +def log_metrics_to_csv(metrics_dict: Dict[str, Union[int, float]], csv_path: str) -> None: + """ + 使用 pandas 将一个指标字典记录到 CSV 文件中。 + (此函数保持不变) + """ + df_new_row = pd.DataFrame([metrics_dict]) + if not os.path.isfile(csv_path): + df_new_row.to_csv(csv_path, index=False) + else: + df_new_row.to_csv(csv_path, mode='a', header=False, index=False) + + +def _plot_metric_group(ax, df: pd.DataFrame, group_metrics: List[str], title: str): + """ + 内部辅助函数,用于在给定的坐标轴上绘制一组指标。 + (此函数保持不变) + """ + epochs_range = df['epoch'] + for name in group_metrics: + if f"val_{name}" in df.columns: + metric_col = f"val_{name}" + label = name.replace("_", " ").title() + ax.plot(epochs_range, df[metric_col], 'o-', label=label) + elif f"{name}" in df.columns: + metric_col = f"{name}" + label = name.replace("_", " ").title() + ax.plot(epochs_range, df[metric_col], 'o-', label=label) + else: + print("没有搜索到:", f"val_{name}") + + ax.set_title(title) + ax.set_xlabel('Epoch') + ax.set_ylabel('Score') + ax.legend(loc='best', fontsize='small') + ax.grid(True) + + +def plot_training_progress(csv_path: str, output_dir: str): + """ + 从 metrics.csv 文件读取训练历史数据,并生成所有分组和总览图表。 + (此函数已修正错误) + """ + os.makedirs(output_dir, exist_ok=True) + + try: + df_metrics = pd.read_csv(csv_path) + except FileNotFoundError: + print(f"警告:在路径 {csv_path} 未找到 metrics.csv 文件。跳过绘图。") + return + + if len(df_metrics) < 2: + print("警告:数据点不足 (少于2个),无法生成趋势图。") + return + + epochs_range = df_metrics['epoch'] + train_loss = df_metrics['train_loss'] + val_loss = df_metrics['val_loss'] + + core_metrics = ["iou_score", "fbeta_score", "accuracy"] + pr_metrics = ["precision", "recall", "specificity", "negative_predictive_value"] + error_metrics = ["false_positive_rate", "false_negative_rate", "false_discovery_rate", "false_omission_rate"] + likelihood_metrics = ["positive_likelihood_ratio", "negative_likelihood_ratio"] + + # --- 1. 单独绘制并保存每个指标组的图表 --- + # (这部分逻辑没有变化) + fig_loss, ax_loss = plt.subplots(figsize=(10, 6)) + ax_loss.plot(epochs_range, train_loss, 'o-', label='Training Loss') + ax_loss.plot(epochs_range, val_loss, 'o-', label='Validation Loss') + ax_loss.set_title('Loss Curves') + ax_loss.set_xlabel('Epoch') + ax_loss.set_ylabel('Loss') + ax_loss.legend() + ax_loss.grid(True) + plt.tight_layout() + plt.savefig(os.path.join(output_dir, "loss_curves.png")) + plt.close(fig_loss) + + metric_groups = { + "core_performance": core_metrics, + "precision_recall": pr_metrics, + "error_rates": error_metrics, + "likelihood_ratios": likelihood_metrics + } + for group_name, group_list in metric_groups.items(): + fig_group, ax_group = plt.subplots(figsize=(10, 7)) + _plot_metric_group(ax_group, df_metrics, group_list, f'{group_name.replace("_", " ").title()} Metrics') + plt.tight_layout() + plt.savefig(os.path.join(output_dir, f"metrics_{group_name}.png")) + plt.close(fig_group) + + # --- 2. 绘制并保存 3x2 综合概览图 --- + fig_overview, axes = plt.subplots(3, 2, figsize=(20, 22)) + fig_overview.suptitle('Training Progress Overview', fontsize=20) + ax_list = axes.ravel() + + # 图 1: 损失曲线 (*** 已修正 ***) + # 使用 ax 而不是 ax + ax_list[0].plot(epochs_range, train_loss, 'o-', label='Training Loss') + ax_list[0].plot(epochs_range, val_loss, 'o-', label='Validation Loss') + ax_list[0].set_title('Loss Curves') + ax_list[0].set_xlabel('Epoch') + ax_list[0].set_ylabel('Loss') + ax_list[0].legend() + ax_list[0].grid(True) + + # 图 2: 核心性能指标 + # 这里使用 ax 是正确的 + _plot_metric_group(ax_list[1], df_metrics, core_metrics, 'Core Performance Metrics') + + # 图 3: 精确率/召回率族 + # 这里使用 ax 是正确的 + _plot_metric_group(ax_list[2], df_metrics, pr_metrics, 'Precision-Recall Family') + + # 图 4: 错误率分析 + # 这里使用 ax 是正确的 + _plot_metric_group(ax_list[3], df_metrics, error_metrics, 'Error Rate Analysis') + + # 图 5: 似然比 + # 这里使用 ax 是正确的 + _plot_metric_group(ax_list[4], df_metrics, likelihood_metrics, 'Likelihood Ratios') + + # 隐藏最后一个未使用的子图 (*** 已修正 ***) + # 使用 ax 而不是 ax + ax_list[5].axis('off') + + plt.tight_layout(rect=[0, 0, 1, 0.96]) + plt.savefig(os.path.join(output_dir, "training_progress_overview.png")) + plt.close(fig_overview) + + print(f"所有图表已更新并保存至目录: {output_dir}") + +# ------------------- 训练与评估函数 ('vit'模型不适用amp) ------------------- # +def train_fn(loader, model, optimizer, loss_fn, scaler, device, model_architecture: str): + """一个 epoch 的训练逻辑。""" + loop = tqdm(loader, desc="Training") + total_loss = 0.0 + model.train() + + # 检查是否为ViT模型,决定是否启用AMP + encoder_name = config.ALL_MODEL_CONFIGS[model_architecture].get('encoder_name', '') + use_amp = 'vit' not in encoder_name.lower() + if not use_amp and loop.n == 0: # 只在第一个batch打印一次警告 + logging.warning(f"检测到 ViT 编码器 ('{encoder_name}')。本次训练将禁用自动混合精度 (AMP) 以确保稳定性。") + + for batch_idx, (data, targets) in enumerate(loop): + data = data.to(device) + targets = targets.to(device) + + # 根据 use_amp 标志执行不同代码路径 + if use_amp: + # 【AMP 路径】适用于 CNN 等稳定模型 + with autocast(device_type='cuda'): + predictions = model(data) + loss = loss_fn(predictions, targets) + + optimizer.zero_grad() + scaler.scale(loss).backward() + scaler.step(optimizer) + scaler.update() + else: + # 【Float32 路径】适用于 ViT/DPT 等模型 + predictions = model(data) + loss = loss_fn(predictions, targets) + + optimizer.zero_grad() + loss.backward() + optimizer.step() + + total_loss += loss.item() + loop.set_postfix(loss=loss.item()) + + return total_loss / len(loader) + +# --- 【修改】 --- +def evaluate_fn(loader, model, loss_fn, device, num_classes, model_architecture: str, beta=1.0): + """在验证集上评估模型性能,并计算包括原始TP/TN/FP/FN在内的多种指标。""" + model.eval() + loop = tqdm(loader, desc="Validation") + + total_loss = 0 + all_tp, all_fp, all_fn, all_tn = [], [], [], [] + + # 检查是否为ViT模型,决定是否启用AMP + encoder_name = config.ALL_MODEL_CONFIGS[model_architecture].get('encoder_name', '') + use_amp = 'vit' not in encoder_name.lower() + + with torch.no_grad(): + for batch_idx, (data, targets) in enumerate(loop): + data = data.to(device) + targets = targets.to(device) + + # 根据 use_amp 标志执行不同代码路径 + if use_amp: + # 【AMP 路径】 + with autocast(device_type='cuda'): + predictions = model(data) + if config.SEG_MODE == 'multilabel': + loss = loss_fn(predictions, targets.float()) + else: + loss = loss_fn(predictions, targets.long()) + else: + # 【Float32 路径】 + predictions = model(data) + if config.SEG_MODE == 'multilabel': + loss = loss_fn(predictions, targets.float()) + else: + loss = loss_fn(predictions, targets.long()) + + total_loss += loss.item() + + # --- 【核心修正】根据 SEG_MODE 条件处理 targets --- + # 1. 获取模型预测的类别索引 + preds_indices = torch.argmax(predictions, dim=1) + + # 2. 根据模式确定真实标签的类别索引 + if config.SEG_MODE == 'multilabel': + # 在 multilabel 模式下, targets 是 one-hot, 需要 argmax + targets_indices = torch.argmax(targets, dim=1) + elif config.SEG_MODE == 'multiclass': + # 在 multiclass 模式下, targets 已经是类别索引, 无需处理 + targets_indices = targets + + # 现在 preds_indices 和 targets_indices 都有正确的形状 [N, H, W] + tp, fp, fn, tn = smp.metrics.get_stats( + preds_indices, + targets_indices.long(), # Ensure it's long type for get_stats + mode=config.SEG_MODE, # Metrics are always calculated in multiclass fashion after argmax + num_classes=num_classes + ) + + # get_stats in smp 0.3.3 might return per-image stats, let's sum over batch dimension if needed. + # Assuming get_stats returns (B-size, C,) tensor per image in batch, we stack and sum. -> (B-num, B-size, C,) + all_tp.append(tp) + all_fp.append(fp) + all_fn.append(fn) + all_tn.append(tn) + + # Sum up stats from all batches (Batch-num, Batch-size, C,) -> (C,) + tp = torch.cat(all_tp, dim=0).sum(dim=0) # (N, B, C) -> (C,) + fp = torch.cat(all_fp, dim=0).sum(dim=0) # (N, B, C) -> (C,) + fn = torch.cat(all_fn, dim=0).sum(dim=0) # (N, B, C) -> (C,) + tn = torch.cat(all_tn, dim=0).sum(dim=0) # (N, B, C) -> (C,) + + # ======================== 【新增代码段开始】 ======================== # + # 根据 config.py 中的设置,筛选出用于计算宏观指标的类别 + + # 1. 获取需要忽略的类别的索引 + ignore_indices = [config.CLASSES.index(cls) for cls in config.EVALUATION_CLASSES_TO_IGNORE if cls in config.CLASSES] + + # 2. 创建一个布尔掩码,标记哪些类别需要被保留 + # 默认保留所有类别 + keep_mask = torch.ones(num_classes, dtype=torch.bool, device=tp.device) + if ignore_indices: + # 将需要忽略的类别的标记设置为 False + keep_mask[ignore_indices] = False + logging.info(f"评估时将忽略类别: {config.EVALUATION_CLASSES_TO_IGNORE}") + + # 3. 使用掩码过滤统计数据 + tp_filtered = tp[keep_mask] + fp_filtered = fp[keep_mask] + fn_filtered = fn[keep_mask] + tn_filtered = tn[keep_mask] + # ======================== 【新增代码段结束】 ======================== # + + + # ======================== 【修改代码段开始】 ======================== # + # 使用过滤后的 tp_filtered, fp_filtered 等张量来计算所有宏观指标 + metrics = { + "val_loss": total_loss / len(loader), + "iou_score": smp.metrics.iou_score(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "f1_score": smp.metrics.f1_score(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "fbeta_score": smp.metrics.fbeta_score(tp_filtered, fp_filtered, fn_filtered, tn_filtered, beta=beta, reduction='micro').item(), + "accuracy": smp.metrics.accuracy(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "recall": smp.metrics.recall(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "precision": smp.metrics.precision(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + # ... 对所有使用 smp.metrics 的宏观指标进行同样修改 ... + "specificity": smp.metrics.specificity(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "negative_predictive_value": smp.metrics.negative_predictive_value(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "false_negative_rate": smp.metrics.false_negative_rate(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "false_positive_rate": smp.metrics.false_positive_rate(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "false_discovery_rate": smp.metrics.false_discovery_rate(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "false_omission_rate": smp.metrics.false_omission_rate(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "positive_likelihood_ratio": smp.metrics.positive_likelihood_ratio(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + "negative_likelihood_ratio": smp.metrics.negative_likelihood_ratio(tp_filtered, fp_filtered, fn_filtered, tn_filtered, reduction='micro').item(), + } + # ======================== 【修改代码段结束】 ======================== # + + cpa_per_class = smp.metrics.recall(tp, fp, fn, tn, reduction='none').cpu().numpy().flatten() + if len(cpa_per_class) > 1: + metrics["mpa"] = np.mean(cpa_per_class[1:]).item() + for i, cpa in enumerate(cpa_per_class): + metrics[f"cpa_class_{i}"] = cpa.item() + else: + metrics["mpa"] = cpa_per_class[0].item() + metrics[f"cpa_class_0"] = cpa_per_class[0].item() + + for i in range(num_classes): + metrics[f'tp_class_{i}'] = tp[i].item() + metrics[f'tn_class_{i}'] = tn[i].item() + metrics[f'fp_class_{i}'] = fp[i].item() + metrics[f'fn_class_{i}'] = fn[i].item() + + model.train() + return metrics + +# ------------------- 数据增强 (保持不变) ------------------- # +def get_training_augmentation(height, width): + return A.Compose([ + A.Resize(height, width), + A.HorizontalFlip(p=0.5), + A.VerticalFlip(p=0.5), + A.RandomRotate90(p=0.5), + A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + ToTensorV2(), + ]) + +def get_validation_augmentation(height, width): + return A.Compose([ + A.Resize(height, width), + A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + ToTensorV2(), + ]) + +# TOOL - 获取用于预测的图像预处理流程 +def get_preprocessing_transform(height: int, width: int) -> A.Compose: + """获取用于预测的图像预处理流程。""" + return A.Compose([ + A.Resize(height, width), + A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + ToTensorV2(), + ]) + +# Tool - 为一个全新的训练任务清理并重新创建输出目录。 +def setup_directories(output_dir: Path) -> None: + """ + 为一个全新的训练任务清理并重新创建输出目录。 + + Args: + output_dir (Path): 指向主输出目录的路径对象。 + """ + if output_dir.exists(): + logging.info(f"输出目录 '{output_dir}' 已存在。正在删除...") + shutil.rmtree(output_dir) + logging.info(f"正在 '{output_dir}' 创建新的空目录。") + output_dir.mkdir(parents=True, exist_ok=True) \ No newline at end of file diff --git a/Seg_All_In_One_SegModel/※2025_9_21_使用手册 b/Seg_All_In_One_SegModel/※2025_9_21_使用手册 new file mode 100644 index 0000000..f0e7911 --- /dev/null +++ b/Seg_All_In_One_SegModel/※2025_9_21_使用手册 @@ -0,0 +1,62 @@ +############## A. 创建conda环境 ############## +sudo apt install git +1. +conda create -n SMP python==3.9 +conda activate SMP +2. +# pytorch安装网址:https://pytorch.org/get-started/locally/ +e.g. pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu129 +3. +proxychains pip install git+https://github.com/qubvel/segmentation_models.pytorch +pip show segmentation-models-pytorch +4. +python3 -m pip install -r requirements.txt + +############## B. Train 训练程序 ############## +# A. 第一次训练开一个梯子【需要下载内容】 +export https_proxy=http://127.0.0.1:1080 http_proxy=http://127.0.0.1:1080 +CUDA_VISIBLE_DEVICES=4,5,6,7 python train.py -a Unet +{Unet,UnetPlusPlus,FPN,PSPNet,DeepLabV3,DeepLabV3Plus,Linknet,MAnet,PAN,UPerNet,Segformer,DPT} + +# B. 运行单个训练程序 +1. 在config.py 中修改 训练数据集;CUDA_VISIBLE_DEVICES修改使用显卡;-a 修改算法; +2. CUDA_VISIBLE_DEVICES=0 python ./train.py -a Unet + +# C. 批量运行训练程序 +1. train.sh 修改想让它使用的显卡、算法; +2. bash train.sh +3. 会生成 ./logs_parallel_DATE 的终端记录文件;结果先存储在 ../DataSet_Public_outputs/DATASET_outputs-SegModel 后自动移动到 ../Hardisk/DATASET_outputs-SegModel 中 + +############## C. Predict 推理程序 ############## +# A1. 预先步骤:将模型同步到 Nas_BackUp_Seg 或 ./Hardisk 文件夹中【cd .. && bash ./Back_Up.sh】 +# A2. 如果需要处理自定义数据集,请保证数据仍然是 DATASET/images/[val/test] 格式 +python ../Seg_Predict_Own_Video_V2/1_Save_Frame_V2.py --video ./Video_Name.mp4 --resize "1920x1080" --output_dir "../DataSet_Public/5_Predict_Video" --interval 0.5 +# B1. 将最优模型文件从 ./Nas_BackUp_Seg 移动到 ./BestMode_Predict_Results_DataSet_Public 指定文件夹中 +bash ./Tool_Copy_Best_Model.sh # 修改里面的路径 +# B2. 如果需要处理自定义数据集,请将模型文件夹手动复制为 ./BestMode_Predict_Results_DataSet_Public/DATASET-Yolo 中 +可以先对原有 ./BestMode_Predict_Results_DataSet_Public/ORI_DATASET_outputs-SegModel 改名 +运行 bash ./Tool_Yolo_Copy_Best_Model.sh +在将生成的 ./BestMode_Predict_Results_DataSet_Public/ORI_DATASET_outputs-SegModel 改为 ./BestMode_Predict_Results_DataSet_Public/DATASET_outputs-SegModel +# C. 运行单个推理程序 +1. 在config.py 中修改 预测数据集 及 val/test 、 模型保存路径PREDICT_ALL_BEST_MODELS_DIR/PREDICT_BEST_MODEL_DIR;CUDA_VISIBLE_DEVICES修改使用显卡;-a 修改算法; +2. CUDA_VISIBLE_DEVICES=0 python 1_predict.py -a Unet/UnetPlusPlus/FPN/PSPNet/DeepLabV3/DeepLabV3Plus/Linknet/MAnet/PAN/UPerNet/Segformer/DPT +# D. 批量运行推理程序 +1. predict.sh 修改想让它使用的显卡、算法; +2. bash 1_predict.sh +3. 会生成 ./predict_logs_parallel_DATE 的终端记录文件;结果存储在 ../BestMode_Predict_Results_DataSet_Public/DATASET_outputs-SegModel 中 +# E. 预测模型 参数量、FLOPs、FPS +CUDA_VISIBLE_DEVICES=5 python 2_predict_params_and_FLOPs_V2.py # --shape 512 512 + +############## D. Predict_raw_img_Check 检测推理输出图片是否齐全 ############## +# 检测 config.TEST_IMAGE_DIR 和 config.PREDICT_BEST_MODEL_DIR/****/predicted_raw_masks 中图片是否匹配 +1. 在config.py 中修改 config.DATA_DIR(即config.TEST_IMAGE_DIR) 及 config.PREDICT_BEST_MODEL_DIR;CUDA_VISIBLE_DEVICES修改使用显卡; +2. python 1_predict_raw_masks_check.py + +############## E. 其他辅助脚本 ############## +1. 解决--resume 不能运行的问题 +您可以直接修改mmengine源文件,使其torch.load可以加载完整的检查点 +vim /home/wkmgc/miniconda3/envs/SMP/lib/python3.9/site-packages/mmengine/runner/checkpoint.py +转到第 347 行。您将看到以下代码: +checkpoint = torch.load(filename, map_location=map_location) +更改该行以添加weights_only=False参数 +checkpoint = torch.load(filename, map_location=map_location, weights_only=False) \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Tool_Yolo_Copy_Best_Model.sh b/Seg_All_In_One_YoloModel/Tool_Yolo_Copy_Best_Model.sh new file mode 100644 index 0000000..5004c8d --- /dev/null +++ b/Seg_All_In_One_YoloModel/Tool_Yolo_Copy_Best_Model.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# +----------------------------------------------------------------------------+ +# | 脚本: 将指定的 .pt 文件 (默认为 best.pt) 从 Nas_BackUp_Seg 复制到 BestMode... | +# | 功能: 1. 自动查找源目录中所有 "weights/[指定文件名].pt" 文件。 | +# | 2. 将其复制到目标目录,并从路径中移除 "/weights" 中间层。 +# | 3. 仅在目标文件不存在或内容不一致时才执行复制,提高效率。 +# | 4. 提供可视化进度条和最终统计。 +# | V3.0: «-- 修改: 改为使用 --pt_name 命名参数 | +# | 用法: ./script.sh [--pt_name "文件名.pt"] (例如: ./script.sh --pt_name "100.pt") «-- 修改 +# +----------------------------------------------------------------------------+ + +# --- 1. 目录设置 --- +SOURCE_BASE_DIR=$(realpath "../Hardisk") +DEST_BASE_DIR=$(realpath "../BestMode_Predict_Results_DataSet_Public") + +# --- 1b. 参数设置 --- «-- 修改: 替换为参数解析循环 +# 设置默认文件名 +TARGET_FILENAME="best.pt" + +# 解析命令行参数 +while [[ "$#" -gt 0 ]]; do + case $1 in + --pt_name) + if [[ -n "$2" && ! "$2" =~ ^-- ]]; then + TARGET_FILENAME="$2" + shift # 消耗 "--pt_name" + shift # 消耗它的值 + else + echo "错误: '--pt_name' 需要一个文件名参数。" >&2 + exit 1 + fi + ;; + *) + echo "未知参数: $1" >&2 + exit 1 + ;; + esac +done + +# --- 2. 环境检查 --- +if [ ! -d "$SOURCE_BASE_DIR" ]; then + echo "错误: 源目录 '$SOURCE_BASE_DIR' 不存在。" + exit 1 +fi + +mkdir -p "$DEST_BASE_DIR" + +echo "正在扫描源目录: $SOURCE_BASE_DIR" +echo "目标模型文件: $TARGET_FILENAME" # 告知用户当前在找哪个文件 + +# --- 3. 查找所有符合条件的文件 --- +echo "正在查找所有符合 '...-Yolo/YOLO*_*/weights/$TARGET_FILENAME' 结构的文件..." +readarray -d '' files_to_process < <(find "$SOURCE_BASE_DIR"/*-Yolo -path "*/YOLO*_*/weights/$TARGET_FILENAME" -type f -print0 2>/dev/null) +TOTAL_FILES=${#files_to_process[@]} + +if [ "$TOTAL_FILES" -eq 0 ]; then + echo "在源目录中没有找到任何 '$TARGET_FILENAME' 文件。脚本退出。" + exit 0 +fi + +echo "总共找到 $TOTAL_FILES 个 '$TARGET_FILENAME' 文件需要处理。" +echo "--------------------------------------------------" + +# --- 4. 进度条函数 --- +print_progress() { + local current=$1 + local total=$2 + local term_width=$(tput cols) + local bar_width=$((term_width - 35)) # 为文本留出更多空间 + + if [ "$bar_width" -lt 10 ]; then + bar_width=10 + fi + local percent=$((current * 100 / total)) + local filled_len=$((bar_width * percent / 100)) + local bar="" + for ((i=0; i List[str]: + """ + 扫描输出目录,查找特定基础模型的所有有效的、已完成的训练项目。 + (函数来自 yolo_predict_visualize_nn_V1.py) + """ + trained_models = [] + if not outputs_dir.is_dir(): + logging.warning(f"输出目录不存在: {outputs_dir}") + return [] + + for project_folder in outputs_dir.iterdir(): + if project_folder.is_dir() and project_folder.name.startswith(model_key + '_'): + if (project_folder / 'weights' / pt_name).exists(): + trained_models.append(project_folder.name) + + trained_models.sort() + return trained_models +# ------------------------------------- + +# --- [保留] V3 的 CAM 方法字典 --- +CAM_METHODS = { + "GradCAM": GradCAM, + "GradCAMPlusPlus": GradCAMPlusPlus, + "XGradCAM": XGradCAM, + "EigenCAM": EigenCAM, + "HiResCAM": HiResCAM, + "LayerCAM": LayerCAM, + "RandomCAM": RandomCAM, + "EigenGradCAM": EigenGradCAM, +} + +# --- [保留] V3 的 Target --- +class ActivationMaximizationTarget: + def __init__(self, channel=0): + self.channel = channel + def __call__(self, model_output): + if model_output.ndim == 4: + return model_output[:, self.channel, :, :].mean() + elif model_output.ndim == 3: + return model_output[self.channel, :, :].mean() + else: + raise ValueError(f"Unsupported model_output shape: {model_output.shape}") + +# --- [保留] V3 的预处理 --- +def preprocess_image(bgr_img: np.ndarray, imgsz: int, device: str) -> (torch.Tensor, Dict[str, Any]): + # ... (此函数内容与上一版完全相同,为节省篇幅已折叠) ... + orig_h, orig_w = bgr_img.shape[:2] + rgb_img = bgr_img[:, :, ::-1] + scale = min(imgsz / orig_w, imgsz / orig_h) + new_w, new_h = int(orig_w * scale), int(orig_h * scale) + resized_image = cv2.resize(rgb_img, (new_w, new_h), interpolation=cv2.INTER_LINEAR) + pad_w, pad_h = imgsz - new_w, imgsz - new_h + pad_left, pad_right = pad_w // 2, pad_w - pad_w // 2 + pad_top, pad_bottom = pad_h // 2, pad_h - pad_h // 2 + padded_image = cv2.copyMakeBorder(resized_image, pad_top, pad_bottom, pad_left, pad_right, + cv2.BORDER_CONSTANT, value=(114, 114, 114)) + img_float = np.float32(padded_image) / 255.0 + input_tensor = torch.from_numpy(img_float).permute(2, 0, 1).unsqueeze(0).to(device) + padding_info = { + "pad_top": pad_top, "new_h": new_h, + "pad_left": pad_left, "new_w": new_w, + "orig_h": orig_h, "orig_w": orig_w + } + return input_tensor, padding_info + + +# --- [保留] V1/V3 的关键修复 --- +def reshape_transform(outputs): + if isinstance(outputs, tuple): + return outputs[0] + return outputs + +# --- [!! 核心功能 !!] (此函数本身无需修改) --- +def generate_heatmaps_for_all_layers(model_path: str, image_path: str, cam_method_name: str, model_name: str): + """ + 加载模型和图像,遍历所有层,并为每个成功的层生成热图。 + (此函数内容与上一版完全相同,为节省篇幅已折叠) + """ + + # --- 1. 获取 CAM 方法 --- + cam_class = CAM_METHODS.get(cam_method_name) + if not cam_class: + logging.error(f"无效的 CAM 方法: {cam_method_name}") + return + # --- 2. 定义 Targets --- + targets = None + if cam_method_name != "EigenCAM": + targets = [ActivationMaximizationTarget(channel=0)] + logging.info(f"已为 {cam_method_name} 设置 ActivationMaximizationTarget(channel=0)。") + else: + logging.info(f"将使用 {cam_method_name} (无需 Targets)。") + # --- 3. 加载模型 --- + try: + logging.info(f"正在加载模型: {model_path}") + device = 'cuda' if torch.cuda.is_available() else 'cpu' + model = YOLO(model_path).to(device) + model.model.eval() + imgsz = model.model.args['imgsz'] + logging.info(f"模型加载成功。设备: {device}, 图像尺寸: {imgsz}") + except Exception as e: + logging.error(f"无法加载模型 {model_path}: {e}", exc_info=True) + return + # --- 4. 加载和预处理图像 --- + try: + logging.info(f"正在加载测试图像: {image_path}") + orig_img_bgr = cv2.imread(image_path) + if orig_img_bgr is None: + logging.error(f"无法读取图像: {image_path}") + return + orig_img_rgb_float = np.float32(orig_img_bgr[:, :, ::-1]) / 255.0 + input_tensor, pad_info = preprocess_image(orig_img_bgr, imgsz, device) + input_tensor.requires_grad_() + logging.info(f"图像加载并预处理完成。Tensor shape: {input_tensor.shape}") + except Exception as e: + logging.error(f"处理图像时出错: {e}", exc_info=True) + return + # --- 5. 创建输出目录 (您的自定义逻辑) --- + image_stem = Path(image_path).stem + output_dir_name = f"./{cam_method_name}_{model_name}_{image_stem}_All_HeartMap" + output_dir = Path(output_dir_name) + output_dir.mkdir(parents=True, exist_ok=True) + logging.info(f"所有热图将保存到: {output_dir.resolve()}") + + # --- 6. 遍历并测试所有层 --- + base_model_layers = model.model.model + successful_layers, failed_layers = [], [] + logging.info(f"\n" + "="*50) + logging.info(f"开始遍历 {len(base_model_layers)} 个层 (使用 {cam_method_name})") + logging.info("\n" + "="*50 + "\n") + + for i, layer in enumerate(base_model_layers): + layer_name = f"model.model.model[{i}]" + layer_type = layer.__class__.__name__ + print(f"--- [ {i:02d} / {len(base_model_layers)-1} ] 尝试层: {layer_name:<25} (类型: {layer_type}) ---") + try: + with cam_class(model=model.model, target_layers=[layer], reshape_transform=reshape_transform) as cam: + grayscale_cam = cam(input_tensor=input_tensor, targets=targets) + if grayscale_cam is None: raise ValueError("CAM-Method 返回了 None") + + # ... (结果提取、裁剪、保存逻辑) ... + if isinstance(grayscale_cam, (list, tuple)): result = grayscale_cam[0] + else: result = grayscale_cam + if isinstance(result, torch.Tensor): result = result.detach().cpu().numpy() + if result.ndim == 4: result = result[0].mean(axis=0) + elif result.ndim == 3: result = result[0, :] + elif result.ndim != 2: raise TypeError(f"CAM 输出维度异常 {result.shape}") + + cam_cropped = result[pad_info['pad_top'] : pad_info['pad_top'] + pad_info['new_h'], + pad_info['pad_left'] : pad_info['pad_left'] + pad_info['new_w']] + cam_upscaled = cv2.resize(cam_cropped, (pad_info['orig_w'], pad_info['orig_h']), interpolation=cv2.INTER_LINEAR) + cam_normalized = (cam_upscaled - cam_upscaled.min()) / (cam_upscaled.max() + 1e-8) + + visualization = show_cam_on_image(orig_img_rgb_float, cam_normalized, use_rgb=True, image_weight=0.5) + viz_bgr = visualization[:, :, ::-1] + heatmap_uint8 = np.uint8(255 * cam_normalized) + heatmap_color = cv2.applyColorMap(heatmap_uint8, cv2.COLORMAP_JET) + + base_filename = f"Layer_{i:02d}_{layer_type}" + cv2.imwrite(str(output_dir / f"{base_filename}_overlay.jpg"), viz_bgr) + # cv2.imwrite(str(output_dir / f"{base_filename}_heatmap.jpg"), heatmap_color) + + print(f" [√] 成功! 已保存 {base_filename}_overlay.jpg 和 _heatmap.jpg") + successful_layers.append((i, layer_name, layer_type)) + + except Exception as e: + error_msg = str(e).split('\n')[0] + print(f" [X] 失败. 错误: {error_msg}") + failed_layers.append((i, layer_name, layer_type, error_msg)) + + # --- 7. 打印总结报告 --- + # ... (报告逻辑与上一版相同) ... + print("\n" + "="*60) + print(" 全层热图生成报告") + print("="*60) + print(f"模型: {Path(model_path).name} (来自: {Path(model_path).parent.parent})") + print(f"测试图像: {Path(image_path).name}") + print(f"测试方法: {cam_method_name}") + print(f"输出目录: {output_dir.resolve()}") + print(f"总计: {len(successful_layers)} 个成功 (已生成), {len(failed_layers)} 个失败/跳过") + print("\n" + "--- [√] 成功生成的层 ---") + if not successful_layers: print(" (无)") + else: + for i, name, type in successful_layers: print(f" - [{i:02d}] {name:<25} (类型: {type})") + print("\n" + "--- [X] 失败或跳过的层 ---") + if not failed_layers: print(" (无)") + else: + for i, name, type, error in failed_layers: + print(f" - [{i:02d}] {name:<25} (类型: {type})") + print(f" └> 失败原因: {error[:100]}...") + print("\n" + "="*60) + + +if __name__ == "__main__": + + # --- [!! 重构 !!] (以下逻辑来自 yolo_predict_visualize_nn_V1.py) --- + + config.show_config_summary() + + parser = argparse.ArgumentParser(description="全层热图生成器 (V1 模型选择模式)") + + # 1. (来自 V1) --model 参数 + parser.add_argument( + "--model", + type=str, + required=True, + choices=list(config.MODEL_CONFIGS.keys()), + help="选择一个基础模型类型来筛选其训练历史。" + ) + # 2. (来自 V1) --pt_name 参数 + parser.add_argument( + "--pt_name", + type=str, + default="best.pt", + help="要使用的权重文件名 (例如 'best.pt' 或 'epoch100.pt')。" + ) + # 3. (来自您) --source 参数 + parser.add_argument( + "--source", + type=str, + default="random", + help="测试图像源: 'random' (从 config.TEST_IMAGE_DIR 随机选), " + "'path/to/image.jpg' (指定文件), " + "或 'path/to/dir/' (从该目录随机选)" + ) + # 4. (来自您) --cam_method 参数 + parser.add_argument( + "--cam_method", + type=str, + default="GradCAM", + choices=list(CAM_METHODS.keys()), + help=f"选择要使用的 CAM 可视化方法。默认为: GradCAM。" + ) + args = parser.parse_args() + + # --- (来自 V1) 模型搜索和选择逻辑 --- + available_runs = find_trained_models(config.PREDICT_BEST_MODEL_DIR, args.model, args.pt_name) + + run_to_use = None + + if not available_runs: + logging.error(f"错误:在 {config.PREDICT_BEST_MODEL_DIR} 中未找到模型 '{args.model}' (使用 '{args.pt_name}') 的有效训练记录。") + sys.exit(1) + + elif len(available_runs) == 1: + run_to_use = available_runs[0] + logging.info(f"只找到一个训练版本,已自动选择: {run_to_use}") + + else: + print(f"\n为模型 '{args.model}' (使用 '{args.pt_name}') 找到多个训练版本:") + for i, run_name in enumerate(available_runs, 1): + print(f" [{i}] {run_name}") + + while True: + try: + choice = input(f"请输入您想使用的版本序号 (1-{len(available_runs)}): ") + choice_index = int(choice) + if 1 <= choice_index <= len(available_runs): + run_to_use = available_runs[choice_index - 1] + break + else: + print("错误:输入无效,请输入列表中的序号。") + except ValueError: + print("错误:请输入一个数字。") + except (KeyboardInterrupt, EOFError): + print("\n操作已取消。") + sys.exit(0) + + # --- (!! 融合 !!) 执行逻辑 --- + if run_to_use: + # 1. (来自 V1) 确定最终模型路径 + model_to_use_path = config.PREDICT_BEST_MODEL_DIR / run_to_use / 'weights' / args.pt_name + + if not model_to_use_path.exists(): + logging.error(f"严重错误:找不到权重文件 {model_to_use_path}。") + sys.exit(1) + + # 2. (来自您) 确定最终图像路径 + source_input = args.source + image_to_test = None + source_dir_for_random = None + + if source_input.lower() == "random": + source_dir_for_random = config.TEST_IMAGE_DIR + logging.info(f"Source='random'。将从 config.TEST_IMAGE_DIR 随机选择图像: {source_dir_for_random}") + elif Path(source_input).is_file(): + image_to_test = source_input + logging.info(f"Source 是一个特定文件: {image_to_test}") + elif Path(source_input).is_dir(): + source_dir_for_random = Path(source_input) + logging.info(f"Source 是一个目录。将从该目录随机选择图像: {source_dir_for_random}") + else: + logging.error(f"错误: 指定的源路径无效: {source_input}") + sys.exit(1) + + # 如果需要随机选择... + if image_to_test is None: + if not source_dir_for_random or not source_dir_for_random.is_dir(): + logging.error(f"错误: 用于随机选择的目录无效: {source_dir_for_random}") + sys.exit(1) + + image_paths = sorted( + list(source_dir_for_random.glob("*.jpg")) + + list(source_dir_for_random.glob("*.jpeg")) + + list(source_dir_for_random.glob("*.png")) + ) + if not image_paths: + logging.error(f"错误: 在目录 {source_dir_for_random} 中未找到任何图像 (.jpg, .jpeg, .png)。") + sys.exit(1) + + image_to_test = str(random.choice(image_paths)) + logging.info(f"已随机选择图像: {Path(image_to_test).name}") + + # 3. (融合) 调用核心函数 + if image_to_test: + generate_heatmaps_for_all_layers( + model_path=str(model_to_use_path), # <-- 使用 V1 找到的路径 + image_path=image_to_test, # <-- 使用您的逻辑找到的路径 + cam_method_name=args.cam_method, # <-- 使用您的参数 + model_name = args.model + ) \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/0_1_check_picture_pair.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/0_1_check_picture_pair.py new file mode 100644 index 0000000..f086857 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/0_1_check_picture_pair.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import argparse +import sys +from pathlib import Path +from typing import Set, Dict, Tuple + +# <--- 新增: 导入 Pillow 库用于图像处理 --- +try: + from PIL import Image + # 禁用解压缩炸弹检查,以防万一标签图非常大且简单 + Image.MAX_IMAGE_PIXELS = None +except ImportError: + print("错误: 本脚本需要 'Pillow' 库来检查图像尺寸。", file=sys.stderr) + print("请运行: pip install Pillow", file=sys.stderr) + sys.exit(1) +# --- 新增结束 --- + + +# 定义支持的文件扩展名 +VALID_EXTENSIONS = {'.png', '.jpg', '.jpeg'} + +def process_directory(path: Path, + prefix: str = "", + suffix: str = "") -> Tuple[Set[str], Dict[str, Path], Dict[Tuple[int, int], int]]: # <--- 修改:更新了返回类型 + """ + 处理单个目录,提取所有有效文件的文件名(stem),并根据前缀和后缀进行规范化。 + 同时统计所有图像的尺寸。 + + 返回: + normalized_stems (Set[str]): 规范化处理后的文件名集合。 + normalized_to_full_path (Dict[str, Path]): 规范化文件名 -> 原始完整路径 的映射。 + size_counts (Dict[Tuple[int, int], int]): (宽度, 高度) -> 数量 的映射。 # <--- 新增 + """ + if not path.is_dir(): + print(f"错误: 路径 '{path}' 不是一个有效的目录。", file=sys.stderr) + return set(), {}, {} # <--- 修改 + + normalized_stems: Set[str] = set() + normalized_to_full_path: Dict[str, Path] = {} + size_counts: Dict[Tuple[int, int], int] = {} # <--- 新增:用于统计尺寸 + + for file_path in path.glob('*'): + # 确保是文件,并且扩展名在我们的有效列表中 + if file_path.is_file() and file_path.suffix.lower() in VALID_EXTENSIONS: + + # <--- 新增: 尝试获取图像尺寸 --- + try: + # 使用 with...as... 确保文件被正确关闭 + with Image.open(file_path) as img: + size: Tuple[int, int] = img.size + size_counts[size] = size_counts.get(size, 0) + 1 + except Exception as e: + print(f"警告: 无法读取图像 '{file_path.name}' 的尺寸. 错误: {e}", file=sys.stderr) + # --- 尺寸获取结束 --- + + original_stem = file_path.stem + normalized_stem = original_stem + + # 仅当提供了前缀/后缀时才进行处理 + if prefix and normalized_stem.startswith(prefix): + normalized_stem = normalized_stem[len(prefix):] + + if suffix and normalized_stem.endswith(suffix): + normalized_stem = normalized_stem[:-len(suffix)] + + # 检查处理后是否重名,如果重名则发出警告 + if normalized_stem in normalized_to_full_path: + print(f"警告: 规范化后文件名发生冲突。") + print(f" '{file_path.name}' 和 '{normalized_to_full_path[normalized_stem].name}' 都变成了 '{normalized_stem}'") + + normalized_stems.add(normalized_stem) + normalized_to_full_path[normalized_stem] = file_path + + return normalized_stems, normalized_to_full_path, size_counts # <--- 修改 + + +# <--- 新增: 打印尺寸统计的辅助函数 --- +def print_size_report(title: str, size_counts: Dict[Tuple[int, int], int]): + """ + 格式化并打印尺寸统计报告。 + """ + print(f"\n{title}") + if not size_counts: + print(" (未找到或无法读取任何图像文件)") + return + + total_files = sum(size_counts.values()) + print(f" 总共 {total_files} 个文件,分布如下:") + + # 按尺寸 (宽, 高) 排序输出 + for size, count in sorted(size_counts.items()): + width, height = size + print(f" - 尺寸 (宽, 高) {width}x{height}: {count} 个文件") +# --- 新增结束 --- + + +def main(): + parser = argparse.ArgumentParser(description="比较两个文件夹中的文件名是否匹配,并可选择删除不匹配项。") + parser.add_argument("-i", "--image", + type=Path, + default="./ORI", + help="Image 文件夹路径") + parser.add_argument("-l", "--label", + type=Path, + default="./Label", + help="Label 文件夹路径") + parser.add_argument("-p", "--prefix", + type=str, + default="", + help="在 Label 文件名中要忽略的前缀") + parser.add_argument("-s", "--suffix", + type=str, + default="", + help="在 Label 文件名中要忽略的后缀") + parser.add_argument("-y", "--yes", + action="store_true", + help="自动确认删除所有不匹配的文件,跳过交互式提示。") + + args = parser.parse_args() + + # 1. 处理 Image 文件夹 + print(f"--- 正在处理 Image 文件夹: {args.image} ---") + image_stems, image_norm_to_path, image_size_counts = process_directory(args.image) # <--- 修改 + if not image_stems: + print(f"未在 Image 文件夹中找到任何 .png 或 .jpg 文件。") + # <--- 新增: 打印 Image 尺寸报告 --- + print_size_report("--- Image 文件夹尺寸统计 ---", image_size_counts) + + # 2. 处理 Label 文件夹 + print(f"\n--- 正在处理 Label 文件夹: {args.label} ---") + print(f"(忽略前缀: '{args.prefix}', 忽略后缀: '{args.suffix}')") + label_stems_normalized, label_norm_to_path, label_size_counts = process_directory( # <--- 修改 + args.label, + args.prefix, + args.suffix + ) + if not label_stems_normalized: + print(f"未在 Label 文件夹中找到任何 .png 或 .jpg 文件。") + # <--- 新增: 打印 Label 尺寸报告 --- + print_size_report("--- Label 文件夹尺寸统计 ---", label_size_counts) + + + # 3. 执行比较 (使用集合运算) + matching_stems = image_stems.intersection(label_stems_normalized) + extra_in_image = image_stems.difference(label_stems_normalized) + extra_in_label_normalized = label_stems_normalized.difference(image_stems) + extra_in_label_original_stems = {label_norm_to_path[stem].stem for stem in extra_in_label_normalized} + + # 4. 输出结果 + print("\n" + "="*30) + print(" 匹配结果报告") + print("="*30) + + print(f"\n匹配的文件总数: {len(matching_stems)}") + + # <--- 新增: 检查匹配项的尺寸是否一致 --- + print("\n--- 正在检查匹配文件的尺寸 ---") + mismatched_size_pairs = [] + if not matching_stems: + print("(无匹配文件,跳过尺寸检查)") + else: + print(f"正在检查 {len(matching_stems)} 对文件...") + for stem in sorted(matching_stems): + try: + img_path = image_norm_to_path[stem] + lbl_path = label_norm_to_path[stem] + + # 再次打开以比较尺寸 + with Image.open(img_path) as img: + img_size = img.size + with Image.open(lbl_path) as lbl: + lbl_size = lbl.size + + if img_size != lbl_size: + mismatched_size_pairs.append((img_path, img_size, lbl_path, lbl_size)) + + except Exception as e: + print(f" 错误: 无法比较 '{stem}' 的尺寸. 错误: {e}", file=sys.stderr) + + if not mismatched_size_pairs: + print(f"√ 成功: 所有 {len(matching_stems)} 对匹配文件均具有相同的尺寸。") + else: + print(f"\n!!! 警告: 发现 {len(mismatched_size_pairs)} 对文件尺寸不匹配:") + for img_path, img_size, lbl_path, lbl_size in mismatched_size_pairs: + print(f" - [Image] {img_path.name} {img_size} != [Label] {lbl_path.name} {lbl_size}") + # --- 尺寸检查结束 --- + + print("\n--- Image 文件夹中多余的文件 (共 {} 个) ---".format(len(extra_in_image))) + if not extra_in_image: + print("(无)") + else: + for file_stem in sorted(extra_in_image): + print(file_stem) + + print("\n--- Label 文件夹中多余的文件 (共 {} 个) ---".format(len(extra_in_label_original_stems))) + print("(显示的是原始文件名,非规范化名称)") + if not extra_in_label_original_stems: + print("(无)") + else: + for file_stem in sorted(extra_in_label_original_stems): + print(file_stem) + + print("\n" + "="*30) + + # 5. 删除交互部分 (无修改) + if not extra_in_image and not extra_in_label_normalized: + print("\n所有文件均完美匹配,无需删除。") + # <--- 新增: 如果文件名匹配,但尺寸不匹配,也在这里提醒一下 --- + if mismatched_size_pairs: + print("注意:虽然文件名匹配,但有 {} 对文件的尺寸不一致,请检查上面的报告。".format(len(mismatched_size_pairs))) + return + + # ... (后续的删除逻辑保持不变) ... + confirm_delete = False + if args.yes: + print("\n检测到 -y/--yes 参数,将自动删除不匹配的文件。") + confirm_delete = True + else: + try: + choice = input("\n警告: 是否要永久删除所有上述 '多余' 的文件? (y/n): ").strip().lower() + if choice in ['y', 'yes']: + confirm_delete = True + except (KeyboardInterrupt, EOFError): + print("\n操作被用户中断。") + confirm_delete = False + + if confirm_delete: + print("\n--- 正在删除 Image 文件夹中的多余文件 ---") + deleted_count = 0 + for stem in sorted(extra_in_image): + file_path = image_norm_to_path[stem] + try: + file_path.unlink() + print(f" 已删除: {file_path.name}") + deleted_count += 1 + except OSError as e: + print(f" 删除失败: {file_path.name} (错误: {e})", file=sys.stderr) + print(f"--- Image 文件夹共删除 {deleted_count} 个文件 ---") + + print("\n--- 正在删除 Label 文件夹中的多余文件 ---") + deleted_count = 0 + for norm_stem in sorted(extra_in_label_normalized): + file_path = label_norm_to_path[norm_stem] + try: + file_path.unlink() + print(f" 已删除: {file_path.name} (原始文件名)") + deleted_count += 1 + except OSError as e: + print(f" 删除失败: {file_path.name} (错误: {e})", file=sys.stderr) + print(f"--- Label 文件夹共删除 {deleted_count} 个文件 ---") + + print("\n删除操作完成。") + + else: + print("\n操作已取消。未删除任何文件。") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_TOOL_stack_pics.sh b/Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_TOOL_stack_pics.sh new file mode 100644 index 0000000..2767cd9 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_TOOL_stack_pics.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 -i -l -r [ -a -p -s -h]" + echo "对image图片和label图片进行匹配(-i、-l -r均不能为空)(-p -s默认为空"" -a默认为\"0.3\") " + echo "-i:原始image的路径,-l:原始label的路径,-p:前缀内容,-s:后缀内容(不用管文件后缀名),-h:帮助" + echo "e.g. bash 0_2_TOOL_stack_pics.sh -i ./ori -l ./label -r ./result_0.3透明度 -a 0.3 -p Prefix -s _label" +} + +ori_image_directorys="" +ori_label_directorys="" +stack_result_directorys="" +prefix="" +suffix="" +alpha="0.3" + +while getopts "hl:i:r:p:s:a:" opt; do + case $opt in + h) + usage + exit 0 + ;; + i) + ori_image_directorys=$OPTARG + ;; + l) + ori_label_directorys=$OPTARG + ;; + p) + prefix=$OPTARG + ;; + s) + suffix=$OPTARG + ;; + r) + stack_result_directorys=$OPTARG + ;; + a) + alpha=$OPTARG + ;; + *) + echo -e '\033[31m!!! Error, Illegal input !!!\033[0m' + usage + exit 1 + ;; + esac +done + +# 判断输入地址是否为空 +if [ -z "$ori_label_directorys" ] || [ -z "$ori_image_directorys" ] || [ -z "$stack_result_directorys" ]; then + echo -e "\033[31m输入地址 -i -l -z 存在空地址\033[0m" + usage + exit 1 +fi + +# 地址转化 +ori_image_directory=$(readlink -f "$ori_image_directorys") +ori_label_directory=$(readlink -f "$ori_label_directorys") +stack_result_directory=$(readlink -f "$stack_result_directorys") +if [ -z "$ori_label_directory" ] || [ -z "$ori_image_directory" ]|| [ -z "$stack_result_directory" ]; then + echo "image、label、result存在无法解析地址,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + echo -e "\033[31mori_label_directory\033[0m: $stack_result_directorys" + exit 1 +fi +if [ ! -d "$ori_label_directory" ] || [ ! -d "$ori_image_directory" ]; then + echo "image、label两目录有一个不存在,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directory" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi + +# 获取当前脚本的路径和名称 +script_path=$(dirname "$0") +# 将当前目录更改为脚本所在的路径 +cd "$script_path" + +# 激活conda环境 +source /home/"$USER"/miniconda/bin/activate Deal_Data + +echo -e "\033[32m_____ 0_2_TOOL_stack_pics.sh _____\033[0m" +echo -e "\033[33mimage所在文件夹为$ori_image_directory\nlable所在文件夹为$ori_label_directory\033[0m" +# 遍历label目录 +for file_path in "$ori_label_directory"/*; do + # 判断是否是文件 + if [[ -f "$file_path" ]]; then + file_name=$(basename "$file_path") + # 判断文件名是否符合规范 + if [[ "$file_name" =~ .*\.(jpg|png|bmp|JPG|PNG|BMP) ]]; then # 判断是否有为图片 + # if [[ "$file_name" =~ "$prefix".*"$suffix".*\.(jpg|png|bmp|JPG|PNG|BMP)$ ]]; then # 判断是否有满足要求的文件名 + # 抽取文件名(有前缀、后缀的抽取前缀、后缀里面的,没有的返回整个) + if [ -z $prefix ];then + file_name_extract=$(echo $file_name | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_extract=$(echo $file_name | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + # 从label目录中看是否有此文件 + file_name_other=$(ls $ori_image_directory | grep "^${file_name_extract}\.") + file_name_other=$(echo "$(echo "$file_name_other" | sed '/^$/d')" | head -n1) # 提取出文件名 + # 如果另一个目录没有此文件的话 + if [ -z "$file_name_other" ]; then + echo "$file_name label中对应内容未在$ori_image_directory搜索到" + # 建立相关存储文件夹 + if [ ! -d "$ori_label_directory/Not_pair_pics" ]; then + mkdir -p "$ori_label_directory/Not_pair_pics" # 建立存储文件夹 + fi + # 移动相关文件 + cp "$ori_label_directory/$file_name" "$ori_label_directory/Not_pair_pics" + echo "$file_name" >> "$ori_label_directory/Not_pair_pics/not_pair.txt" + else # 如果另一个目录有此配对文件的话,则运行相关程序 + echo "image中的$file_name_other,与lable中的$file_name" + mkdir -p "$stack_result_directory" + python 0_2_stack_picture.py "$ori_image_directory/$file_name_other" "$ori_label_directory/$file_name" "$stack_result_directory" "$alpha" + echo "" + fi + + fi + else + echo "$file_path不是文件" + fi +done \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_stack_picture.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_stack_picture.py new file mode 100644 index 0000000..7fcf130 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/0_2_stack_picture.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import cv2, os, sys + +def Stack_pic(Background_path, Overlay_path, Result_dir, alpha=0.3): + # 读取两张没有alpha通道的图片 + img1 = cv2.imread(Background_path) # 底层图片 + img2 = cv2.imread(Overlay_path) # 顶层图片 + + Result_name = os.path.splitext(os.path.basename(Background_path))[0] + + # 将img2调整为与img1大小相同 + img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0])) + + # 将img2的透明度调整为20% + overlay_alpha = alpha + + # 将img2叠加到img1上 + overlay = cv2.addWeighted(img1, 1 - overlay_alpha, img2, overlay_alpha, 0) + + # 保存结果 + if not os.path.exists(Result_dir): + os.makedirs(Result_dir) + cv2.imwrite(os.path.join(Result_dir, Result_name+'.png'), overlay) + print("堆叠图片写入地址:", os.path.join(Result_dir, Result_name+'.png')) + + +if __name__ == '__main__': + Background_path = sys.argv[1] # 背景所在路径 + Overlay_path = sys.argv[2] # 上层图片所在路径 + Result_dir = sys.argv[3] # 结果所在目录 + # 透明度,默认为0.3 + try: + alpha = float(sys.argv[4]) + if(alpha > 1 or alpha < 0): + print("alpha 透明度输入不正确,其值应该在0~1之间") + alpha = 0.3 + except: + alpha = 0.3 + # 进行对叠程序 + Stack_pic(Background_path, Overlay_path, Result_dir, alpha) \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/1_deal_labels.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/1_deal_labels.py new file mode 100644 index 0000000..0592d50 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/1_deal_labels.py @@ -0,0 +1,442 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -* +import os,time,sys,threading, colorsys, argparse +import asyncio, cv2, multiprocessing, random +from PIL import Image +import numpy as np +from Tool_deal_labels import edge_detection, detect_connected_regions, Tool_color_connected_array, fill_white_regions, color_connected_regions +from Tool_Classes_And_Palette import Annotate_CLASSES, Annotate_PALETTE, bg_PALETTE + +def getFileList(dir,Filelist=[], ext=None, Max_layer=1, layer=0, Donot_Search=['1_边缘检测并膨胀', '2_连通区域检测', '3_分水岭算法填充']): + """ + 获取文件夹及其子文件夹中文件列表 + 输入 dir:文件夹根目录 + 输入 ext: 扩展名 + 返回: 文件路径列表 + """ + newDir = dir + if os.path.isfile(dir): + if ext is None: + Filelist.append(dir) + else: + if ext in dir[-3:]: + Filelist.append(dir) + + elif os.path.isdir(dir): + file_name = os.path.basename(dir) + # 判断是否在禁搜名单中 + if file_name in Donot_Search: + return Filelist + for s in os.listdir(dir): + newDir=os.path.join(dir,s) + if layer <= Max_layer: + getFileList(newDir, Filelist, ext, Max_layer, layer+1) + + return Filelist + +class Deal_image(): + def __init__(self, Annotate_CLASSES = ('肝脏','胆囊'), Annotate_PALETTE = [[255,91,0],[255,234,0]], src_label_fold = "./Label", save_pro_label_fold = "./LABEL_PNG_new", save_GT_label_fold = "./Label_Generate", GT_channel = 1, pro_append_name="_label", GT_append_name="", ori_img_folder="./ORI_PNG", res_label_folder="./Result_label", save_merge_pic_folder="./Result_merge", back_gnd_color=0, first_class_color=1, pic_type="png", Max_width = 10000, Label_Max_Search_layer=1000, save_process_pics=False, bg_PALETTE = [0,0,0]): + # 背景最好放在最后 + # self.src_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','背景') + # self.src_PALETTE = np.array([[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66],[0,0,0]]) + # self.src_CLASSES_NUM = np.shape(self.src_CLASSES)[0] + self.bg_PALETTE = bg_PALETTE # 背景颜色 TODO + + self.Annotate_CLASSES = Annotate_CLASSES # 待分类的类 + self.Annotate_PALETTE = np.array(Annotate_PALETTE) # 每一类的像素直 + self.Annotate_CLASSES_NUM = np.shape(Annotate_CLASSES)[0] # 类数量 + + self.save_process_pics = save_process_pics # 保存中间过程图片 + + self.src_label_fold = src_label_fold # 原始标签图片 保存位置 + self.save_pro_label_fold = save_pro_label_fold # 优化后标签图片 保存位置 + self.save_GT_label_fold = save_GT_label_fold # GT标签图片 保存位置 + + self.ori_img_folder = ori_img_folder # 最原始手术图片 保存位置 + self.res_label_folder = res_label_folder # 训练出来的label 保存位置 + self.save_merge_pic_folder = save_merge_pic_folder # 融合图像保存位置 + + self.pro_append_name = pro_append_name # 优化后标签图片后缀 + self.GT_append_name = GT_append_name # GT标签图片后缀 + self.GT_channel = GT_channel # GT标签图片通道数 + + self.Max_width = Max_width # 最大图片宽度(匹配时候用) + self.pic_type = pic_type # 图片类型 + self.back_gnd_color = back_gnd_color # 背景颜色 + self.first_class_color = first_class_color # 第一类上的颜色 + self.Label_Max_Search_layer=Label_Max_Search_layer # 文件夹最大搜索深度 + try: + self.labellist_src = getFileList(src_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到ori_label图片 '+str(len(self.labellist_src))+' 张图像') + except: + self.labellist_src = None + print("没有ori_label相关文件") + + try: + # print(save_pro_label_fold) + self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像') + except: + self.labellist_pro = None + print("没有pro_label相关文件") + + try: + self.imglist_src = getFileList(ori_img_folder, [], pic_type, self.Label_Max_Search_layer) + self.reslist_src = getFileList(res_label_folder, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到ori原始图片 '+str(len(self.imglist_src))+' 张图像') + print('本次执行检索到训练train_result图片 '+str(len(self.reslist_src))+' 张图像') + except: + self.imglist_src = None + self.reslist_src = None + print("没有train_result和原始图片相关文件") + + # 获取单张图片各个通路信息 + def get_single_pic_rgb(self, imgpath): + print(imgpath) + image = Image.open(imgpath).convert('RGB') # 转为RGB图片 + # 将 RGB 色值分离 + image.load() + r, g, b = image.split() + r = np.array(r) + g = np.array(g) + b = np.array(b) + return image, r, g, b + + # 将单个pro图片变成GT图片 + def Conver_pro_label_pic_2_GT_pic(self, imgpath, imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + image, r,g,b = self.get_single_pic_rgb(imgpath) + + result_gt = np.ones(np.shape(image))*self.back_gnd_color # 初始化填充内容为back_gnd_color + gt_number = self.first_class_color # 第一类上色颜色确定 + + # PALETTE中排除掉 '背景' [0,0,0] + PALETTE_No_Bg = self.Annotate_PALETTE[~np.all(self.Annotate_PALETTE == self.bg_PALETTE, axis=1)] + + # 遍历所有待识别颜色 + for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in PALETTE_No_Bg: + # 查找三原色匹配位置 + locate_r = np.where( r == Annotate_PALETTE_r ) + locate_g = np.where( g == Annotate_PALETTE_g ) + locate_b = np.where( b == Annotate_PALETTE_b ) + + # 查找都匹配位置(交集) + # 将矩阵换一种表示形式 + locate_r = np.array(locate_r[0]) * self.Max_width + np.array(locate_r[1]) + locate_g = np.array(locate_g[0]) * self.Max_width + np.array(locate_g[1]) + locate_b = np.array(locate_b[0]) * self.Max_width + np.array(locate_b[1]) + + # 用自带函数寻找匹配项 + matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b) + matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0) + result_gt[matched[0],matched[1], :] = gt_number + gt_number = gt_number + 1 + + # 输出GT图片 + if(int(self.GT_channel) == 1): + result_gt = result_gt[:,:,0] + elif(int(self.GT_channel) == 3): + result_gt = cv2.cvtColor(np.float32(result_gt), cv2.COLOR_RGB2BGR) # rgb颜色互换 + else: + print("GT_channel 必须为1或3") + quit + try: # 新建文件夹 + os.mkdir(self.save_GT_label_fold) + except: + print("已有"+self.save_GT_label_fold) + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_GT_label_fold, os.path.basename(imgname).rpartition('.')[0]+self.GT_append_name+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_GT_label_fold, os.path.basename(imgname)+self.GT_append_name+'.'+self.pic_type) + cv2.imwrite(save_dir, result_gt) + print("GT图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将处理好的图片转化为GT图片 + def Conver_pro_label_pic_2_GT_pic_all(self): + print("\033[33m**** 进行转换将Pro_label_pic转换为GT_label_pic ****\033[0m") + print("\033[33mPro_label_pic存储位置为:\033[0m", self.save_pro_label_fold) + print("\033[33mGT_label_pic生成位置为:\033[0m", self.save_GT_label_fold) + try: + # print(save_pro_label_fold) + self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像') + except: + self.labellist_pro = None + print("没有pro_label相关文件") + try: + os.mkdir(self.save_GT_label_fold) # 新建存储文件夹 + except: + print("已有"+self.save_GT_label_fold) + + # 指定最大进程数为 3 + max_processes = 20 + # 创建Pool对象 + pool = multiprocessing.Pool(processes=max_processes) + # 创建并启动进程 + args_list1 = [] + args_list2 = [] + + # 遍历整个文件夹 + for imgpath in self.labellist_pro: + imgname = os.path.basename(imgpath).rpartition('.')[0].replace(self.pro_append_name,"") + args_list1.append(imgpath) + args_list2.append(imgname) + args_list = zip(args_list1, args_list2) + # 使用进程池并行执行任务 + pool.starmap(self.Conver_pro_label_pic_2_GT_pic, args_list) + # 关闭进程池 + pool.close() + pool.join() + + def Conver_ori_label_pic_2_pro_pic(self, imgpath, imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + image = cv2.imread(imgpath) + + # 1. 边缘检测并膨胀 + dilated_image = edge_detection(image) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_Edge'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀', os.path.basename(imgname)+self.pro_append_name+'_Edge'+'.'+self.pic_type) + cv2.imwrite(save_dir, dilated_image) + print("中间态-边缘检测并膨胀 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 2. 检测连通区域 + filtered_labeled_array, _ = detect_connected_regions(dilated_image) + colored_image_filtered = Tool_color_connected_array(filtered_labeled_array) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '2_连通区域检测', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_Region'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '2_连通区域检测', os.path.basename(imgname)+self.pro_append_name+'_Region'+'.'+self.pic_type) + cv2.imwrite(save_dir, colored_image_filtered) + print("中间态-连通区域检测 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 3. 分水岭填充白色区域 + filled_labeled_array = fill_white_regions(filtered_labeled_array) + colored_image_filled = Tool_color_connected_array(filled_labeled_array) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '3_分水岭算法填充', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_FillEdge'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '3_分水岭算法填充', os.path.basename(imgname)+self.pro_append_name+'_FillEdge'+'.'+self.pic_type) + cv2.imwrite(save_dir, colored_image_filled) + print("中间态-分水岭算法填充 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 4. 对连通区域最终上色 + ori_labeled_image = image + result_pro = color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, self.Annotate_PALETTE) + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, os.path.basename(imgname)+self.pro_append_name+'.'+self.pic_type) + print("Pro图片已保存", save_dir) + cv2.imwrite(save_dir, result_pro) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将原始src图片转化为处理好的pro图片 + def Conver_ori_label_pic_2_pro_pic_all(self): + print("\033[33m**** 进行转换将Ori_label_pic转换为Pro_label_pic ****\033[0m") + print("\033[33mOri_label_pic存储位置为:\033[0m", self.src_label_fold) + print("\033[33mPro_label_pic生成位置为:\033[0m", self.save_pro_label_fold) + # 输出颜色预处理图片 + try: + os.mkdir(self.save_pro_label_fold) # 新建存储文件夹 + except: + print("已有"+self.save_pro_label_fold) + if(self.save_process_pics == True): + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀')) # 新建存储1_边缘检测并膨胀文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀')) + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '2_连通区域检测')) # 新建存储2_连通区域检测文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '2_连通区域检测')) + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '3_分水岭算法填充')) # 新建存储1_边缘检测并膨胀文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '3_分水岭算法填充')) + + # 指定最大进程数为 20,多参数函数并行 + max_processes = 20 + # 创建Pool对象 + pool = multiprocessing.Pool(processes=max_processes) + # 创建并启动进程 + args_list1 = [] + args_list2 = [] + + # 遍历整个文件夹 + for imgpath in self.labellist_src: + if imgpath.lower().endswith(('.jpg', '.png')): + imgname= os.path.basename(imgpath).rpartition('.')[0].replace(self.pro_append_name,"") + else: + imgname= os.path.basename(imgpath).replace(self.pro_append_name,"") + try: + print("Processing: ", imgname, "...") + # self.Conver_ori_label_pic_2_pro_pic(imgpath, imgname)s + # args_list.append({'imgpath': imgpath, 'imgname': imgname}) + args_list1.append(imgpath) + args_list2.append(imgname) + except: + os.system("echo "+imgname+" >> error_1.txt") + args_list = zip(args_list1, args_list2) + # 使用进程池并行执行任务 + pool.starmap(self.Conver_ori_label_pic_2_pro_pic, args_list) # 使用starmap进行多参数并行 + # 关闭进程池 + pool.close() + pool.join() + + # 图片堆叠 + def Merge_ori_pic_and_label_pic(self, res_img_path, res_imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + ori_img_path = os.path.join(self.ori_img_folder, res_imgname+'.'+self.pic_type) + if not os.path.exists(ori_img_path): + print("****照片不存在:****", ori_img_path) + return -1 + ori_image, ori_r, ori_g, ori_b = self.get_single_pic_rgb(ori_img_path) + res_image, res_r, res_g, res_b = self.get_single_pic_rgb(res_img_path) + + merge_img = np.array(ori_image) # merge图片初始化,默认图片背景为0.0.0 + + # 遍历所有待识别颜色 + for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in self.Annotate_PALETTE: + # 查找三原色匹配位置 + locate_r = np.where( res_r == Annotate_PALETTE_r ) + locate_g = np.where( res_g == Annotate_PALETTE_g ) + locate_b = np.where( res_b == Annotate_PALETTE_b ) + + # 查找都匹配位置(交集) + # 将矩阵换一种表示形式 + locate_r = np.array(locate_r[0]) * self.Max_width + np.array(locate_r[1]) + locate_g = np.array(locate_g[0]) * self.Max_width + np.array(locate_g[1]) + locate_b = np.array(locate_b[0]) * self.Max_width + np.array(locate_b[1]) + + # 用自带函数寻找匹配项 + matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b) + matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0) + merge_img[matched[0],matched[1], 0] = Annotate_PALETTE_r + merge_img[matched[0],matched[1], 1] = Annotate_PALETTE_g + merge_img[matched[0],matched[1], 2] = Annotate_PALETTE_b + + # 转成cv2形式 + merge_img = cv2.cvtColor(np.float32(merge_img), cv2.COLOR_RGB2BGR) + + try: # 新建文件夹 + os.mkdir(self.save_merge_pic_folder) + except: + print("已有"+self.save_merge_pic_folder) + if res_imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_merge_pic_folder, os.path.basename(res_imgname).rpartition('.')[0]+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_merge_pic_folder, os.path.basename(res_imgname)+'.'+self.pic_type) + + + cv2.imwrite(save_dir, merge_img) + print("Merge图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将label图片与原图片重合 + def Merge_ori_pic_and_label_pic_all(self): + # 遍历整个文件夹 + for res_img_path in self.reslist_src: + if res_img_path.lower().endswith(('.jpg', '.png')): + res_imgname = os.path.basename(res_img_path).rpartition('.')[0].replace(self.pro_append_name,"") + else: + res_imgname = os.path.basename(res_img_path).replace(self.pro_append_name,"") + print("Processing: ", res_imgname, "...") + self.Merge_ori_pic_and_label_pic(res_img_path, res_imgname) + +if __name__ == "__main__": + + # 创建参数解析器 + parser = argparse.ArgumentParser(description='Process some files.') + # 添加参数选项 + parser.add_argument('-src_fold', dest='src_label_fold', default='./', help='source label folder') + parser.add_argument('-save_pro_fold', dest='save_pro_label_fold', default='./ORI_pro_label_fold', help='processed label folder') + parser.add_argument('-save_GT_fold', dest='save_GT_label_fold', default='./ORI_GT_label_fold', help='ground truth folder') + parser.add_argument('-fold_search_depth', dest='Label_Max_Search_layer', default='1000', type=int, help='Folder Search Depth') + parser.add_argument('-pro_suffix_name', dest='pro_append_name', default='_label', help='Pro file suffix') + parser.add_argument('-GT_suffix_name', dest='GT_append_name', default='', help='GT file suffix') + parser.add_argument('-GT_channel', dest='GT_channel', default='1', type=int, help='GT file channel(1 or 3)') + parser.add_argument('-back_gnd_color', dest='back_gnd_color', default='0', type=int, help='Color of "Back ground"(0 or 255)') + parser.add_argument('-first_class_color', dest='first_class_color', default='1', type=int, help='Color of "First Class"') + parser.add_argument('-pic_type', dest='pic_type', default='png', help='type of picture(Do not add ".")') + parser.add_argument('-Max_width', dest='Max_width', default='10000', type=int, help='Max width of picture') + parser.add_argument('-Rebuild_from', dest='Rebuild_from', default='label', help='Source to Rebuild Labels(label/pro)') + parser.add_argument('-Rebuild_to', dest='Rebuild_to', default='GT', help='Destination of Rebuild Labels(pro/GT)') + parser.add_argument('-save_process_pics', dest='save_process_pics', default='False', help='Save the processed pics(e.g.Gray_pics,Color_pics) in generating pro_pics') + + # 解析命令行参数 + args = parser.parse_args() + + src_label_fold = args.src_label_fold + save_pro_label_fold = args.save_pro_label_fold + save_GT_label_fold = args.save_GT_label_fold + Label_Max_Search_layer = args.Label_Max_Search_layer + pro_append_name = args.pro_append_name + GT_append_name = args.GT_append_name + GT_channel = args.GT_channel + back_gnd_color = args.back_gnd_color + first_class_color = args.first_class_color + pic_type = args.pic_type + Max_width = args.Max_width + Rebuild_from = args.Rebuild_from + Rebuild_to = args.Rebuild_to + save_process_pics = args.save_process_pics + + + try: # 遍历文件深度,最小为1 + Label_Max_Search_layer=int(Label_Max_Search_layer) + except: + Label_Max_Search_layer=1000 + try: # GT标签图片通道数 + GT_channel=int(GT_channel) + except: + GT_channel=1 + try: # 背景颜色(背景选择0或255) + back_gnd_color=int(back_gnd_color) + except: + back_gnd_color=0 + try: # 第一类上的颜色(如果背景为0,选择1;) + first_class_color=int(first_class_color) + except: + first_class_color=1 + try: # 最大图片宽度(匹配时候用) + Max_width=int(Max_width) + except: + Max_width=10000 + if(save_process_pics.lower() == 'false'): + save_process_pics = False + elif(save_process_pics.lower() == 'true'): + save_process_pics = True + else: + save_process_pics = False + + D = Deal_image(Annotate_CLASSES=Annotate_CLASSES, Annotate_PALETTE=Annotate_PALETTE, src_label_fold=src_label_fold, save_pro_label_fold=save_pro_label_fold, save_GT_label_fold=save_GT_label_fold, GT_channel=GT_channel, pro_append_name=pro_append_name, GT_append_name=GT_append_name, back_gnd_color=back_gnd_color, first_class_color=first_class_color, pic_type=pic_type, Max_width=Max_width, Label_Max_Search_layer=Label_Max_Search_layer, save_process_pics=save_process_pics, bg_PALETTE = bg_PALETTE) + # print(D.src_CLASSES_NUM) + if Rebuild_from == 'label': + # 1.先将所有原始图片转为pro图片 + D.Conver_ori_label_pic_2_pro_pic_all() + pass + if Rebuild_to == 'GT': + # 2.再将pro图片转为GT图片 + D.Conver_pro_label_pic_2_GT_pic_all() + pass \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_ori_label.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_ori_label.py new file mode 100644 index 0000000..c777a84 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_ori_label.py @@ -0,0 +1,169 @@ +import os +import cv2 +import numpy as np +from collections import Counter, defaultdict +from PIL import Image +from Tool_Classes_And_Palette import Annotate_CLASSES, Annotate_PALETTE, bg_PALETTE + +# ※ 需要修改输入输出路径 ※ # +input_dir = './ORI_GT_label_fold' +output_dir_1 = 'Data/labels/train' +output_dir_2 = 'Data/labels/val' + +# ---------------------------------------------------- +# ※※※ 1. 修改: 移除背景项并统一格式 ※※※ +# ---------------------------------------------------- +# (1) 从 Annotate_CLASSES (元组) 中移除 '背景' +if Annotate_CLASSES and Annotate_CLASSES[-1] == '背景': + Annotate_CLASSES = Annotate_CLASSES[:-1] +# (2) 从 Annotate_PALETTE (列表) 中移除背景颜色 [0,0,0] +if Annotate_PALETTE and (Annotate_PALETTE[-1] == [0, 0, 0] or Annotate_PALETTE[-1] == (0, 0, 0)): + Annotate_PALETTE.pop() +# (3) 确保调色板中的所有颜色都是元组 (Tuple),以便后续查找 +Annotate_PALETTE = [tuple(color) for color in Annotate_PALETTE] +# (4) 确保 bg_PALETTE 是元组,以匹配 Counter 中的键类型 +bg_PALETTE = tuple(bg_PALETTE) +# (5) 检查类别和调色板长度是否一致 +if len(Annotate_CLASSES) != len(Annotate_PALETTE): + print(f"[警告] 移除背景后,类别 ({len(Annotate_CLASSES)}) 和调色板 ({len(Annotate_PALETTE)}) 长度不一致!") +else: + print(f"--- 成功移除背景项,剩余 {len(Annotate_CLASSES)} 个有效类别。 ---") + +os.makedirs(output_dir_1, exist_ok=True) +os.makedirs(output_dir_2, exist_ok=True) + +# 全局统计颜色频率与 class 像素频率 +global_color_counter = Counter() +global_class_counter = Counter() # 将用于统计(ori_label 模式) +color_class_counter = defaultdict(int) # (R,G,B) → count +color_to_old_class = {} # (R,G,B) → class_id +# *** ori_label 模式: 移除了 remap_class_dict *** + +# 自动提取颜色映射(跳过背景) +def extract_color_mapping(img_path): + img = Image.open(img_path).convert('RGB') + pixels = list(img.getdata()) + counter = Counter(pixels) + color_map = {} + for color, count in counter.items(): + global_color_counter[color] += count + if color != bg_PALETTE and color[0] == color[1] == color[2]: # 使用元组 bg_PALETTE + class_id = color[0] - 1 + if class_id >= 0: + color_map[color] = class_id + global_class_counter[class_id] += count # 使用 global_class_counter 统计 + color_class_counter[color] += count + color_to_old_class[color] = class_id + return color_map + +# 处理单张图片 +def process_image(img_path, save_path_list): + color_to_class = extract_color_mapping(img_path) + if not color_to_class: + print(f"[跳过] {os.path.basename(img_path)} 无有效目标") + return + + img = cv2.imread(img_path) + h, w = img.shape[:2] + img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + + lines = [] + for rgb, old_class_id in color_to_class.items(): + mask = np.all(img_rgb == rgb, axis=-1).astype(np.uint8) * 255 + contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + for contour in contours: + if contour.shape[0] < 3: + continue + norm_pts = contour.squeeze(1).astype(np.float32) + norm_pts[:, 0] /= w + norm_pts[:, 1] /= h + flat = norm_pts.flatten() + + # *** ori_label 模式: 直接使用 old_class_id *** + line = f"{old_class_id} " + " ".join([f"{x:.6f}" for x in flat]) + lines.append(line) + + if lines: + for save_path in save_path_list: + with open(save_path, 'w') as f: + for line in lines: + f.write(line + "\n") + print(f"[✔] 转换成功: {os.path.basename(save_path)},共 {len(lines)} 个实例") + else: + print(f"[⚠] {os.path.basename(img_path)} 没有轮廓") + +# 第一次遍历图像,仅提取颜色信息用于构建 class 映射 +for fname in os.listdir(input_dir): + if fname.lower().endswith('.png'): + img_path = os.path.join(input_dir, fname) + extract_color_mapping(img_path) + +# *** ori_label 模式: 移除了重映射 (remap) 逻辑块 *** +# (保留 class_pixel_count 用于统计) +class_pixel_count = {color_to_old_class[c]: count for c, count in color_class_counter.items()} + +# 第二次遍历图像,正式处理并生成标签 +for fname in os.listdir(input_dir): + if fname.lower().endswith('.png'): + img_path = os.path.join(input_dir, fname) + base_name = os.path.splitext(fname)[0] + txt_path_1 = os.path.join(output_dir_1, base_name + '.txt') + txt_path_2 = os.path.join(output_dir_2, base_name + '.txt') + process_image(img_path, [txt_path_1, txt_path_2]) + +# ---------------------------------------------------- +# ※※※ 2. 修改: 打印详细的颜色统计 ※※※ +# ---------------------------------------------------- +print("\n📊 所有图像颜色统计:") + +def get_label_info(class_id): + """辅助函数:根据 class_id 获取标签名和颜色""" + if 0 <= class_id < len(Annotate_CLASSES) and 0 <= class_id < len(Annotate_PALETTE): + return Annotate_CLASSES[class_id], Annotate_PALETTE[class_id] + else: + return "未知标签", (255, 255, 255) # 返回一个默认值 + +for color, count in global_color_counter.most_common(): + if color == bg_PALETTE: # 使用 bg_PALETTE 变量 + print(f"背景颜色 {color} 出现次数: {count}") + elif color[0] == color[1] == color[2]: + class_id = color[0] - 1 + label_name, label_color = get_label_info(class_id) + print(f"颜色 {color} → class {class_id} (标签: '{label_name}', 颜色: {label_color}),出现次数: {count}") + else: + # 此情况理论上不应出现,因为 extract_color_mapping 已过滤 + print(f"[⚠] 非灰阶颜色 {color},出现次数: {count} (此颜色不应被处理)") + +# ---------------------------------------------------- +# ※※※ 3. 修改: 打印详细的类别统计 (ori_label 模式) ※※※ +# ---------------------------------------------------- +print("\n✅ 有效类别统计(按 原 class_id 排序 → 总像素数):") +# 按照 old_id (原类别索引) 排序输出 +sorted_classes = sorted(class_pixel_count.items(), key=lambda item: item[0]) + +for old_id, pixel_count in sorted_classes: + label_name, label_color = get_label_info(old_id) + print(f"class {old_id} (标签: '{label_name}', 颜色: {label_color}): {pixel_count} pixels") + +# ---------------------------------------------------- +# ※※※ 4. 修改: 额外输出 原 class_id 到颜色的映射 (ori_label 模式) ※※※ +# ---------------------------------------------------- +print(f"\n🎨 找到的 原 class_id 与标签颜色 (Annotate_PALETTE) 映射表:") + +# 1. 按照 old_id (0, 1, 2...) 排序并按指定格式打印 +# 我们使用 class_pixel_count.keys() 来获取所有实际找到的 old_id +sorted_old_ids = sorted(class_pixel_count.keys()) + +for old_id in sorted_old_ids: + if 0 <= old_id < len(Annotate_PALETTE): + color_tuple = Annotate_PALETTE[old_id] + color_list = list(color_tuple) + print(f" {old_id}: {color_list}") + else: + # 预防性代码 + print(f" {old_id}: [颜色未在调色板中定义]") +# ---------------------------------------------------- +# ※※※ 修改结束 ※※※ +# ---------------------------------------------------- + +print(f"\n✅ 全部图像处理完毕。标签输出目录:{output_dir_1}、{output_dir_2}") \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_sort_label.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_sort_label.py new file mode 100644 index 0000000..d1c27e6 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/2_Check_and_Gen_Txt_Label_sort_label.py @@ -0,0 +1,204 @@ +import os +import cv2 +import numpy as np +from collections import Counter, defaultdict +from PIL import Image +from Tool_Classes_And_Palette import Annotate_CLASSES, Annotate_PALETTE, bg_PALETTE + +# ※ 需要修改输入输出路径 ※ # +input_dir = 'ORI_GT_label_fold' +output_dir_1 = 'Data/labels/train' +output_dir_2 = 'Data/labels/val' +output_GT_dir_1 = 'Data/labels_GT/train' +output_GT_dir_2 = 'Data/labels_GT/val' + +# 1. --- 您提供的类别和调色板 --- +# (1) 从 Annotate_CLASSES (元组) 中移除 '背景' +if Annotate_CLASSES and Annotate_CLASSES[-1] == '背景': + Annotate_CLASSES = Annotate_CLASSES[:-1] +# (2) 从 Annotate_PALETTE (列表) 中移除背景颜色 [0,0,0] +if Annotate_PALETTE and (Annotate_PALETTE[-1] == [0, 0, 0] or Annotate_PALETTE[-1] == (0, 0, 0)): + Annotate_PALETTE.pop() +# (3) 确保调色板中的所有颜色都是元组 (Tuple),以便后续查找 +Annotate_PALETTE = [tuple(color) for color in Annotate_PALETTE] +# (4) 确保 bg_PALETTE 是元组,以匹配 Counter 中的键类型 +bg_PALETTE = tuple(bg_PALETTE) +# (5) 检查类别和调色板长度是否一致 +if len(Annotate_CLASSES) != len(Annotate_PALETTE): + print(f"[警告] 移除背景后,类别 ({len(Annotate_CLASSES)}) 和调色板 ({len(Annotate_PALETTE)}) 长度不一致!") +else: + print(f"--- 成功移除背景项,剩余 {len(Annotate_CLASSES)} 个有效类别。 ---") + +os.makedirs(output_dir_1, exist_ok=True) +os.makedirs(output_dir_2, exist_ok=True) +os.makedirs(output_GT_dir_1, exist_ok=True) +os.makedirs(output_GT_dir_2, exist_ok=True) + +# 全局统计颜色频率与 class 像素频率 +global_color_counter = Counter() +global_class_counter = Counter() +color_class_counter = defaultdict(int) # (R,G,B) → count +color_to_old_class = {} # (R,G,B) → class_id +remap_class_dict = {} # old_class_id → new_class_id + +# 自动提取颜色映射(跳过背景) +def extract_color_mapping(img_path): + img = Image.open(img_path).convert('RGB') + pixels = list(img.getdata()) + counter = Counter(pixels) + color_map = {} + for color, count in counter.items(): + global_color_counter[color] += count + if color != (0, 0, 0) and color[0] == color[1] == color[2]: + class_id = color[0] - 1 + if class_id >= 0: + color_map[color] = class_id + global_class_counter[class_id] += count + color_class_counter[color] += count + color_to_old_class[color] = class_id + return color_map + +# --- 修改:函数签名增加了 gt_save_paths --- +def process_image(img_path, txt_save_paths, gt_save_paths): + color_to_class = extract_color_mapping(img_path) + if not color_to_class: + print(f"[跳过] {os.path.basename(img_path)} 无有效目标") + return + + img = cv2.imread(img_path) + h, w = img.shape[:2] + img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + + # --- 新增:创建新的 GT 掩码 (灰度图),0 为背景 --- + new_gt_mask = np.zeros((h, w), dtype=np.uint8) + lines = [] + + for rgb, old_class_id in color_to_class.items(): + # --- 修改:获取 new_class_id 并填充 new_gt_mask --- + new_class_id = remap_class_dict.get(old_class_id, old_class_id) + + # 创建布尔掩码 + mask_bool = np.all(img_rgb == rgb, axis=-1) + + # --- 新增:在 new_gt_mask 上填充新的 class_id + # (使用 new_class_id + 1,因为 0 是背景) + new_gt_mask[mask_bool] = new_class_id + 1 + + # --- 修改:基于布尔掩码创建 255 掩码用于 findContours --- + mask_255 = mask_bool.astype(np.uint8) * 255 + + contours, _ = cv2.findContours(mask_255, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + for contour in contours: + if contour.shape[0] < 3: + continue + norm_pts = contour.squeeze(1).astype(np.float32) + norm_pts[:, 0] /= w + norm_pts[:, 1] /= h + flat = norm_pts.flatten() + + # --- 修改:此处 new_class_id 已在循环外层定义 --- + line = f"{new_class_id} " + " ".join([f"{x:.6f}" for x in flat]) + lines.append(line) + + # --- 新增:保存处理后的 GT 掩码图像 --- + # 只要存在有效类别 (color_to_class 非空),就保存 GT 掩码 + for save_path in gt_save_paths: + try: + # 使用 PIL 保存灰度 PNG 图像 + Image.fromarray(new_gt_mask).save(save_path) + print(f"[✔] GT 掩码保存成功: {os.path.basename(save_path)}") + except Exception as e: + print(f"[✘] GT 掩码保存失败: {os.path.basename(save_path)} - {e}") + + # 保存 .txt 标签文件 (仅当找到轮廓时) + if lines: + for save_path in txt_save_paths: + with open(save_path, 'w') as f: + for line in lines: + f.write(line + "\n") + print(f"[✔] 转换成功: {os.path.basename(save_path)},共 {len(lines)} 个实例") + else: + # 即使没有轮廓,GT 掩码也已保存 + print(f"[⚠] {os.path.basename(img_path)} 没有轮廓 (但 GT 掩码已保存)") + +# 第一次遍历图像,仅提取颜色信息用于构建 class 映射 +for fname in os.listdir(input_dir): + if fname.lower().endswith('.png'): + img_path = os.path.join(input_dir, fname) + extract_color_mapping(img_path) + +# 构建 class_id 重映射表(按像素数从大到小排序) +class_pixel_count = {color_to_old_class[c]: count for c, count in color_class_counter.items()} +sorted_classes = sorted(class_pixel_count.items(), key=lambda x: x[1], reverse=True) +remap_class_dict = {old_cls: new_idx for new_idx, (old_cls, _) in enumerate(sorted_classes)} + +# 第二次遍历图像,正式处理并生成标签 +for fname in os.listdir(input_dir): + if fname.lower().endswith('.png'): + img_path = os.path.join(input_dir, fname) + base_name = os.path.splitext(fname)[0] + + txt_path_1 = os.path.join(output_dir_1, base_name + '.txt') + txt_path_2 = os.path.join(output_dir_2, base_name + '.txt') + + # --- 新增:定义 GT 掩码输出路径 (保存为 .png) --- + gt_path_1 = os.path.join(output_GT_dir_1, base_name + '.png') + gt_path_2 = os.path.join(output_GT_dir_2, base_name + '.png') + + process_image(img_path, [txt_path_1, txt_path_2], [gt_path_1, gt_path_2]) + +# 打印颜色统计和类别映射 +print("\n📊 所有图像颜色统计:") + +def get_label_info(class_id): + """辅助函数:根据 class_id 获取标签名和颜色""" + if 0 <= class_id < len(Annotate_CLASSES) and 0 <= class_id < len(Annotate_PALETTE): + return Annotate_CLASSES[class_id], Annotate_PALETTE[class_id] + else: + return "未知标签", (255, 255, 255) # 返回一个默认值 + +for color, count in global_color_counter.most_common(): + if color == bg_PALETTE: # 使用 bg_PALETTE 变量 + print(f"背景颜色 {color} 出现次数: {count}") + elif color[0] == color[1] == color[2]: + class_id = color[0] - 1 + label_name, label_color = get_label_info(class_id) + print(f"颜色 {color} → class {class_id} (标签: '{label_name}', 颜色: {label_color}),出现次数: {count}") + else: + # 此情况理论上不应出现,因为 extract_color_mapping 已过滤 + print(f"[⚠] 非灰阶颜色 {color},出现次数: {count} (此颜色不应被处理)") + +# 打印详细的类别统计 +print("\n✅ 有效类别统计(原 class_id → 新 class_id → 总像素数):") +# 按照 new_id (新类别索引) 排序输出 +sorted_remap = sorted(remap_class_dict.items(), key=lambda item: item[1]) +for old_id, new_id in sorted_remap: + label_name, label_color = get_label_info(old_id) + print(f"class {old_id} (标签: '{label_name}', 颜色: {label_color}) → {new_id}: {class_pixel_count[old_id]} pixels") + +# 4. 额外输出新 class_id 到颜色的映射 ※※※ +print(f"\n🎨 新 class_id 与标签颜色 (Annotate_PALETTE) 映射表:") +# 1. 创建一个 new_id -> color 的映射 +new_id_to_color = {} +for old_id, new_id in remap_class_dict.items(): + if 0 <= old_id < len(Annotate_PALETTE): + # 从 Annotate_PALETTE 获取原始颜色(它是一个元组) + color_tuple = Annotate_PALETTE[old_id] + # 转换为列表 [R, G, B] 以匹配您要的格式 + new_id_to_color[new_id] = list(color_tuple) + else: + # 预防性代码,以防 old_id 超出范围 + new_id_to_color[new_id] = [-1, -1, -1] # 表示错误/未找到 + +# 2. 按照 new_id (0, 1, 2...) 排序并按指定格式打印 +sorted_new_ids = sorted(new_id_to_color.keys()) +for new_id in sorted_new_ids: + color_list = new_id_to_color[new_id] + # 格式化输出: {id}: {color_list} + # (注意:颜色列表的格式会自然包含逗号和空格) + print(f" {new_id}: {color_list}") + +print(f"\n✅ 全部图像处理完毕。") +print(f" 标签 (.txt) 输出目录: {output_dir_1}, {output_dir_2}") +print(f" GT 掩码 (.png) 输出目录: {output_GT_dir_1}, {output_GT_dir_2}") diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_Classes_And_Palette.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_Classes_And_Palette.py new file mode 100644 index 0000000..ffd1cee --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_Classes_And_Palette.py @@ -0,0 +1,20 @@ +# # 胆囊标注 +# Annotate_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','背景') # 待分类的类 +# Annotate_PALETTE = [[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[117,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66],[0,0,0]] # 每一类的像素直 +# # 甲状腺标注 +# Annotate_CLASSES = ('甲状腺', '针', '双极电凝', '止血海绵', '止血纱布', '喷洒', '标本袋', '肝蒂', '神经', '线', '肝脏', '肝总管', '胆总管', '胆囊管', '引流管', '推结器', '肌肉', '肿瘤', '胆囊', '吸引器', '生物夹', '动脉', '纱布', '乳胶管', '血管阻断夹', '分离钳', '肌肉', '无损伤钳', '电凝', '金属钛夹', '吻合器', '棉球', '脂肪', '超声刀', '钳夹', '静脉', '韧带', '术中超声','背景') # 待分类的类 +# Annotate_PALETTE = [[255, 148, 81], [134, 124, 118], [155, 62, 0], [181, 227, 14], [160, 15, 95], [90, 120, 41], [112, 113, 150], [133, 102, 140], [168, 162, 252], [0, 157, 142], [255, 91, 0], [72, 0, 255], [0, 155, 33], [255, 0, 0], [0, 255, 0], [202, 202, 200], [254, 141, 179], [254, 161, 0], [255, 234, 0], [255, 0, 255], [0, 160, 233], [117, 0, 0], [255, 255, 255], [197, 83, 181],[255, 172, 159], [85, 111, 181], [29, 32, 136], [52, 184, 178], [166, 24, 232], [0, 254, 254], [136, 162, 196], [146, 175, 236], [155, 132, 0], [240, 16, 116], [66, 115, 82], [42, 8, 66], [181, 85, 105], [138, 251, 213],[0,0,0]] # 每一类的像素直 +# # 胃癌标注+去雾影像标注 +Annotate_CLASSES = ('胃', '针', '双极电凝', '止血海绵', '止血纱布', '喷洒', '标本袋', '肝蒂', '小肠', '线', '肝脏', '脾脏', '胆总管', '胆囊管', '引流管', '推结器', '淋巴结', '胰腺', '胆囊', '吸引器', '生物夹', '动脉', '纱布', '乳胶管', '分离钳', '超声刀', '无损伤钳', '电凝', '金属钛夹', '吻合器', '脂肪', '剪刀', '钳夹', '静脉', '韧带','背景') # 待分类的类 +Annotate_PALETTE = [(237, 35, 85), (134, 124, 118), (155, 62, 0), (187, 227, 14), (160, 15, 95), (90, 120, 41), (112, 113, 150), (133, 102, 140), (110, 255, 166), (0, 157, 142), (255, 91, 0), (72, 0, 255), (0, 155, 33), (255, 0, 0), (0, 255, 0), (202, 202, 200), (201, 255, 74), (245, 161, 0), (255, 234, 0), (255, 0, 255), (0, 160, 233), (117, 0, 0), (255, 255, 255), (197, 83, 181), (85, 111, 181), (29, 32, 136), (52, 184, 178), (167, 24, 233), (0, 255, 255), (136, 162, 196), (155, 132, 0), (240, 16, 116), (66, 115, 82), (42, 8, 66), (181, 85, 105), [0,0,0]] # 每一类的像素直 +# # 甲状腺标注 +# Annotate_CLASSES = ('甲状旁腺', '喉返神经', '电凝', '无损伤钳', '超声刀', '分离钳', '纱布', '背景') # 待分类的类 +# Annotate_PALETTE = [(238, 25, 30), (24, 124, 248), (198, 24, 248), (24, 248, 240), (248, 119, 24), (24, 248, 114), (255,255,255), [0,0,0]] # 每一类的像素值 +# # 磁器械标注 +# Annotate_CLASSES = ( '双极电凝', '止血海绵', '止血纱布', '喷洒', '标本袋', '肝脏', '肝总管', '胆总管', '胆囊管', '引流管', '胆囊', '磁器械', '生物夹', '胆囊动脉', '纱布', '分离钳', '剪刀', '无损伤钳','电凝', '金属钛夹', '脂肪', '背景') # 待分类的类 +# Annotate_PALETTE = [ (155, 62, 0), (181, 227, 14), (160, 15, 95), (90, 120, 41), (112, 113, 150), (255, 91, 0), (72, 0, 255), (0, 155, 33), (255, 0, 0), (0, 255, 0), (255, 234, 0), (255, 0, 255), (0, 160, 233), (117, 0, 0), (255, 255, 255), (85, 111, 181), (29, 32, 136), (52, 184, 178), (167, 24, 233), (0, 255, 255), (155, 132, 0), [0,0,0]] # 每一类的像素值 +# # 二分类标注 +# Annotate_CLASSES = ( '特殊部位', '背景') # 待分类的类 +# Annotate_PALETTE = [ [255,255,255], [0,0,0]] # 每一类的像素值 + +bg_PALETTE = [0,0,0] # 背景的RGB \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_convert_bmp_jpg_to_png.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_convert_bmp_jpg_to_png.py new file mode 100644 index 0000000..be95ef7 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_convert_bmp_jpg_to_png.py @@ -0,0 +1,167 @@ +import os +import argparse +from PIL import Image +import concurrent.futures + +# 确保使用的是 os.cpu_count() 来自动检测核心数 +try: + DEFAULT_WORKERS = os.cpu_count() or 1 +except AttributeError: + try: + import multiprocessing + DEFAULT_WORKERS = multiprocessing.cpu_count() + except (ImportError, NotImplementedError): + DEFAULT_WORKERS = 4 + +def _process_single_file(source_path, png_path, delete_source): + """ + 工作函数:处理单个文件的转换、缩放,并保存为PNG。 + (此函数与上一版完全相同) + """ + filename = os.path.basename(source_path) + png_filename = os.path.basename(png_path) + + try: + with Image.open(source_path) as img: + width, height = img.size + min_side = min(width, height) + resize_info = "" + + if min_side > 1080: + scale_factor = 1080 / min_side + new_width = int(width * scale_factor) + new_height = int(height * scale_factor) + img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) + resize_info = f" (已缩放至 {new_width}x{new_height})" + + img.save(png_path, 'PNG') + + if delete_source: + os.remove(source_path) + return "success_del", filename, f"{png_filename}{resize_info}" + else: + return "success", filename, f"{png_filename}{resize_info}" + + except Exception as e: + return "fail", filename, str(e) + +def process_images(folder_path, delete_source=False, max_workers=DEFAULT_WORKERS): + """ + 批量将指定文件夹 (及其所有子文件夹) 内的 .bmp, .jpg, .jpeg 图像 + 转换为 .png, 并按比例缩放 (最小边<=1080px)。 + """ + + if not os.path.isdir(folder_path): + print(f"错误:文件夹 '{folder_path}' 不存在或不是一个有效的目录。") + return + + print(f"--- 开始递归处理文件夹: {folder_path} ---") + print(f"--- 使用最多 {max_workers} 个进程并行处理 ---") + if delete_source: + print("警告:已启用源文件删除模式。") + + # --- 1. 收集所有需要处理的任务 --- + tasks = [] + supported_extensions = ('.bmp', '.jpg', '.jpeg') + + # <--- 更改:从 os.listdir() 切换到 os.walk() 以支持递归 + print("--- 正在扫描所有子文件夹...") + for dirpath, dirnames, filenames in os.walk(folder_path): + for filename in filenames: + if filename.lower().endswith(supported_extensions): + + # 构建完整的文件路径 + source_path = os.path.join(dirpath, filename) + + # 构建PNG输出路径 (保持在同一个子文件夹内) + base_name = os.path.splitext(filename)[0] + png_path = os.path.join(dirpath, base_name + '.png') + + # 避免重复转换 + if os.path.exists(png_path): + # 打印相对路径,使其更清晰 + relative_path = os.path.relpath(png_path, folder_path) + print(f" [跳过] {relative_path} (目标文件已存在)") + continue + + tasks.append((source_path, png_path, delete_source)) + # <--- 更改结束 --- + + if not tasks: + print(f"未在文件夹中找到新的 {supported_extensions} 文件。") + return + + print(f"--- 发现 {len(tasks)} 个新图像文件,开始转换... ---") + + converted_count = 0 + failed_count = 0 + + # --- 2. 使用 ProcessPoolExecutor 执行任务 (此部分不变) --- + with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor: + future_to_source = { + executor.submit(_process_single_file, source, png, delete): source + for source, png, delete in tasks + } + + # --- 3. 实时获取已完成的结果 (此部分不变) --- + for future in concurrent.futures.as_completed(future_to_source): + source_path_orig = future_to_source[future] + + try: + status, name, result = future.result() + + # 获取相对路径以便于阅读 + relative_dir = os.path.relpath(os.path.dirname(source_path_orig), folder_path) + # 如果是根目录,relative_dir 会是 ".",我们将其替换为空字符串 + if relative_dir == ".": + log_name = name + else: + log_name = os.path.join(relative_dir, name) + + if status == "success": + print(f" [成功] {log_name} -> {result}") + converted_count += 1 + elif status == "success_del": + print(f" [成功] {log_name} -> {result} (并已删除源文件)") + converted_count += 1 + elif status == "fail": + print(f" [失败] 转换 {log_name} 时出错: {result} (源文件未删除)") + failed_count += 1 + + except Exception as e: + print(f" [严重失败] 处理 {os.path.basename(source_path_orig)} 时进程出错: {e}") + failed_count += 1 + + print("--- 处理完毕 ---") + if converted_count > 0: + print(f"成功转换 {converted_count} 个文件。") + if failed_count > 0: + print(f"失败 {failed_count} 个文件。") + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="将指定文件夹(及其所有子文件夹)中的所有 .bmp, .jpg, .jpeg 文件批量转换为 .png 文件 (并行加速),并按比例缩放 (最小边<=1080px)。" + ) + + parser.add_argument( + "folder", + type=str, + help="包含 .bmp, .jpg, .jpeg 图像的 (根) 目标文件夹路径 (例如 ./ABC)" + ) + + parser.add_argument( + "-d", "--delete-source", + action="store_true", + help="在成功转换为 .png 后,删除原始的 .bmp/.jpg/.jpeg 文件。" + ) + + parser.add_argument( + "-w", "--workers", + type=int, + default=DEFAULT_WORKERS, + help=f"指定用于转换的工作进程数 (默认: {DEFAULT_WORKERS}, 即本机CPU核心数)" + ) + + args = parser.parse_args() + + process_images(args.folder, args.delete_source, args.workers) \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_deal_labels.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_deal_labels.py new file mode 100644 index 0000000..2d1ed34 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_deal_labels.py @@ -0,0 +1,180 @@ +import cv2 +import random +import numpy as np +from scipy.ndimage import label, distance_transform_edt + +def skeletonize(image): + """骨架化函数,确保线条连通性并缩减为1像素宽""" + skeleton = np.zeros_like(image) + temp_image = np.copy(image) + + while True: + eroded = cv2.erode(temp_image, None) # 腐蚀操作 + temp_dilate = cv2.dilate(eroded, None) # 膨胀操作 + temp = cv2.subtract(temp_image, temp_dilate) # 提取边缘 + skeleton = cv2.bitwise_or(skeleton, temp) # 将边缘加入骨架 + temp_image = np.copy(eroded) + if cv2.countNonZero(temp_image) == 0: + break + return skeleton + +# 1. *** 边缘检测并膨胀 *** +def edge_detection(image): + """对图像的各个通道进行边缘检测并进行膨胀处理""" + b_channel, g_channel, r_channel = cv2.split(image) + gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + + edges_b = cv2.Canny(b_channel, 100, 200) + edges_g = cv2.Canny(g_channel, 100, 200) + edges_r = cv2.Canny(r_channel, 100, 200) + edges_gray = cv2.Canny(gray_image, 100, 200) + + # 合并所有边缘检测结果 + edges = cv2.bitwise_or(edges_b, edges_g) + edges = cv2.bitwise_or(edges, edges_r) + edges = cv2.bitwise_or(edges, edges_gray) + + # 创建膨胀核并进行膨胀操作 + kernel = np.ones((3, 3), np.uint8) + dilated_image = cv2.dilate(edges, kernel, iterations=1) + + return dilated_image + +# 2. ** 检测连通区域 *** +def detect_connected_regions(dilated_image): + """检测图像中的连通区域并过滤掉小区域""" + _, binary = cv2.threshold(dilated_image, 1, 255, cv2.THRESH_BINARY_INV) + binary[binary > 0] = 1 # 转换为二值图像 + + # 标记连通区域 + structure = np.ones((3, 3), dtype=int) + labeled_array, num_features = label(binary, structure=structure) + + # 清除掉小于100像素的区域 + filtered_labeled_array = np.copy(labeled_array) + for label_num in range(1, num_features + 1): + area = np.sum(labeled_array == label_num) + if area < 100: + filtered_labeled_array[filtered_labeled_array == label_num] = 0 + + # 重新标记过滤后的连通区域 + filtered_labeled_array, num_features = label(filtered_labeled_array, structure=structure) + return filtered_labeled_array, num_features # 返回过滤后的labeled_array和未过滤的labeled_array(用于寻找颜色) + +# 3. *** 分水岭填充白色区域 *** +def fill_white_regions(filtered_labeled_array): + """使用分水岭算法填充白色区域""" + # 准备三通道图像作为分水岭算法的输入 + color_image = np.zeros((filtered_labeled_array.shape[0], filtered_labeled_array.shape[1], 3), dtype=np.uint8) + + # 将过滤后的 labeled_array 转化为 32 位整型,作为分水岭的 markers + markers = np.copy(filtered_labeled_array).astype(np.int32) + markers[markers == 0] = -1 # 背景标记为 -1 + + # 执行分水岭算法 + cv2.watershed(color_image, markers) + + # 更新分水岭结果 + filled_labeled_array = markers.astype(int) + filled_labeled_array[filled_labeled_array == -1] = 0 + + # 使用距离变换,计算边缘像素(0)最近的非零值 + non_zero_mask = filled_labeled_array != 0 + distance, nearest_indices = distance_transform_edt(non_zero_mask == 0, return_indices=True) + nearest_values = filled_labeled_array[tuple(nearest_indices)] + filled_labeled_array[filled_labeled_array == 0] = nearest_values[filled_labeled_array == 0] + + return filled_labeled_array + +# 4. *** 对连通区域上色(使用“filtered_labeled_array”作为颜色判断,给“filled_labeled_array”上色) *** +def color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, Annotate_PALETTE): + """根据原始图像的颜色和注解调色板给连通区域上色""" + + # 初始化一个三通道的彩色图像 + colored_image = np.zeros((*filled_labeled_array.shape, 3), dtype=np.uint8) + + # 遍历filtered_labeled_array中的每个标签 + unique_labels = np.unique(filtered_labeled_array) + + for label_num in unique_labels: + if label_num == 0: + continue # 跳过背景标签 + + # 找到filtered_labeled_array中等于当前标签的区域 + mask_filtered = (filtered_labeled_array == label_num) + + # 获取ori_labeled_image中对应区域的RGB值 + region_rgb_values = ori_labeled_image[mask_filtered] + + if len(region_rgb_values) == 0: + continue + + # 计算区域的RGB平均值 + average_rgb = np.mean(region_rgb_values, axis=0) + + # 找到Annotate_PALETTE中与average_rgb最接近的颜色 + closest_palette_color = find_closest_palette_color(average_rgb, Annotate_PALETTE) + + # 将该颜色赋给filled_labeled_array对应区域的元素 + mask_filled = (filled_labeled_array == label_num) + colored_image[mask_filled] = closest_palette_color + + return colored_image + +# 寻找最近邻颜色 +def find_closest_palette_color(average_rgb, Annotate_PALETTE): + """根据平均RGB值找到Annotate_PALETTE中最接近的颜色""" + Annotate_PALETTE = [[color[2], color[1], color[0]] for color in Annotate_PALETTE] + + average_rgb = np.array(average_rgb) + min_distance = float('inf') + closest_color = None + + # 遍历调色板,计算每个颜色与平均RGB的欧几里得距离 + for palette_color in Annotate_PALETTE: + palette_color = np.array(palette_color) + distance = np.linalg.norm(average_rgb - palette_color) # 欧几里得距离 + + if distance < min_distance: + min_distance = distance + closest_color = palette_color + + return closest_color + +# 5. 对Array区域上色(4的简化版) +def Tool_color_connected_array(Array): + colored_image = np.zeros((*Array.shape, 3), dtype=np.uint8) + for label_num in range(1, np.max(Array) + 1): + color = [np.random.randint(0, 254) for _ in range(3)] + colored_image[Array == label_num] = color + return colored_image + +if __name__ == '__main__': + """超参数""" + image_path = './2023_02_03_09_13_48.00_08_04_21.Still085.png' + + """主函数,处理图像并保存结果""" + # 读取图像 + image = cv2.imread(image_path) + + # 1. 边缘检测并膨胀 + dilated_image = edge_detection(image) + cv2.imwrite('./1_1_range_image.png', dilated_image) + + # 2. 检测连通区域 + filtered_labeled_array, _ = detect_connected_regions(dilated_image) + colored_image_filtered = Tool_color_connected_array(filtered_labeled_array) + cv2.imwrite('./2_colored_image_filtered.png', colored_image_filtered) + + # 3. 分水岭填充白色区域 + filled_labeled_array = fill_white_regions(filtered_labeled_array) + colored_image_filled = Tool_color_connected_array(filled_labeled_array) + cv2.imwrite('./3_colored_image_filled.png', colored_image_filled) + + # 4. 对连通区域上色 + ori_labeled_image = image + colored_image_final = color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, Annotate_PALETTE) + cv2.imwrite('./4_color_image_Final.png', colored_image_final) + + + print("处理后的图片已保存。") diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_resize_pics.py b/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_resize_pics.py new file mode 100644 index 0000000..1ab5592 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/Tool_resize_pics.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import argparse +from PIL import Image +import concurrent.futures +import time + +# --- 1. 复制自您脚本中的 CPU 核心数检测逻辑 --- +try: + DEFAULT_WORKERS = os.cpu_count() or 1 +except AttributeError: + try: + import multiprocessing + DEFAULT_WORKERS = multiprocessing.cpu_count() + except (ImportError, NotImplementedError): + DEFAULT_WORKERS = 4 + +# --- 2. 定义支持的图片格式 --- +SUPPORTED_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.bmp', '.webp', '.tiff', '.tif') + +# --- 3. 确定Pillow的重采样过滤器 --- +try: + RESAMPLE_FILTER = Image.Resampling.LANCZOS +except AttributeError: + RESAMPLE_FILTER = Image.LANCZOS + + +def _process_single_image(image_path, target_min_side): + """ + 工作函数:处理单个文件的缩放。(此函数与上一版完全相同) + + :param image_path: 完整图片路径 + :param target_min_side: 目标最小边长 (例如 1080) + :return: (状态, 原始文件名, 结果/错误信息) + """ + filename = os.path.basename(image_path) + + try: + with Image.open(image_path) as img: + img_info = img.info.copy() + width, height = img.size + min_side = min(width, height) + + # --- 核心逻辑:检查是否需要缩放 --- + if min_side <= target_min_side: + # 返回相对路径以便于阅读 + return "skipped", image_path, f"最小边 ({min_side}px) 已 <= {target_min_side}px" + + # --- 计算新尺寸 --- + scale_ratio = target_min_side / min_side + new_width = int(width * scale_ratio) + new_height = int(height * scale_ratio) + + # --- 执行缩放 --- + resized_img = img.resize((new_width, new_height), RESAMPLE_FILTER) + + # --- 覆盖保存 --- + resized_img.save(image_path, **img_info) + + # 返回相对路径以便于阅读 + return "success", image_path, f"从 {width}x{height} -> {new_width}x{new_height}" + + except Exception as e: + # 返回相对路径以便于阅读 + return "fail", image_path, str(e) + +def resize_images_in_folder(folder_path, target_min_side=1080, max_workers=DEFAULT_WORKERS): + """ + *** [已更新] *** + 批量将文件夹及其所有子文件夹中最小边 > target_min_side 的图片等比例缩小。 + + :param folder_path: 目标根文件夹路径 + :param target_min_side: 目标最小边长 + :param max_workers: 使用的进程数 + """ + + if not os.path.isdir(folder_path): + print(f"错误:文件夹 '{folder_path}' 不存在或不是一个有效的目录。") + return + + print(f"--- 开始 **递归** 处理文件夹: {folder_path} ---") + print(f"--- 目标最小边: {target_min_side}px ---") + print(f"--- 使用最多 {max_workers} 个进程并行处理 ---") + + start_time = time.time() + + # --- 1. 收集所有需要处理的任务 (*** [核心修改] ***) --- + tasks = [] + print("--- 正在递归扫描所有子文件夹... ---") + + # 使用 os.walk 递归遍历 + for dirpath, dirnames, filenames in os.walk(folder_path): + for filename in filenames: + # 检查文件扩展名是否在支持的列表中 + if filename.lower().endswith(SUPPORTED_EXTENSIONS): + # 构造完整的文件路径 + image_path = os.path.join(dirpath, filename) + tasks.append((image_path, target_min_side)) + + if not tasks: + print(f"未在 {folder_path} 及其子文件夹中找到支持的图片文件。") + return + + print(f"--- 发现 {len(tasks)} 个图片文件,开始处理... ---") + + resized_count = 0 + skipped_count = 0 + failed_count = 0 + + # 转换为相对路径,使输出更简洁 + base_folder_path = os.path.abspath(folder_path) + + # --- 2. 使用 ProcessPoolExecutor 执行任务 (与上一版相同) --- + with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as executor: + future_to_path = { + executor.submit(_process_single_image, path, size): path + for path, size in tasks + } + + # --- 3. 实时获取已完成的结果 (与上一版相同) --- + for future in concurrent.futures.as_completed(future_to_path): + try: + # status, path_or_name, result + status, image_path, result = future.result() + + # 转换为相对路径 + try: + display_path = os.path.relpath(image_path, base_folder_path) + except ValueError: + display_path = image_path # 如果不在同一驱动器(Windows),则显示完整路径 + + if status == "success": + print(f" [成功] {display_path}: {result}") + resized_count += 1 + elif status == "skipped": + print(f" [跳过] {display_path}: {result}") + skipped_count += 1 + elif status == "fail": + print(f" [失败] {display_path}: {result}") + failed_count += 1 + + except Exception as e: + orig_path = future_to_path[future] + print(f" [严重失败] 处理 {orig_path} 时进程出错: {e}") + failed_count += 1 + + end_time = time.time() + print("--- 处理完毕 ---") + if resized_count > 0: + print(f"成功缩放 {resized_count} 个文件。") + if skipped_count > 0: + print(f"跳过 {skipped_count} 个文件 (无需缩放)。") + if failed_count > 0: + print(f"失败 {failed_count} 个文件。") + print(f"总耗时: {end_time - start_time:.2f} 秒。") + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="*** [已更新] *** 批量 **递归** 缩放文件夹及其子文件夹中的图片,使最小边不超过指定值。" + ) + + # 位置参数:文件夹路径 (必需) + parser.add_argument( + "folder", + type=str, + help="包含图片的 **根** 文件夹路径 (例如 ./MyImages),将递归处理所有子文件夹。" + ) + + # 选项参数:目标尺寸 (可选) + parser.add_argument( + "-s", "--size", + type=int, + default=1080, + help="指定目标最小边长 (默认: 1080)。如果图片最小边已小于此值,则跳过。" + ) + + # 选项参数:控制进程数 (可选) + parser.add_argument( + "-w", "--workers", + type=int, + default=DEFAULT_WORKERS, + help=f"指定用于转换的工作进程数 (默认: {DEFAULT_WORKERS}, 即本机CPU核心数)" + ) + + args = parser.parse_args() + + # 将文件夹路径、"目标尺寸" 和 "工作进程数" 传递给函数 + resize_images_in_folder(args.folder, args.size, args.workers) \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/Yolo数据集构建/Yolo数据集构建_使用手册 b/Seg_All_In_One_YoloModel/Yolo数据集构建/Yolo数据集构建_使用手册 new file mode 100644 index 0000000..3832565 --- /dev/null +++ b/Seg_All_In_One_YoloModel/Yolo数据集构建/Yolo数据集构建_使用手册 @@ -0,0 +1,69 @@ +#### 0. 准备工作 #### +# 清理旧文件 +rm -r ./Label/* ./ORI/* +rm -r ./result_stack_*透明度 ./Data +rm -r ./ORI_GT_label_fold ./ORI_pro_label_fold ./__pycache__ +conda activate SMP + +# A. 图像移动 +cp 磁辅助分割图像-有效图/* Label/ # TODO TODO 将标注后图片放入Label中 # 保证Label中图片与ORI中图片一一对应,且命名相同 +cp 磁辅助分割-原图/* ORI/ # TODO TODO 将原始图片放入ORI中 +# B.1 修改类别颜色 +vim Tool_Classes_And_Palette.py # 修改 Annotate_CLASSES、Annotate_PALETTE 以匹配标注时的类别与颜色 +# B.2 将bmp、jpg图片转为png +python Tool_convert_bmp_jpg_to_png.py ./Label --delete-source +python Tool_convert_bmp_jpg_to_png.py ./ORI --delete-source +# B.3 将图片转为最大边限制为1080 +python Tool_resize_pics.py ./Label # -s 1080 +python Tool_resize_pics.py ./ORI # -s 1080 +# C. 检测图片是否匹配 +python 0_1_check_picture_pair.py # -i "./ORI" -l "./Label" -p "" -s "" +# python 0_1_check_picture_pair.py # -i "../../DataSet_Public/6_CWK_2_cfz/images/train" -l "../../DataSet_Public/6_CWK_2_cfz/labels_GT/train" -p "" -s "" +# D. 生成堆叠图片(可视化标签效果) +bash 0_2_TOOL_stack_pics.sh -i "./ORI" -l ./Label -r ./result_stack_0.3透明度 -a 0.3 -p "" -s "_label" +# E. ※下载图片,查看匹配的是否有问题※ + +#### 1. 批量化生成训练、测试集图片 #### +# A. 将图片转为GT图片 +python 1_deal_labels.py -src_fold ./Label +# B. 新建数据最终存储文件夹 +rm -r ./Data +mkdir -p Data/images/train Data/images/val +cp ORI/* Data/images/train/ +cp ORI/* Data/images/val/ # 或根据需要分配训练集、验证集 +mkdir -p Data/labels/train Data/labels/val +mkdir -p Data/labels_GT/train Data/labels_GT/val +# C. 生成labels 、 labels_GT图片到 Data/labels_GT/train 中 +# Way 1:※推荐※(将类别压缩) +python 2_Check_and_Gen_Txt_Label_sort_label.py +# Way 2:(使用原始类别) +# python 2_Check_and_Gen_Txt_Label_ori_label.py # Way 2 +# cp ORI_GT_label_fold/* Data/labels_GT/train/ # Way 2 +# cp ORI_GT_label_fold/* Data/labels_GT/val/ # Way 2 + +#### 2. 纳入到Yolo训练体系 #### +# A. 移动数据 +cp -r ./Data ../../DataSet_Public/8_Haze_Baidu_Plus # TODO TODO 将 Data 文件夹改名后 放入 ../../DataSet_Public 下 +# B. 设置输出颜色参考 +将 程序输出的信息存入 ../../Seg_All_In_One_YoloModel/dataset.yaml 的 color中作为最终输出颜色的参考 +# C. 根据 Seg_All_In_One_YoloModel 中的使用手册新增数据集、进行配置、训练 +修改 dataset.yaml 中 训练数据集、修改 yolo_config.py 中 EPOCHS、PATIENCE + +############################## 进行训练推理(整体流程) ############################################ +# 1. 批量化训练 +conda activate SMP +cd ~/Desktop/Seg/Seg_All_In_One_YoloModel +bash yolo_train.sh +# 2. 复制最优模型到预测文件夹 +bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "best.pt" && bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "epoch100.pt" && bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "epoch50.pt" && bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "epoch150.pt" +# 3. 批量化预测+热度图可视化 +bash yolo_predict.sh --conf 0.2 --pt_name "epoch100.pt" && bash yolo_predict.sh --conf 0.2 && bash yolo_predict.sh --conf 0.2 --pt_name "epoch50.pt" && bash yolo_predict.sh --conf 0.2 --pt_name "epoch150.pt" +bash ./yolo_predict.sh --pt_name "best.pt" --heatmap_method "All" # && bash ./yolo_predict.sh --pt_name "epoch100.pt" --heatmap_method "All" +# 4. 横向对比结果 +python yolo_predict_V2_compare_all.py +# 5. 打包预测结果(不包含*.pt模型文件) +cd /home/wkmgc/Desktop/Seg/BestMode_Predict_Results_DataSet_Public/ +zip -r /home/wkmgc/Desktop/8_Haze_Baidu_Plus-Yolo.zip 8_Haze_Baidu_Plus*-Yolo -x "*.pt" # TODO TODO +# 6. 打包训练结果(只有.png、.jpg、.csv文件) +cd /home/wkmgc/Desktop/Seg/Hardisk/ +zip -r /home/wkmgc/Desktop/8_Haze_Baidu_Plus-Yolo_train.zip 8_Haze_Baidu_Plus-Yolo -i \*.png \*.jpg \*.csv # TODO TODO \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/dataset.yaml b/Seg_All_In_One_YoloModel/dataset.yaml new file mode 100644 index 0000000..be2fbbd --- /dev/null +++ b/Seg_All_In_One_YoloModel/dataset.yaml @@ -0,0 +1,318 @@ +############ 第一部分:数据集跟路径 ############ +# # V1: 1_CholecSeg8k-13Type-1920x1080 +# path: ../DataSet_Public/1_CholecSeg8k-13Type-1920x1080 +# # V2:2_AutoLaparo-10Type-1920x1080 +# path: ../DataSet_Public/2_AutoLaparo-10Type-1920x1080 +# # V3:3_1_Endovis_2017-8Type-512x512 +# path: ../DataSet_Public/3_1_Endovis_2017-8Type-512x512 +# # V4:3_2_Endovis_2018-8Type-512x512 +# path: ../DataSet_Public/3_2_Endovis_2018-8Type-512x512 +# # V5:4_Dresden-11Type-512x512 +# path: ../DataSet_Public/4_Dresden-11Type-512x512 + +# # V6:5_LC_1_blood_verssel +# path: ../DataSet_Public/5_LC_1_blood_verssel +# # V7:5_LC_2_artery +# path: ../DataSet_Public/5_LC_2_artery +# # V8:5_LC_3_cystic_duct +# path: ../DataSet_Public/5_LC_3_cystic_duct +# # V9:5_LC_4_foreigner +# path: ../DataSet_Public/5_LC_4_foreigner +# # V10:5_LC_5_stop_bleed +# path: ../DataSet_Public/5_LC_5_stop_bleed +# # V11:6_CWK_1_yws +# path: ../DataSet_Public/6_CWK_1_yws +# # V12:6_CWK_2_cfz +# path: ../DataSet_Public/6_CWK_2_cfz +# # V13: 5_TQY # TODO +# path: ../DataSet_Public/5_TQY +# # V14: 5_Haze_ori、6_Haze_AOD_Net、7_Haze_Baidu、8_Haze_Baidu_Plus # TODO +# path: ../DataSet_Public/8_Haze_Baidu_Plus + +# 默认使用本机已存在的公开数据集,便于 yolo_config.py smoke test 直接通过。 +# 切换实验数据集时,请同步修改 path/test/train/val 和 names。 +path: ../DataSet_Public/3_1_Endovis_2017-8Type-512x512 + +# # # Test_V1:5_Predict_Video +# path: ../DataSet_Public/5_Predict_Video/LC_Video_1 + +############ 第二部分:测试集相对路径 ############ +# 训练集和验证集图片路径 (相对于 'path') +# # V1: 1_CholecSeg8k-13Type-1920x1080 +# test: images/val +# # V2:2_AutoLaparo-10Type-1920x1080 +# test: images/val +# # V3:3_1_Endovis_2017-8Type-512x512 +# test: images/val +# # V4:3_2_Endovis_2018-8Type-512x512 +# test: images/val +# # V5:4_Dresden-11Type-512x512 +# test: images/val # images/test + +# # V6:5_LC_1_blood_verssel +test: images/val +# # V7:5_LC_2_artery +# test: images/val +# # V8:5_LC_3_cystic_duct +# test: images/val +# # V9:5_LC_4_foreigner +# test: images/val +# # V10:5_LC_5_stop_bleed +# test: images/val +# # V11:6_CWK_1_yws +# test: images/val +# # V12:6_CWK_2_cfz +# test: images/val +# # V13:5_TQY +# test: images/val +# # V14:5_Haze_ori、6_Haze_AOD_Net、7_Haze_Baidu、8_Haze_Baidu_Plus +# test: images/val + +# # Test_V1:5_Predict_Video +# test: images/val + +############ 第三部分:训练集、验证集相对路径 ############ +train: images/train +val: images/val + +############ 第四部分:类别名称【从0开始】 ############ +# # V1: 1_CholecSeg8k-13Type-1920x1080 +# names: +# 0: background +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# 6: 6 +# 7: 7 +# 8: 8 +# 9: 9 +# 10: 10 +# 11: 11 +# 12: 12 +# V2:2_AutoLaparo-10Type-1920x1080 +# names: +# 0: background +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# 6: 6 +# 7: 7 +# 8: 8 +# 9: 9 +# # V3:3_1_Endovis_2017-8Type-512x512 +# names: +# 0: background +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# 6: 6 +# 7: 7 +# # V4:3_2_Endovis_2018-8Type-512x512 +# names: +# 0: background +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# 6: 6 +# 7: 7 +# # V5:4_Dresden-11Type-512x512 +# names: +# 0: background +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# 6: 6 +# 7: 7 +# 8: 8 +# 9: 9 +# 10: 10 + +# # V6:5_LC_1_blood_verssel # TODO +# names: +# 0: 0 +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# # V7:5_LC_2_artery # TODO +# names: +# 0: 0 +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# 6: 6 +# 7: 7 +# 8: 8 +# 9: 9 +# 10: 10 +# 11: 11 +# 12: 12 +# 13: 13 +# # V8:5_LC_3_cystic_duct # TODO +# names: +# 0: 0 +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# 6: 6 +# 7: 7 +# 8: 8 +# 9: 9 +# # V9:5_LC_4_foreigner # TODO +# names: +# 0: 0 +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# # V10:5_LC_5_stop_bleed # TODO +# names: +# 0: 0 +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# 6: 6 +# 7: 7 +# 8: 8 +# 9: 9 +# 10: 10 +# 11: 11 +# # V11:6_CWK_1_yws # TODO +# names: +# 0: 0 +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# # V12:6_CWK_2_cfz # TODO +# names: +# 0: 0 +# 1: 1 +# 2: 2 +# 3: 3 +# 4: 4 +# 5: 5 +# # V13:5_TQY # TODO +# names: +# 0: 0 +# # V14:5_Haze_ori、6_Haze_AOD_Net、7_Haze_Baidu、8_Haze_Baidu_Plus # TODO +# names: +# 0: 0 +# 1: 1 +# 2: 2 +# 3: 3 + +# 当前默认:V3 3_1_Endovis_2017-8Type-512x512 +names: + 0: background + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + 6: 6 + 7: 7 + +############ 第五部分:最终上色 ############ +# # V6:5_LC_1_blood_verssel # TODO +# colors: +# 0: [255, 91, 0] +# 1: [255, 234, 0] +# 2: [167, 24, 233] +# 3: [52, 184, 178] +# 4: [255, 0, 0] +# 5: [0, 155, 33] +# # V7:5_LC_2_artery # TODO +# colors: +# 0: [255, 91, 0] +# 1: [255, 234, 0] +# 2: [255, 0, 0] +# 3: [0, 160, 233] +# 4: [0, 155, 33] +# 5: [52, 184, 178] +# 6: [167, 24, 233] +# 7: [255, 255, 255] +# 8: [117, 0, 0] +# 9: [72, 0, 255] +# 10: [85, 111, 181] +# 11: [0, 255, 255] +# 12: [42, 8, 66] +# 13: [66, 115, 82] +# # V8:5_LC_3_cystic_duct # TODO +# colors: +# 0: [255, 91, 0] +# 1: [255, 234, 0] +# 2: [255, 0, 0] +# 3: [167, 24, 233] +# 4: [0, 155, 33] +# 5: [0, 160, 233] +# 6: [72, 0, 255] +# 7: [52, 184, 178] +# 8: [255, 255, 255] +# 9: [117, 0, 0] +# # V9:5_LC_4_foreigner # TODO +# colors: +# 0: [255, 91, 0] +# 1: [255, 234, 0] +# 2: [255, 0, 0] +# 3: [0, 155, 33] +# 4: [52, 184, 178] +# 5: [167, 24, 233] +# # V10:5_LC_5_stop_bleed # TODO +# colors: +# 0: [255, 91, 0] +# 1: [181, 227, 14] +# 2: [160, 15, 95] +# 3: [85, 111, 181] +# 4: [52, 184, 178] +# 5: [255, 0, 0] +# 6: [255, 255, 255] +# 7: [255, 0, 255] +# 8: [167, 24, 233] +# 9: [255, 234, 0] +# 10: [0, 160, 233] +# 11: [113, 102, 140] +# # V11:6_CWK_1_yws # TODO +# colors: +# 0: [255, 91, 0] +# 1: [155, 132, 0] +# 2: [255, 234, 0] +# 3: [255, 0, 255] +# 4: [52, 184, 178] +# 5: [85, 111, 181] +# # # V12:6_CWK_2_cfz # TODO +# colors: +# 0: [255, 91, 0] +# 1: [155, 132, 0] +# 2: [255, 234, 0] +# 3: [52, 184, 178] +# 4: [255, 0, 255] +# 5: [85, 111, 181] +# # V13: 5_TQY # TODO +# colors: +# 0: [225, 182, 193] +# V14:5_Haze_ori、6_Haze_AOD_Net、7_Haze_Baidu、8_Haze_Baidu_Plus +colors: + 0: [167, 24, 233] + 1: [52, 184, 178] + 2: [255, 255, 255] + 3: [0, 160, 233] diff --git a/Seg_All_In_One_YoloModel/yolo12-seg.yaml b/Seg_All_In_One_YoloModel/yolo12-seg.yaml new file mode 100644 index 0000000..6d03a3e --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo12-seg.yaml @@ -0,0 +1,48 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +# YOLO12-seg instance segmentation model with P3/8 - P5/32 outputs +# Model docs: https://docs.ultralytics.com/models/yolo12 +# Task docs: https://docs.ultralytics.com/tasks/segment + +# Parameters +nc: 80 # number of classes +scales: # model compound scaling constants, i.e. 'model=yolo12n-seg.yaml' will call yolo12-seg.yaml with scale 'n' + # [depth, width, max_channels] + n: [0.50, 0.25, 1024] # summary: 294 layers, 2,855,056 parameters, 2,855,040 gradients, 10.6 GFLOPs + s: [0.50, 0.50, 1024] # summary: 294 layers, 9,938,592 parameters, 9,938,576 gradients, 35.7 GFLOPs + m: [0.50, 1.00, 512] # summary: 314 layers, 22,505,376 parameters, 22,505,360 gradients, 123.5 GFLOPs + l: [1.00, 1.00, 512] # summary: 510 layers, 28,756,992 parameters, 28,756,976 gradients, 145.1 GFLOPs + x: [1.00, 1.50, 512] # summary: 510 layers, 64,387,264 parameters, 64,387,248 gradients, 324.6 GFLOPs + +# YOLO12n backbone +backbone: + # [from, repeats, module, args] + - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 + - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 + - [-1, 2, C3k2, [256, False, 0.25]] + - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 + - [-1, 2, C3k2, [512, False, 0.25]] + - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 + - [-1, 4, A2C2f, [512, True, 4]] + - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 + - [-1, 4, A2C2f, [1024, True, 1]] # 8 + +# YOLO12n head +head: + - [-1, 1, nn.Upsample, [None, 2, "nearest"]] + - [[-1, 6], 1, Concat, [1]] # cat backbone P4 + - [-1, 2, A2C2f, [512, False, -1]] # 11 + + - [-1, 1, nn.Upsample, [None, 2, "nearest"]] + - [[-1, 4], 1, Concat, [1]] # cat backbone P3 + - [-1, 2, A2C2f, [256, False, -1]] # 14 + + - [-1, 1, Conv, [256, 3, 2]] + - [[-1, 11], 1, Concat, [1]] # cat head P4 + - [-1, 2, A2C2f, [512, False, -1]] # 17 + + - [-1, 1, Conv, [512, 3, 2]] + - [[-1, 8], 1, Concat, [1]] # cat head P5 + - [-1, 2, C3k2, [1024, True]] # 20 (P5/32-large) + + - [[14, 17, 20], 1, Segment, [nc, 32, 256]] # Detect(P3, P4, P5) diff --git a/Seg_All_In_One_YoloModel/yolo_config.py b/Seg_All_In_One_YoloModel/yolo_config.py new file mode 100644 index 0000000..d84b8db --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_config.py @@ -0,0 +1,123 @@ +import yaml, sys +import torch +from pathlib import Path + +# --- 工具函数:动态设备选择 (Dynamic Device Selection) --- +def get_auto_device(verbose = False): + """自动检测并返回最合适的设备""" + if torch.cuda.is_available(): + gpu_count = torch.cuda.device_count() + if gpu_count > 1: + # 如果有多个GPU,使用所有GPU + device_str = ",".join(str(i) for i in range(gpu_count)) + if verbose: + print(f"检测到 {gpu_count} 个可用的GPU。将使用所有GPU: {device_str}") + return device_str + else: + # 如果只有1个GPU + if verbose: + print("检测到 1 个可用的GPU。将使用 GPU: 0") + return '0' + else: + # 如果没有可用的GPU,使用CPU + if verbose: + print("未检测到可用的GPU。将使用CPU。") + return 'cpu' +# --- 1. 核心目录设置 (Core Directories) --- +HARDISK_DIR = Path.home() / "Desktop" / "Seg" / "Hardisk" # 硬盘根目录 +BASE_DIR = Path(__file__).parent.parent # 当前脚本所在目录上级 +DATASET_YAML_PATH = Path(__file__).parent / "dataset.yaml" # 数据集的 YAML 配置文件路径 +OUTPUTS_DIR = BASE_DIR / "DataSet_Public_outputs" # 输出结果目录 # 最终为:OUTPUTS_DIR / dataset_name +PREDICT_ALL_BEST_MODELS_DIR = BASE_DIR / 'BestMode_Predict_Results_DataSet_Public' # 所有最佳模型存放目录 + +TEST_IMAGE_DIR = None # 初始化 TEST_IMAGE_DIR # 测试文件地址 dataset.path / TODO "val" / "test" TODO +# try: +# 3. 读取并解析 YAML 文件 +with open(DATASET_YAML_PATH, 'r', encoding='utf-8') as f: + yaml_data = yaml.safe_load(f) +# 4. 从解析后的数据中获取 'path' 的值 +relative_path_from_yaml = yaml_data.get('path') +test_path_from_yaml = yaml_data.get('test') +dataset_name = Path(relative_path_from_yaml).name +if relative_path_from_yaml: + # 5. 【核心步骤】构建绝对路径 + # relative_path_from_yaml 是相对于 .yaml 文件本身的路径。 + # 所以,我们需要获取 .yaml 文件所在的目录,然后与这个相对路径拼接。 + yaml_file_directory = DATASET_YAML_PATH.parent + TEST_IMAGE_DIR = (DATASET_YAML_PATH.parent / relative_path_from_yaml / test_path_from_yaml).resolve() # 这里val 或 test在dataset.yaml中定义 + # 使用 .exists() 方法来检查路径是否存在 + if not TEST_IMAGE_DIR.exists(): + # 如果路径不存在,则执行这里的代码 + print(f"警告: 测试图片目录不存在: {TEST_IMAGE_DIR}") + sys.exit(1) + # 6. 获取OUTPUTS_DIR + if dataset_name and dataset_name not in {'.', '..'}: + dataset_name_ = dataset_name + "-Yolo" + # 设定输出路径 + OUTPUTS_DIR = OUTPUTS_DIR / dataset_name_ + # ../BestMode_Predict_Results_DataSet_Public/"dataset_name+"-Yolo"" # 需要用到 Tool_Yolo_Copy_Best_Model.sh + PREDICT_BEST_MODEL_DIR = PREDICT_ALL_BEST_MODELS_DIR / dataset_name_ # 最优模型保存位置 PREDICT_ALL_BEST_MODELS_DIR / dataset_name_ / weights / best.pt + else: + print(f"警告: 提取的dataset_name: '{dataset_name}' 无效(为空、'.' 或 '..')。") + sys.exit(1) +else: + print(f"警告: 在 '{DATASET_YAML_PATH}' 文件中没有找到 'path' 键。") + sys.exit(1) +# except FileNotFoundError: +# print(f"错误: YAML 配置文件未找到: '{DATASET_YAML_PATH}'") +# except Exception as e: +# print(f"读取或解析 YAML 文件时发生错误: {e}") + + +# --- 修改:模型、图像大小和批处理大小的集成配置 --- +# 将所有模型的配置(权重、图像大小、批处理大小)集中管理 +MODEL_CONFIGS = { + # YOLOv8 + 'YOLOv8n-seg': {'weights': 'yolov8n-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLOv8s-seg': {'weights': 'yolov8s-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLOv8m-seg': {'weights': 'yolov8m-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLOv8l-seg': {'weights': 'yolov8l-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,12.5GB + 'YOLOv8x-seg': {'weights': 'yolov8x-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,15.5GB # 示例:X模型使用1280分辨率 + # YOLOv9 + 'YOLOv9c-seg': {'weights': 'yolov9c-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,13GB + 'YOLOv9e-seg': {'weights': 'yolov9e-seg.pt', 'image_size': 640, 'batch_size': 8}, # 640,16,内存超了 # 示例:E模型(最大)使用1280分辨率和更小的batch + # YOLOv11 (假设) + 'YOLO11n-seg': {'weights': 'yolo11n-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLO11s-seg': {'weights': 'yolo11s-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLO11m-seg': {'weights': 'yolo11m-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLO11l-seg': {'weights': 'yolo11l-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,12.5GB + 'YOLO11x-seg': {'weights': 'yolo11x-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,19.5GB + # YOLOv12 (假设) + 'YOLO12-seg': {'weights': str(Path(__file__).parent / 'yolo12-seg.yaml'), 'image_size': 640, 'batch_size': 16}, # 640,16,3GB +} + +# --- 4. 训练超参数 (Training Hyperparameters) --- +DEVICE = get_auto_device(verbose=False) # 调用函数来设置设备 +EPOCHS = 300 # 训练轮次 # TODO +PATIENCE = 150 # 提前停止训练的轮数 +SAVE_PERIOD = 10 # 每隔多少轮保存一次模型 # TODO +# BATCH_SIZE 已在上面根据模型自动设置 +LEARNING_RATE = 0.01 # 初始学习率('SGD:0.01', 'Adam:1e-4', 'AdamW:1e-4', 'auto:0.01') +OPTIMIZER = 'auto' # 优化器 (TODO 'SGD', 'Adam', 'AdamW', 'auto' TODO) +WORKERS = 4 # 数据加载的工作线程数 + +# --- 5. 预测设置 (Prediction Settings) --- +SHOW_LABELS = True # 是否在预测结果上显示类别标签 +SHOW_CONF = True # 是否在预测结果上显示置信度和边界框 +SAVE_PREDICTIONS = True # 是否保存预测结果图像 + +# ============================================================================== +# --- 主执行块:只在直接运行时才打印配置信息 --- +# ============================================================================== +def show_config_summary(): + """打印所有配置摘要信息""" + print(f"设定输出路径为: '{str(OUTPUTS_DIR)}'") + print(f"最佳模型路径为: '{str(PREDICT_BEST_MODEL_DIR)}'") + # 再次调用 get_auto_device 并设置 verbose=True 来打印设备信息 + get_auto_device(verbose=True) + + +# 当这个脚本被直接执行时(python yolo_config.py),__name__ 的值是 '__main__' +# 当它被其他脚本import时,__name__ 的值是 'yolo_config' +if __name__ == '__main__': + show_config_summary() \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/yolo_predict.sh b/Seg_All_In_One_YoloModel/yolo_predict.sh new file mode 100644 index 0000000..28c8912 --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_predict.sh @@ -0,0 +1,167 @@ +#!/bin/bash + +# ================================================================= +# YOLO 模型批量并行预测脚本 +# ================================================================= +# - 此脚本会自动为每个模型架构查找其训练好的 'best.pt' +# - 使用 'echo "1" |' 来自动选择找到的第一个训练版本 +# - 在不同的指定GPU上并行执行预测任务 +# ================================================================= + +# --- 1. Conda 环境设置 --- +CONDA_BASE_PATH="/home/wkmgc/miniconda3" # <--- 在这里修改为您自己的 Conda 路径 +CONDA_ENV_NAME="${SEG_CONDA_ENV:-seg_smp}" # 可用 SEG_CONDA_ENV=SMP bash yolo_predict.sh 临时覆盖 +pt_name="best.pt" # <--- 在这里修改为您想使用的权重文件名,例如 "best.pt" 或 "epoch100.pt" +conf_threshold=0.2 # <--- [新增] 默认的置信度阈值 +heatmap_method="None" # <--- [!! 新增 !!] 默认不运行热度图 + +# 循环解析参数 +while [[ $# -gt 0 ]]; do + key="$1" + case $key in + --pt_name) + if [ -n "$2" ] && [[ "$2" != -* ]]; then + pt_name="$2" + shift # 移过 --pt_name + shift # 移过它的值 + else + echo "错误: --pt_name 参数需要一个值。" >&2 + exit 1 + fi + ;; + --conf) + if [ -n "$2" ] && [[ "$2" != -* ]]; then + conf_threshold="$2" + shift # 移过 --conf + shift # 移过它的值 + else + echo "错误: --conf 参数需要一个值。" >&2 + exit 1 + fi + ;; + --heatmap_method) + if [ -n "$2" ] && [[ "$2" != -* ]]; then + heatmap_method="$2" + shift # 移过 --heatmap_method + shift # 移过它的值 + else + echo "错误: --heatmap_method 参数需要一个值 (例如 'GradCAM' 或 'All')。" >&2 + exit 1 + fi + ;; + *) + # 移过未知参数,不报错 + shift + ;; + esac +done + +# 初始化并激活 Conda 环境 +if [ -f "${CONDA_BASE_PATH}/etc/profile.d/conda.sh" ]; then + source "${CONDA_BASE_PATH}/etc/profile.d/conda.sh" + conda activate "${CONDA_ENV_NAME}" + if [ $? -ne 0 ]; then + echo "错误: 激活 Conda 环境 '${CONDA_ENV_NAME}' 失败!" + exit 1 + fi + echo "Conda 环境 '${CONDA_ENV_NAME}' 已成功激活。" +else + echo "错误: 找不到 conda.sh 脚本。请检查您的 CONDA_BASE_PATH 设置是否正确。" + exit 1 +fi + +# --- 2. 模型与 GPU 配置 --- +# 此处的分组应与 train.sh 保持一致,以确保能正确找到模型并分配资源 +GPUS_GROUP_0="0" +GPUS_GROUP_1="1" +GPUS_GROUP_2="2" +GPUS_GROUP_3="3" +# TODO # +GPUS_GROUP_4="0" +GPUS_GROUP_5="1" +GPUS_GROUP_6="2" +GPUS_GROUP_7="3" + +# 从 yolo_config.py/train.sh 中选择的模型列表 +GROUP_0_MODELS=("YOLO11l-seg") +GROUP_1_MODELS=("YOLOv8n-seg" "YOLOv8m-seg") +GROUP_2_MODELS=("YOLO11n-seg" "YOLO11s-seg" "YOLO11m-seg") +GROUP_3_MODELS=("YOLOv9e-seg") +GROUP_4_MODELS=("YOLO11x-seg") +GROUP_5_MODELS=("YOLOv9c-seg" "YOLOv8s-seg") +GROUP_6_MODELS=("YOLOv8l-seg" "YOLO12-seg") +GROUP_7_MODELS=("YOLOv8x-seg") + +# 1. 从 config.py 中读取 PREDICT_BEST_MODEL_DIR 的值 +PREDICT_BEST_MODEL_DIR=$(python -c "from yolo_config import PREDICT_BEST_MODEL_DIR; print(PREDICT_BEST_MODEL_DIR)") +# 检查是否成功获取了 PREDICT_BEST_MODEL_DIR +if [ -z "$PREDICT_BEST_MODEL_DIR" ] || [ ! -e "$PREDICT_BEST_MODEL_DIR" ]; then + echo "PREDICT_BEST_MODEL_DIR: $PREDICT_BEST_MODEL_DIR" + echo "Error: Could not read PREDICT_BEST_MODEL_DIR from yolo_config.py. Exiting." + echo "Error 2: Or the directory specified by PREDICT_BEST_MODEL_DIR does not exist. Please create it first." + exit 1 +fi +# 2. 定义带有时间戳的日志目录名 +LOG_DIR_NAME="yolo_predict_logs_parallel_$(date +%Y-%m-%d_%H-%M-%S)" +# 3. 拼接成最终的完整路径 +LOG_DIR="$PREDICT_BEST_MODEL_DIR/$LOG_DIR_NAME" +mkdir -p "${LOG_DIR}" +echo "所有模型的预测日志将保存在 ./${LOG_DIR}/ 目录中。" +echo "----------------------------------------------------" + + +# --- 3. 预测执行函数 --- +# 定义一个函数来启动一组预测,以避免代码重复 +start_prediction_group() { + # 使用 nameref (引用) 来传递数组 + local -n models=$1 + local gpus=$2 + local group_name=$3 + + echo ">>> 准备启动 ${group_name} 的预测任务 (后台运行)..." + # 遍历指定组中的所有模型 + for model_key in "${models[@]}"; do + if [ "${heatmap_method}" == "None" ]; then + # --- 模式 1: 运行标准预测 (原有逻辑) --- + echo " -> 正在后台启动 [标准预测]: ${model_key} on GPUs: ${gpus}" + # [注意] 我为您添加了 --conf 参数,您原有的脚本 没有传递它 + echo "1" | CUDA_VISIBLE_DEVICES=${gpus} python yolo_predict_V2.py --model "${model_key}" --pt_name "${pt_name}" --conf "${conf_threshold}" > "${LOG_DIR}/${model_key}_predict.log" 2>&1 & + echo " - 模型 ${model_key} 的预测已在后台启动。日志文件: ${LOG_DIR}/${model_key}_predict.log" + else + # --- 模式 2: 运行热度图可视化 --- + echo " -> 正在后台启动 [热度图可视化]: ${model_key} on GPUs: ${gpus} (Method: ${heatmap_method})" + # [注意] 我们使用 yolo_predict_visualize_nn.py 并传递新参数 + echo "1" | CUDA_VISIBLE_DEVICES=${gpus} python yolo_predict_visualize_nn.py --model "${model_key}" --target_layers "default" --cam_method "${heatmap_method}" --pt_name "${pt_name}" > "${LOG_DIR}/${model_key}_heatmap.log" 2>&1 & + echo " - 模型 ${model_key} 的热度图已在后台启动。日志文件: ${LOG_DIR}/${model_key}_heatmap.log" + fi + echo " - 等待 5 秒,确保 GPU 资源稳定分配..." + sleep 5 + done + echo ">>> ${group_name} 的所有模型均已启动。" + echo "----------------------------------------------------" +} + +# --- 4. 依次启动所有预测任务 --- +# 脚本将快速地按顺序启动每一组任务到后台 +start_prediction_group GROUP_0_MODELS "${GPUS_GROUP_0}" "第零组" +start_prediction_group GROUP_1_MODELS "${GPUS_GROUP_1}" "第一组" +start_prediction_group GROUP_2_MODELS "${GPUS_GROUP_2}" "第二组" +start_prediction_group GROUP_3_MODELS "${GPUS_GROUP_3}" "第三组" +start_prediction_group GROUP_4_MODELS "${GPUS_GROUP_4}" "第四组" +start_prediction_group GROUP_5_MODELS "${GPUS_GROUP_5}" "第五组" +start_prediction_group GROUP_6_MODELS "${GPUS_GROUP_6}" "第六组" +start_prediction_group GROUP_7_MODELS "${GPUS_GROUP_7}" "第七组" + + +# --- 5. 等待所有后台任务完成 --- +echo "" +echo "--- 所有模型均已在后台启动。现在等待所有预测任务完成... ---" +# 'wait' 命令会暂停脚本,直到所有由此脚本启动的后台子进程全部执行完毕 +wait +echo "--- 所有后台预测任务已全部完成! ---" + + +# --- 6. 退出脚本 --- +echo "预测流程结束。" +conda deactivate +echo "已取消激活 Conda 环境。" diff --git a/Seg_All_In_One_YoloModel/yolo_predict_V1.py b/Seg_All_In_One_YoloModel/yolo_predict_V1.py new file mode 100644 index 0000000..b20ecde --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_predict_V1.py @@ -0,0 +1,238 @@ +import logging, sys, argparse, yaml +from pathlib import Path +from ultralytics import YOLO +import yolo_config as config +config.show_config_summary() # 显示配置信息 +from typing import List +import cv2 +import numpy as np +# +----------------------------------------------------------------------------+ +# ../BestMode_Predict_Results_DataSet_Public/ +# └── YOLOv8n-seg_2025-09-20_10-00-00/ # The folder for the trained model you selected +# ├── weights/ +# │ └── best.pt +# │ +# ├── prediction/ # Visualized results (image with colored mask overlay) +# │ ├── image1.jpg +# │ └── ... +# │ +# └── prediction_masks_combined/ # <-- Your new unique folder for combined masks +# ├── image1.png # Grayscale mask, original size, pixel values are class IDs +# ├── image2.png # Grayscale mask +# └── ... +# +----------------------------------------------------------------------------+ + +# --- 日志设置 --- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler()] +) + +# --- 使用带 model_key 过滤的版本 --- +def find_trained_models(outputs_dir: Path, model_key: str) -> List[str]: + """ + 扫描输出目录,查找特定基础模型的所有有效的、已完成的训练项目。 + """ + trained_models = [] + if not outputs_dir.is_dir(): + logging.warning(f"输出目录不存在: {outputs_dir}") + return [] + + for project_folder in outputs_dir.iterdir(): + if project_folder.is_dir() and project_folder.name.startswith(model_key + '_'): + if (project_folder / 'weights' / 'best.pt').exists(): + trained_models.append(project_folder.name) + + trained_models.sort() + return trained_models + +# 老版predict函数,只有默认输出 +# def predict_old(model_path: str, source: str, project_name: str): +# """ +# 使用指定的模型对图像源进行预测。 +# """ +# if not Path(source).exists(): +# logging.error(f"错误:预测源路径不存在: {source}") +# return + +# try: +# logging.info(f"正在加载模型: {model_path}") +# model = YOLO(model_path) +# logging.info("模型加载成功。") + +# logging.info(f"正在对源进行预测: {source}") +# results = model.predict( +# source=source, +# save=config.SAVE_PREDICTIONS, +# show_labels=config.SHOW_LABELS, +# show_conf=config.SHOW_CONF, +# project=project_name, +# name="prediction", +# exist_ok=True, +# retina_masks=True, # <<< ADDED: 保存与原图一样大的分割图 +# ) + +# final_save_dir = Path(project_name) / "prediction" +# logging.info(f"预测完成。结果保存在: {final_save_dir}") + +# except Exception as e: +# logging.error(f"预测过程中发生错误: {e}") + +def predict(model_path: str, source: str, project_name: str): + """ + 使用指定模型进行预测,并将所有类别的掩码合并到一个单通道灰度图中。 + 在该图中,每个像素的值对应其类别ID (0=背景, 1=类别1, 2=类别2, ...)。 + """ + # --- 1. 加载类别名称 (用于日志记录) --- + try: + with open(config.DATASET_YAML_PATH, 'r', encoding='utf-8') as f: + class_names = yaml.safe_load(f)['names'] + logging.info(f"成功从 {config.DATASET_YAML_PATH} 加载类别名称: {class_names}") + except Exception as e: + logging.error(f"加载或解析 dataset.yaml 失败: {e}") + return + + if not Path(source).exists(): + logging.error(f"错误:预测源路径不存在: {source}") + return + + try: + # --- 2. 加载模型并执行预测 --- + logging.info(f"正在加载模型: {model_path}") + model = YOLO(model_path) + logging.info("模型加载成功。") + + logging.info(f"正在对源进行预测: {source}") + results = model.predict( + source=source, + stream=True, # 使用流式处理以节省内存,但是会减速 + save=config.SAVE_PREDICTIONS, + show_labels=config.SHOW_LABELS, + show_conf=config.SHOW_CONF, + project=project_name, + name="prediction", + exist_ok=True, + retina_masks=True, + conf=0.1 + ) + + # --- 3. 创建新的输出目录并处理结果 --- + mask_save_dir = Path(project_name) / "predicted_raw_masks" + mask_save_dir.mkdir(parents=True, exist_ok=True) + + logging.info(f"预测完成。正在处理结果并保存组合掩码图于: {mask_save_dir}") + + for result in results: + original_image_path = Path(result.path) + logging.info(f"正在处理图片: {original_image_path.name}") + + h, w = result.orig_shape + # 创建一个空白画布,用于存储所有类别的组合掩码。0 代表背景。 + combined_mask = np.zeros((h, w), dtype=np.uint8) + + if result.masks is None: + logging.warning(f" -> 在图片 {original_image_path.name} 中未检测到任何物体,将创建一张全黑的掩码图。") + # 如果没有检测到物体,直接保存空白掩码图并继续处理下一张图片 + mask_filename = mask_save_dir / f"{original_image_path.stem}.png" + cv2.imwrite(str(mask_filename), combined_mask) + continue + + # --- 核心:创建组合掩码 --- + masks_data = result.masks.data + class_ids = result.boxes.cls.int().cpu().numpy() + + if len(masks_data) != len(class_ids): + logging.error(f" -> 掩码和类别ID数量不匹配,跳过图片 {original_image_path.name}") + continue + + # YOLO结果通常按置信度从高到低排序。 + # 我们反向迭代(从低置信度到高置信度),以确保在掩码重叠区域, + # 置信度更高的物体类别能够覆盖置信度较低的。 + for i in reversed(range(len(masks_data))): + instance_mask = masks_data[i].cpu().numpy().astype(bool) + class_id = class_ids[i] + + # 将掩码区域的像素值设置为其对应的类别ID。 + # 我们假设类别ID 0 是背景,所以即使模型预测了ID为0的物体,它也会被视为背景。 + if class_id != 0: + combined_mask[instance_mask] = class_id + + # --- 保存最终的组合掩码图 --- + mask_filename = mask_save_dir / f"{original_image_path.stem}.png" + cv2.imwrite(str(mask_filename), combined_mask) + + logging.info(f"所有组合掩码图已成功保存。") + + except Exception as e: + logging.error(f"预测或手动保存过程中发生错误: {e}", exc_info=True) + + +if __name__ == "__main__": + # 1. 创建解析器,现在只需要 --model 和 --source + parser = argparse.ArgumentParser(description="使用已训练的YOLO模型进行预测。") + + parser.add_argument( + "--model", + type=str, + required=True, + choices=list(config.MODEL_CONFIGS.keys()), + help="选择一个基础模型类型,以筛选其训练历史。" + ) + parser.add_argument( + "--source", + type=str, + default=str(config.TEST_IMAGE_DIR), + help="图片或图片文件夹的路径。" + ) + args = parser.parse_args() + + # 2. 根据 --model 参数查找对应的训练历史 + available_runs = find_trained_models(config.PREDICT_BEST_MODEL_DIR, args.model) + + run_to_use = None + + # 3. 根据找到的结果数量,决定下一步操作 + if not available_runs: + logging.error(f"错误:在 {config.PREDICT_BEST_MODEL_DIR} 中没有找到任何关于模型 '{args.model}' 的有效训练记录。") + sys.exit(1) + + elif len(available_runs) == 1: + # 如果只有一个结果,自动选择 + run_to_use = available_runs[0] + logging.info(f"只找到一个训练版本,已自动选择: {run_to_use}") + + else: + # 如果有多个结果,进入交互式选择模式 + print(f"\n为模型 '{args.model}' 找到多个训练版本:") + for i, run_name in enumerate(available_runs, 1): + print(f" [{i}] {run_name}") + + while True: + try: + choice = input(f"请输入您想使用的版本序号 (1-{len(available_runs)}): ") + choice_index = int(choice) + if 1 <= choice_index <= len(available_runs): + run_to_use = available_runs[choice_index - 1] + break + else: + print("错误:输入无效,请输入列表中的序号。") + except ValueError: + print("错误:请输入一个数字。") + except (KeyboardInterrupt, EOFError): + print("\n操作已取消。") + sys.exit(0) + + # 4. 使用最终确定的 run_to_use 来执行预测 + if run_to_use: + model_to_use = config.PREDICT_BEST_MODEL_DIR / run_to_use / 'weights' / 'best.pt' + project_to_save_in = str(config.PREDICT_BEST_MODEL_DIR / run_to_use) + + if not model_to_use.exists(): + logging.error(f"严重错误:找不到权重文件 {model_to_use}。") + else: + predict( + model_path=str(model_to_use), + source=args.source, + project_name=project_to_save_in + ) \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/yolo_predict_V2.py b/Seg_All_In_One_YoloModel/yolo_predict_V2.py new file mode 100644 index 0000000..c16953a --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_predict_V2.py @@ -0,0 +1,538 @@ +import logging, sys, argparse, yaml +from pathlib import Path +from ultralytics import YOLO +import yolo_config as config +config.show_config_summary() # 显示配置信息 +from typing import List +import cv2 +import numpy as np +# +----------------------------------------------------------------------------+ +# ../BestMode_Predict_Results_DataSet_Public/ +# └── YOLOv8n-seg_2025-09-20_10-00-00/ # The folder for the trained model you selected +# ├── weights/ +# │ └── best.pt +# │ +# ├── prediction/ # Visualized results (image with colored mask overlay) +# │ ├── image1.jpg +# │ └── ... +# │ +# └── prediction_masks_combined/ # <-- Your new unique folder for combined masks +# ├── image1.png # Grayscale mask, original size, pixel values are class IDs +# ├── image2.png # Grayscale mask +# └── ... +# +----------------------------------------------------------------------------+ + +# --- 日志设置 --- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler()] +) + +# --- 使用带 model_key 过滤的版本 --- +def find_trained_models(outputs_dir: Path, model_key: str, pt_name: str) -> List[str]: + """ + 扫描输出目录,查找特定基础模型的所有有效的、已完成的训练项目。 + """ + trained_models = [] + if not outputs_dir.is_dir(): + logging.warning(f"输出目录不存在: {outputs_dir}") + return [] + + for project_folder in outputs_dir.iterdir(): + if project_folder.is_dir() and project_folder.name.startswith(model_key + '_'): + if (project_folder / 'weights' / pt_name).exists(): + trained_models.append(project_folder.name) + + trained_models.sort() + return trained_models + +# 老版predict函数,只有默认输出 +# def predict_old(model_path: str, source: str, project_name: str): +# """ +# 使用指定模型进行预测。 +# 1. 保存原始灰度掩码 (predicted_raw_masks),像素值为类别ID。 +# 2. 保存彩色可视化掩码 (predicted_color_masks),根据 color_map.yaml 上色。 +# """ + +# # --- 1. 加载类别名称 (用于日志记录和确定类别数量) --- +# try: +# with open(config.DATASET_YAML_PATH, 'r', encoding='utf-8') as f: +# class_names = yaml.safe_load(f)['names'] +# logging.info(f"成功从 {config.DATASET_YAML_PATH} 加载类别名称: {class_names}") +# num_classes = len(class_names) +# logging.info(f"检测到 {num_classes} 个类别 (ID 0 到 {num_classes-1})。") +# except Exception as e: +# logging.error(f"加载或解析 dataset.yaml 失败: {e}") +# return + +# # --- [新增] 2. 加载颜色查找表 (LUT) --- +# color_lut = np.zeros((num_classes, 3), dtype=np.uint8) # 默认全黑 +# color_map_path = 'color_map.yaml' + +# try: +# with open(color_map_path, 'r', encoding='utf-8') as f: +# color_data = yaml.safe_load(f)['colors'] +# logging.info(f"正在从 {color_map_path} 加载颜色...") + +# for class_id, bgr_value in color_data.items(): +# if 0 <= class_id < num_classes: +# color_lut[class_id] = bgr_value +# else: +# logging.warning(f"color_map.yaml 中的类别ID {class_id} 超出范围 (0-{num_classes-1}),已忽略。") +# logging.info("颜色查找表 (LUT) 创建成功。") + +# except FileNotFoundError: +# logging.error(f"错误:未找到 {color_map_path}。") +# logging.warning("将使用随机颜色作为备用方案(背景除外)。") +# # 创建随机颜色作为备用 +# for i in range(1, num_classes): # 保持 0 (背景) 为黑色 [0,0,0] +# color_lut[i] = np.random.randint(0, 255, 3, dtype=np.uint8) +# except Exception as e: +# logging.error(f"加载或解析 {color_map_path} 失败: {e}", exc_info=True) +# return + +# if not Path(source).exists(): +# logging.error(f"错误:预测源路径不存在: {source}") +# return + +# try: +# # --- 3. 加载模型并执行预测 --- +# logging.info(f"正在加载模型: {model_path}") +# model = YOLO(model_path) +# logging.info("模型加载成功。") + +# logging.info(f"正在对源进行预测: {source}") +# results = model.predict( +# source=source, +# stream=True, +# save=config.SAVE_PREDICTIONS, +# show_labels=config.SHOW_LABELS, +# show_conf=config.SHOW_CONF, +# project=project_name, +# name="prediction", +# exist_ok=True, +# retina_masks=True, +# conf=0.1 +# ) + +# # --- 4. 创建新的输出目录并处理结果 --- +# # [修改] 定义两个保存目录 +# raw_mask_save_dir = Path(project_name) / "predicted_raw_masks" +# color_mask_save_dir = Path(project_name) / "predicted_color_masks" # [新增] 彩色图目录 + +# raw_mask_save_dir.mkdir(parents=True, exist_ok=True) +# color_mask_save_dir.mkdir(parents=True, exist_ok=True) # [新增] + +# # [修改] 更新日志信息 +# logging.info(f"预测完成。正在处理结果...") +# logging.info(f" -> 原始灰度掩码将保存于: {raw_mask_save_dir}") +# logging.info(f" -> 彩色可视化掩码将保存于: {color_mask_save_dir}") + +# for result in results: +# original_image_path = Path(result.path) +# logging.info(f"正在处理图片: {original_image_path.name}") + +# h, w = result.orig_shape +# # 创建一个空白画布,用于存储所有类别的组合掩码。0 代表背景。 +# combined_mask = np.zeros((h, w), dtype=np.uint8) + +# if result.masks is None: +# logging.warning(f" -> 在图片 {original_image_path.name} 中未检测到任何物体,将创建一张全黑的掩码图。") + +# # --- 保存原始灰度掩码 (全黑) --- +# raw_mask_filename = raw_mask_save_dir / f"{original_image_path.stem}.png" +# cv2.imwrite(str(raw_mask_filename), combined_mask) + +# # --- [新增] 保存彩色掩码 (全黑) --- +# # 即使是全黑的,也应用LUT,结果还是全黑 +# colorized_mask = color_lut[combined_mask] # (H, W, 3) +# color_mask_filename = color_mask_save_dir / f"{original_image_path.stem}.png" +# cv2.imwrite(str(color_mask_filename), colorized_mask) + +# continue # 继续处理下一张图片 + +# # --- 核心:创建组合掩码 --- +# masks_data = result.masks.data +# class_ids = result.boxes.cls.int().cpu().numpy() + +# if len(masks_data) != len(class_ids): +# logging.error(f" -> 掩码和类别ID数量不匹配,跳过图片 {original_image_path.name}") +# continue + +# # (核心逻辑保持不变) +# # 反向迭代,确保高置信度覆盖低置信度 +# for i in reversed(range(len(masks_data))): +# instance_mask = masks_data[i].cpu().numpy().astype(bool) +# class_id = class_ids[i] + +# if class_id != 0: # 假设 0 是背景 +# combined_mask[instance_mask] = class_id + +# # --- 保存 1: 最终的组合掩码图 (灰度) --- +# raw_mask_filename = raw_mask_save_dir / f"{original_image_path.stem}.png" +# cv2.imwrite(str(raw_mask_filename), combined_mask) + +# # --- [新增] 保存 2: 最终的彩色掩码图 (RGB) --- +# # 这是最高效的上色方法: +# # color_lut (14, 3) +# # combined_mask (H, W),值 0-13 +# # NumPy 会自动将 (H, W) 中的每个值作为索引去 color_lut 中取 (B,G,R) 元组 +# colorized_mask = color_lut[combined_mask] # 结果维度 (H, W, 3) + +# color_mask_filename = color_mask_save_dir / f"{original_image_path.stem}.png" +# cv2.imwrite(str(color_mask_filename), colorized_mask) + +# logging.info(f"所有掩码图已成功保存。") + +# except Exception as e: +# logging.error(f"预测或手动保存过程中发生错误: {e}", exc_info=True) + +def predict(model_path: str, source: str, project_name: str, pt_name: str, conf_threshold: float): + """ + 使用指定模型进行预测。 + 1. 保存原始灰度掩码 (predicted_raw_masks),像素值为类别ID。 + 2. 保存彩色可视化掩码 (predicted_color_masks)。 + 3. 保存三合一对比图 (predicted_comparison) [Original | Ground Truth | Prediction]。 + """ + # + pt_name_raw = pt_name.replace('.pt','') # pt_name去掉.pt后缀,方便文件命名使用 + # [新增] 标志,用于决定是否将(类别0..N-1) 偏移到 (1..N),为 "真背景" 腾出 0 + perform_id_shift = False + + # --- 1. 加载类别名称和颜色 --- + try: + with open(config.DATASET_YAML_PATH, 'r', encoding='utf-8') as f: + yaml_data = yaml.safe_load(f) + class_names = yaml_data.get('names') + color_data_from_dataset = yaml_data.get('colors') + + if not class_names: + logging.error(f"在 {config.DATASET_YAML_PATH} 中未找到 'names' 键。") + return + + logging.info(f"成功从 {config.DATASET_YAML_PATH} 加载类别名称: {class_names}") + num_classes = len(class_names) + logging.info(f"检测到 {num_classes} 个类别 (ID 0 到 {num_classes-1})。") + + except Exception as e: + logging.error(f"加载或解析 dataset.yaml 失败: {e}") + return + + # --- 2. 创建颜色查找表 (LUT) --- + # [调整] 先创建一个临时的 (N) 维 LUT + temp_lut = np.zeros((num_classes, 3), dtype=np.uint8) + + if color_data_from_dataset: + logging.info(f"正在从 {config.DATASET_YAML_PATH} 加载颜色...") + try: + for class_id, rgb_value in color_data_from_dataset.items(): + if 0 <= class_id < num_classes: + temp_lut[class_id] = rgb_value[::-1] # BGR + logging.info("临时颜色查找表 (LUT) 已成功从 dataset.yaml 创建。") + except Exception as e: + logging.error(f"解析 'colors' 键失败: {e}。将尝试备用方案。") + color_data_from_dataset = None # 强制使用备用方案 + + if not color_data_from_dataset: + # 备用方案 1: 尝试 color_map.yaml + color_map_path = 'color_map.yaml' + logging.warning(f"未从 dataset.yaml 加载颜色,正在尝试备用方案: {color_map_path}") + try: + with open(color_map_path, 'r', encoding='utf-8') as f: + color_data = yaml.safe_load(f)['colors'] + for class_id, bgr_value in color_data.items(): + if 0 <= class_id < num_classes: + color_lut[class_id] = bgr_value + logging.info(f"颜色查找表 (LUT) 已成功从 {color_map_path} 创建。") + except Exception: + # 备用方案 2: 随机颜色 + logging.warning(f"{color_map_path} 未找到或解析失败。将使用随机颜色。") + for i in range(1, num_classes): # 保持 0 (背景) 为黑色 [0,0,0] + color_lut[i] = np.random.randint(0, 255, 3, dtype=np.uint8) + + # --- [新增] 核心条件判断 --- + # 检查 类别0 (索引0) 的颜色是否为 BGR(0,0,0) + if np.any(temp_lut[0] != [0, 0, 0]): + # 颜色不是黑色,执行 "ID+1" 偏移 + logging.info("检测到 类别0 的颜色不是黑色。将执行 ID+1 偏移,使 0 成为专用背景。") + perform_id_shift = True + # [调整] 创建一个 (N+1) 维的最终 LUT + color_lut = np.zeros((num_classes + 1, 3), dtype=np.uint8) + # 将原 0..N-1 的颜色 复制到 1..N + color_lut[1:] = temp_lut + # 索引 0 保持为 [0, 0, 0] (真背景) + else: + # 颜色是黑色,不执行偏移 + logging.info("检测到 类别0 的颜色是黑色。将 类别0 视为背景。") + perform_id_shift = False + color_lut = temp_lut # [调整] 使用 (N) 维的原始 LUT + + if not Path(source).exists(): + logging.error(f"错误:预测源路径不存在: {source}") + return + + try: + # --- 3. 加载模型并执行预测 --- + logging.info(f"正在加载模型: {model_path}") + model = YOLO(model_path) + logging.info("模型加载成功。") + + logging.info(f"正在对源进行预测: {source}") + results = model.predict( + source=source, + stream=True, + save=False, # <-- 关闭保存,之后手动处理 + show_labels=config.SHOW_LABELS, + show_conf=config.SHOW_CONF, + # project=project_name, # <-- 不再需要,我们手动处理 + # name="prediction", # <-- 不再需要,我们手动处理 + exist_ok=True, + retina_masks=True, + conf=conf_threshold # TODO + ) + + # --- 4. [扩展] 创建所有输出目录 --- + yolo_prediction_dir = Path(project_name) / "prediction" + raw_mask_save_dir = Path(project_name) / "predicted_raw_masks" + color_mask_save_dir = Path(project_name) / "predicted_color_masks" + comparison_save_dir = Path(project_name) / "predicted_comparison" # [新增] + + logging.info(f"预测完成。正在处理结果...") + logging.info(f" -> Yolo预测结果将保存于: {yolo_prediction_dir}") + logging.info(f" -> 原始灰度掩码将保存于: {raw_mask_save_dir}") + logging.info(f" -> 彩色预测掩码将保存于: {color_mask_save_dir}") + logging.info(f" -> 三合一对比图将保存于: {comparison_save_dir}") # [新增] + + yolo_prediction_dir.mkdir(parents=True, exist_ok=True) + raw_mask_save_dir.mkdir(parents=True, exist_ok=True) + color_mask_save_dir.mkdir(parents=True, exist_ok=True) + comparison_save_dir.mkdir(parents=True, exist_ok=True) # [新增] + + # --- 辅助函数:在图像上添加标签 --- + def add_label_to_image(img, label): + """在图像左上角添加一个带背景的标签""" + img_with_label = img.copy() + h, w = img_with_label.shape[:2] + # 动态调整字体大小和粗细 + font_scale = max(0.8, w // 1000) + thickness = max(1, w // 500) + + (text_w, text_h), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness) + + # 绘制黑色背景矩形 + cv2.rectangle(img_with_label, (0, 0), (text_w + 20, text_h + 30), (0, 0, 0), -1) + # 绘制白色文字 + cv2.putText(img_with_label, label, (10, text_h + 15), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), thickness, cv2.LINE_AA) + return img_with_label + + # --- 5. [扩展] 处理结果循环 --- + for result in results: + original_image_path = Path(result.path) + logging.info(f"正在处理图片: {original_image_path.name}") + + h, w = result.orig_shape + + # --- 5a. 保存 Yolo-predict 本身结果 --- + if config.SAVE_PREDICTIONS == True: + # a. 获取原始图片的文件名和后缀 + # result.path 是原始图片的完整路径 + original_path = Path(result.path) + image_stem = original_path.stem # 这是 "图片名" + image_suffix = original_path.suffix # 这是 ".后缀" (例如 .jpg) + # b. 构建你的新文件名 + # 格式: 图片名_{pt_name_raw}.后缀 + new_filename = f"{image_stem}_{pt_name_raw}{image_suffix}" + # c. 构建完整的保存路径 + save_path = yolo_prediction_dir / new_filename + # d. 获取绘制了检测框的图像 (NumPy 数组) + # result.plot() 会自动使用你在 predict 中设置的 show_labels, show_conf 等参数 + annotated_image = result.plot() + # e. 使用 OpenCV (cv2) 保存图像 + cv2.imwrite(str(save_path), annotated_image) + print(f"已保存: {save_path}") + + # --- 5b. [新增] 查找并加载 Ground Truth 掩码 --- + gt_mask = None + try: + # 假设的路径结构: .../DataSet_Public/5_.../images/val/image.png + # 我们要找: .../DataSet_Public/5_.../labels_GT/val/image.png + image_filename = original_image_path.name + image_parent_dir_name = original_image_path.parent.name # 'val' or 'train' + images_dir = original_image_path.parent.parent # '.../images' + dataset_root_dir = images_dir.parent # '.../5_My_Gastric_2025_10_29' + + gt_path = dataset_root_dir / "labels_GT" / image_parent_dir_name / image_filename + + if not gt_path.exists(): + logging.warning(f" -> 未找到 Ground Truth 文件: {gt_path}") + else: + gt_mask = cv2.imread(str(gt_path), cv2.IMREAD_GRAYSCALE) + if gt_mask is None: + logging.warning(f" -> 读取 Ground Truth 文件失败: {gt_path}") + elif gt_mask.shape != (h, w): + logging.warning(f" -> GT 掩码形状 {gt_mask.shape} 与图像形状 {(h, w)} 不匹配。正在调整GT大小...") + gt_mask = cv2.resize(gt_mask, (w, h), interpolation=cv2.INTER_NEAREST) + except Exception as e: + logging.error(f" -> 查找或加载GT文件时出错: {e}", exc_info=True) + gt_mask = None + + # --- 5c. 创建预测掩码 (与之前相同) --- + combined_mask = np.zeros((h, w), dtype=np.uint8) + + if result.masks is None: + logging.warning(f" -> 在图片 {original_image_path.name} 中未检测到任何物体。") + else: + masks_data = result.masks.data + class_ids = result.boxes.cls.int().cpu().numpy() + + if len(masks_data) != len(class_ids): + logging.error(f" -> 掩码和类别ID数量不匹配,跳过图片 {original_image_path.name}") + continue + + for i in reversed(range(len(masks_data))): + instance_mask = masks_data[i].cpu().numpy().astype(bool) + class_id = class_ids[i] + # [调整] 根据标志执行不同逻辑 + if perform_id_shift: + # 偏移模式:将所有 ID (包括0) 都 +1 + combined_mask[instance_mask] = class_id + 1 + else: + # 原始模式:忽略 ID 0 + if class_id != 0: + combined_mask[instance_mask] = class_id + + # --- 5d. [扩展] 保存所有三种输出 --- + + # --- 保存 1: 原始灰度掩码 (预测) --- + raw_mask_filename = raw_mask_save_dir / f"{original_image_path.stem}_{pt_name_raw}.png" + cv2.imwrite(str(raw_mask_filename), combined_mask) + + # --- 保存 2: 彩色预测掩码 --- + colorized_pred_mask = color_lut[combined_mask] # (H, W, 3) + color_mask_filename = color_mask_save_dir / f"{original_image_path.stem}_{pt_name_raw}.png" + cv2.imwrite(str(color_mask_filename), colorized_pred_mask) + + # --- [新增] 保存 3: 三合一对比图 --- + try: + # 1. 获取原始图 + original_image_bgr = result.orig_img # YOLO 结果中自带 BGR 格式原图 + + # 2. 获取彩色 GT 图 + if gt_mask is not None: + colorized_gt_mask = color_lut[gt_mask] + else: + # 如果GT不存在,创建一个黑色占位图 + logging.warning(f" -> 在对比图中将使用黑色图像作为 Ground Truth 占位符。") + colorized_gt_mask = np.zeros_like(original_image_bgr) # (H, W, 3) + + # 3. 获取彩色预测图 (已在上面生成: colorized_pred_mask) + + # 确保所有图像形状一致 (理论上应该一致) + if not (original_image_bgr.shape == colorized_gt_mask.shape == colorized_pred_mask.shape): + logging.error(f" -> 形状不匹配! 原图: {original_image_bgr.shape}, GT: {colorized_gt_mask.shape}, 预测: {colorized_pred_mask.shape}。跳过对比图。") + continue + + # 为每张图添加标签 + img_labeled = add_label_to_image(original_image_bgr, "Original") + gt_labeled = add_label_to_image(colorized_gt_mask, "Ground Truth") + pred_labeled = add_label_to_image(colorized_pred_mask, "Prediction") + + # 水平拼接 + comparison_image = cv2.hconcat([img_labeled, gt_labeled, pred_labeled]) + + # 保存 (使用 .jpg 格式以节省空间) + comparison_filename = comparison_save_dir / f"{original_image_path.stem}_{pt_name_raw}.jpg" + cv2.imwrite(str(comparison_filename), comparison_image) + + except Exception as e: + logging.error(f" -> 创建或保存对比图失败: {e}", exc_info=True) + + + logging.info(f"所有掩码图和对比图已成功保存。") + + except Exception as e: + logging.error(f"预测或手动保存过程中发生错误: {e}", exc_info=True) + +if __name__ == "__main__": + # 1. 创建解析器,现在只需要 --model 和 --source + parser = argparse.ArgumentParser(description="使用已训练的YOLO模型进行预测。") + + parser.add_argument( + "--model", + type=str, + required=True, + choices=list(config.MODEL_CONFIGS.keys()), + help="选择一个基础模型类型,以筛选其训练历史。" + ) + parser.add_argument( + "--source", + type=str, + default=str(config.TEST_IMAGE_DIR), + help="图片或图片文件夹的路径。" + ) + parser.add_argument( + "--pt_name", + type=str, + default=str("best.pt"), + help="图片或图片文件夹的路径。" + ) + parser.add_argument( + "--conf", + type=float, # 1. 类型应为浮点数 + default=0.2, + help="设置预测的置信度阈值 (例如: 0.25)" # 2. 补充 help 信息 + ) + args = parser.parse_args() + + # 2. 根据 --model 参数查找对应的训练历史 + available_runs = find_trained_models(config.PREDICT_BEST_MODEL_DIR, args.model, args.pt_name) + + run_to_use = None + + # 3. 根据找到的结果数量,决定下一步操作 + if not available_runs: + logging.error(f"错误:在 {config.PREDICT_BEST_MODEL_DIR} 中没有找到任何关于模型 '{args.model}' 的有效训练记录。") + sys.exit(1) + + elif len(available_runs) == 1: + # 如果只有一个结果,自动选择 + run_to_use = available_runs[0] + logging.info(f"只找到一个训练版本,已自动选择: {run_to_use}") + + else: + # 如果有多个结果,进入交互式选择模式 + print(f"\n为模型 '{args.model}' 找到多个训练版本:") + for i, run_name in enumerate(available_runs, 1): + print(f" [{i}] {run_name}") + + while True: + try: + choice = input(f"请输入您想使用的版本序号 (1-{len(available_runs)}): ") + choice_index = int(choice) + if 1 <= choice_index <= len(available_runs): + run_to_use = available_runs[choice_index - 1] + break + else: + print("错误:输入无效,请输入列表中的序号。") + except ValueError: + print("错误:请输入一个数字。") + except (KeyboardInterrupt, EOFError): + print("\n操作已取消。") + sys.exit(0) + + # 4. 使用最终确定的 run_to_use 来执行预测 + if run_to_use: + model_to_use = config.PREDICT_BEST_MODEL_DIR / run_to_use / 'weights' / args.pt_name + project_to_save_in = str(config.PREDICT_BEST_MODEL_DIR / run_to_use) + + if not model_to_use.exists(): + logging.error(f"严重错误:找不到权重文件 {model_to_use}。") + else: + predict( + model_path=str(model_to_use), + source=args.source, + project_name=project_to_save_in, + pt_name = args.pt_name, + conf_threshold=args.conf + ) \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/yolo_predict_V2_compare_all.py b/Seg_All_In_One_YoloModel/yolo_predict_V2_compare_all.py new file mode 100644 index 0000000..779c0ca --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_predict_V2_compare_all.py @@ -0,0 +1,383 @@ +import cv2 +import numpy as np +import yaml +import logging +import argparse +import sys +from pathlib import Path +from tqdm import tqdm + +# --- 日志设置 --- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler()] +) + +try: + import yolo_config as config + logging.info(f"成功加载配置 yolo_config.py") + logging.info(f" -> 最佳模型目录: {config.PREDICT_BEST_MODEL_DIR}") + logging.info(f" -> 数据集 YAML: {config.DATASET_YAML_PATH}") + logging.info(f" -> 测试图片目录: {config.TEST_IMAGE_DIR}") +except ImportError: + logging.error("错误: yolo_config.py 未找到。请确保它在同一目录下。") + sys.exit(1) +except AttributeError as e: + logging.error(f"错误: yolo_config.py 中缺少必要的配置。 {e}") + sys.exit(1) + +# --- 辅助函数: 1. 添加标签 --- +def add_label_to_image(img, label): + """在图像左上角添加一个带背景的标签""" + img_with_label = img.copy() + h, w = img_with_label.shape[:2] + # 动态调整字体大小和粗细 + font_scale = max(0.8, w // 1000) + thickness = max(1, w // 500) + + (text_w, text_h), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness) + + # 绘制黑色背景矩形 + cv2.rectangle(img_with_label, (0, 0), (text_w + 20, text_h + 30), (0, 0, 0), -1) + # 绘制白色文字 + cv2.putText(img_with_label, label, (10, text_h + 15), cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), thickness, cv2.LINE_AA) + return img_with_label + +# --- 辅助函数: 2. 加载颜色LUT --- +def load_color_lut(dataset_yaml_path): + """ + 加载颜色查找表 (LUT),精确复制 yolo_predict_V2.py 的逻辑 + """ + try: + with open(dataset_yaml_path, 'r', encoding='utf-8') as f: + yaml_data = yaml.safe_load(f) + class_names = yaml_data.get('names') + color_data_from_dataset = yaml_data.get('colors') + + if not class_names: + logging.error(f"在 {dataset_yaml_path} 中未找到 'names' 键。") + return None + + num_classes = len(class_names) + logging.info(f"检测到 {num_classes} 个类别 (ID 0 到 {num_classes-1})。") + + except Exception as e: + logging.error(f"加载或解析 dataset.yaml 失败: {e}") + return None + + # 创建 (N) 维临时 LUT + temp_lut = np.zeros((num_classes, 3), dtype=np.uint8) + + if color_data_from_dataset: + logging.info(f"正在从 {dataset_yaml_path} 加载颜色...") + try: + for class_id, rgb_value in color_data_from_dataset.items(): + if 0 <= class_id < num_classes: + temp_lut[class_id] = rgb_value[::-1] # BGR + logging.info("临时颜色查找表 (LUT) 已成功从 dataset.yaml 创建。") + except Exception as e: + logging.error(f"解析 'colors' 键失败: {e}。将尝试备用方案。") + color_data_from_dataset = None + + if not color_data_from_dataset: + color_map_path = 'color_map.yaml' + logging.warning(f"未从 dataset.yaml 加载颜色,正在尝试备用方案: {color_map_path}") + try: + with open(color_map_path, 'r', encoding='utf-8') as f: + color_data = yaml.safe_load(f)['colors'] + for class_id, bgr_value in color_data.items(): + if 0 <= class_id < num_classes: + temp_lut[class_id] = bgr_value # 假设 color_map.yaml 存的是 BGR + logging.info(f"颜色查找表 (LUT) 已成功从 {color_map_path} 创建。") + except Exception: + logging.warning(f"{color_map_path} 未找到或解析失败。将使用随机颜色。") + for i in range(1, num_classes): + temp_lut[i] = np.random.randint(0, 255, 3, dtype=np.uint8) + + # --- 核心条件判断 (与 yolo_predict_V2.py 完全一致) --- + if np.any(temp_lut[0] != [0, 0, 0]): + logging.info("检测到 类别0 的颜色不是黑色。将创建 (N+1) LUT,使 0 成为专用背景。") + color_lut = np.zeros((num_classes + 1, 3), dtype=np.uint8) + color_lut[1:] = temp_lut + else: + logging.info("检测到 类别0 的颜色是黑色。将使用 (N) LUT。") + color_lut = temp_lut + + logging.info(f"最终颜色LUT创建成功,维度: {color_lut.shape}") + return color_lut + +# --- 辅助函数: 3. 查找模型目录 --- +def find_model_dirs(base_dir, required_subdir): + """查找所有包含 'required_subdir' 子目录的有效模型文件夹""" + model_dirs = [] + if not base_dir.is_dir(): + logging.error(f"基础目录不存在: {base_dir}") + return [] + + for d in base_dir.iterdir(): + # 必须是一个目录,并且包含所需的预测结果子目录 + if d.is_dir() and (d / required_subdir).exists(): + model_dirs.append(d) + + model_dirs.sort() + return model_dirs + +# --- 辅助函数: 4. 查找图像名称 --- +def find_image_names(model_dirs, sub_dir_name, file_glob_patterns): + """ + 从第一个模型目录的指定子目录(sub_dir_name)中获取所有待处理的图像文件名 + 使用 file_glob_patterns 列表 (例如 ['*.jpg', '*.png']) + """ + image_names = set() # 使用集合避免重复 + if not model_dirs: + return [] + + first_model_target_dir = model_dirs[0] / sub_dir_name + + for pattern in file_glob_patterns: + for f in first_model_target_dir.glob(pattern): + if f.is_file(): + image_names.add(f.name) + + sorted_names = sorted(list(image_names)) + return sorted_names + +# --- 辅助函数: 5. 获取原图和GT路径 --- +def get_gt_and_original_paths(image_name): + """ + 根据输出的掩码文件名,反向推导原始图像和GT图像的路径。 + 假设文件名格式为: {original_stem}_{pt_name_raw}.png + """ + try: + # 1. 从 config 中获取关键路径 + test_image_dir = config.TEST_IMAGE_DIR + images_dir = test_image_dir.parent + dataset_root_dir = images_dir.parent + image_parent_dir_name = test_image_dir.name # e.g., 'val' + + # 2. 推导原始图像的 stem + if '_' not in image_name: + logging.warning(f" -> 图像 {image_name} 似乎不含 '_{{pt_name}}' 后缀,将尝试使用完整文件名作为 stem。") + original_stem = Path(image_name).stem + else: + # 从最后一个 '_' 拆分 + original_stem = image_name[:image_name.rfind('_')] + + + # 3. 在 test_image_dir 中查找原始图像 + original_image_path = None + # glob 查找, 匹配 .png, .jpg, .bmp 等 + for f in test_image_dir.glob(f"{original_stem}.*"): + if f.is_file() and f.suffix.lower() in ['.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff']: + original_image_path = f + break # 找到第一个匹配项 + + if not original_image_path: + # 尝试在上一级目录查找 (兼容某些奇怪的 val/test 结构) + for f in test_image_dir.parent.glob(f"{original_stem}.*"): + if f.is_file() and f.suffix.lower() in ['.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff']: + original_image_path = f + break + + if not original_image_path: + logging.warning(f" -> 在 {test_image_dir} (及其父目录) 中未找到原始图像 (如 {original_stem}.*)") + return None, None + + # 4. 推导GT路径 (逻辑复制自 yolo_predict_V2.py) + image_filename = original_image_path.name + + gt_path = dataset_root_dir / "labels_GT" / image_parent_dir_name / image_filename + gt_path_png = gt_path.with_suffix('.png') # 备用 + + if gt_path.exists(): + return original_image_path, gt_path + elif gt_path_png.exists(): + return original_image_path, gt_path_png + else: + # 返回预期的路径,主循环将处理 "文件未找到" + return original_image_path, gt_path + + except Exception as e: + logging.error(f" -> 推导路径时出错: {e}", exc_info=True) + return None, None + + +# --- [新增] 核心处理函数 --- +def generate_comparison_images(base_dir, output_dir_name, source_subdir_name, + gt_panel_label, use_gt_overlay, + file_glob_patterns, color_lut): + """ + 核心处理函数,用于生成一种类型的对比图。 + """ + logging.info(f"--- 🚀 开始生成 '{output_dir_name}' ---") + + output_dir = base_dir / output_dir_name + output_dir.mkdir(exist_ok=True) + + # 3. 查找模型目录 + model_dirs = find_model_dirs(base_dir, source_subdir_name) + if not model_dirs: + logging.error(f"在 {base_dir} 中未找到任何包含 '{source_subdir_name}' 的有效模型预测目录。") + logging.warning(f"--- ⚠️ 跳过 '{output_dir_name}' 的生成 ---") + return + logging.info(f"找到 {len(model_dirs)} 个模型进行对比:") + for d in model_dirs: + logging.info(f" -> {d.name}") + + # 4. 查找图像列表 + image_names = find_image_names(model_dirs, source_subdir_name, file_glob_patterns) + if not image_names: + logging.error(f"未找到任何匹配 {file_glob_patterns} 的图像文件。") + logging.error(f"请检查 {model_dirs[0] / source_subdir_name} 目录。") + logging.warning(f"--- ⚠️ 跳过 '{output_dir_name}' 的生成 ---") + return + logging.info(f"找到 {len(image_names)} 张图像进行处理。") + + # 5. --- 遍历每张图像 --- + for image_name in tqdm(image_names, desc=f"生成 {output_dir_name}"): + + comparison_panels = [] + + # 5a. 获取路径 + orig_path, gt_path = get_gt_and_original_paths(image_name) + + if not orig_path: + logging.warning(f"\n跳过: 未能找到 {image_name} 的原始图像。") + continue + + # 5b. 加载原图 + orig_img = cv2.imread(str(orig_path)) + if orig_img is None: + logging.warning(f"\n跳过: 无法读取原始图像 {orig_path}") + continue + + h, w = orig_img.shape[:2] + + # 1. ORI_PIC + comparison_panels.append(add_label_to_image(orig_img, "ORI_PIC")) + + # 5c. 加载 Ground Truth + gt_color = np.zeros_like(orig_img) # 默认黑色 + if gt_path and gt_path.exists(): + gt_gray = cv2.imread(str(gt_path), cv2.IMREAD_GRAYSCALE) + if gt_gray is not None: + if gt_gray.shape != (h, w): + gt_gray = cv2.resize(gt_gray, (w, h), interpolation=cv2.INTER_NEAREST) + gt_color = color_lut[gt_gray] + else: + logging.warning(f"\n无法读取GT图像 {gt_path},使用黑色占位符。") + else: + logging.warning(f"\nGT图像未找到 (预期路径: {gt_path}),使用黑色占位符。") + + # 2. GT Panel (Mask or Overlay) + if use_gt_overlay: + gt_panel = cv2.addWeighted(orig_img, 0.6, gt_color, 0.4, 0) + else: + gt_panel = gt_color + comparison_panels.append(add_label_to_image(gt_panel, gt_panel_label)) + + # 5d. 加载每个模型的预测结果 + for model_dir in model_dirs: + pred_path = model_dir / source_subdir_name / image_name + pred_img = np.zeros_like(orig_img) # 默认黑色 + + if pred_path.exists(): + img = cv2.imread(str(pred_path)) + if img is not None: + if img.shape != (h, w, 3): + logging.warning(f"\n预测图 {pred_path.name} 尺寸 {img.shape} 与原图 {(h,w,3)} 不匹配。正在调整大小...") + img = cv2.resize(img, (w, h), interpolation=cv2.INTER_AREA) + pred_img = img + else: + logging.warning(f"\n无法读取预测图 {pred_path},使用黑色占位符。") + else: + logging.warning(f"\n预测图未找到 {pred_path},使用黑色占位符。") + + short_label = model_dir.name.split('_')[0] + comparison_panels.append(add_label_to_image(pred_img, short_label)) + + # 5e. 拼接并保存 + try: + final_image = cv2.hconcat(comparison_panels) + save_stem = Path(image_name).stem + save_path = output_dir / (save_stem + ".png") + cv2.imwrite(str(save_path), final_image) + + except Exception as e: + logging.error(f"\n--- 拼接或保存 {image_name} 失败: {e} ---") + logging.error(f"面板尺寸 (H, W, C): {[p.shape for p in comparison_panels]}") + logging.warning("请检查所有图像是否具有相同的高度。") + + logging.info(f"--- ✅ '{output_dir_name}' 生成完毕 ---") + + +# --- [修改] 主函数 (重构) --- +def main(): + parser = argparse.ArgumentParser( + description="将所有模型的预测结果 (Mask 和 Yolo) 与原图和GT图拼接成对比图。" + ) + # 模式参数已移除 + parser.add_argument( + "--pt_name", + type=str, + default="all", + help="您想比较的权重文件名称 (例如 'best.pt' 或 'epoch100.pt','all'表示对全部权重进行处理)" + ) + args = parser.parse_args() + + base_dir = config.PREDICT_BEST_MODEL_DIR + + # 1. 加载颜色LUT (只需一次) + color_lut = load_color_lut(config.DATASET_YAML_PATH) + if color_lut is None: + logging.error("无法加载颜色LUT,脚本终止。") + return + + # 2. 定义通用文件模式 + pt_name_raw = args.pt_name.replace('.pt', '') + all_image_types = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.tif", "*.tiff"] + + # 3. --- 执行 "Mask" 模式 --- + mask_patterns = [] + if args.pt_name.lower() == "all": + mask_patterns = ["*.png"] + else: + mask_patterns = [f"*_{pt_name_raw}.png"] + + generate_comparison_images( + base_dir=base_dir, + output_dir_name="compare_all_masks", + source_subdir_name="predicted_color_masks", + gt_panel_label="Ground_Truth", + use_gt_overlay=False, + file_glob_patterns=mask_patterns, + color_lut=color_lut + ) + + # 4. --- 执行 "Yolo" 模式 --- + yolo_patterns = [] + if args.pt_name.lower() == "all": + yolo_patterns = all_image_types + else: + yolo_patterns = [ + f"*_{pt_name_raw}.jpg", f"*_{pt_name_raw}.jpeg", + f"*_{pt_name_raw}.png", f"*_{pt_name_raw}.bmp", + f"*_{pt_name_raw}.tif", f"*_{pt_name_raw}.tiff" + ] + + generate_comparison_images( + base_dir=base_dir, + output_dir_name="compare_all_masks_Yolo", + source_subdir_name="prediction", + gt_panel_label="GT_Overlay", + use_gt_overlay=True, + file_glob_patterns=yolo_patterns, + color_lut=color_lut + ) + + logging.info("--- 🏁 所有对比图任务执行完毕 ---") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/yolo_predict_raw_masks_check.py b/Seg_All_In_One_YoloModel/yolo_predict_raw_masks_check.py new file mode 100644 index 0000000..3e3f735 --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_predict_raw_masks_check.py @@ -0,0 +1,233 @@ +import sys +import yaml +from pathlib import Path +from typing import Set, Tuple +import argparse # <-- [新增] +import logging # <-- [新增] + +try: + # Import config to get base directories and dataset configuration path + import yolo_config as config +except ImportError: + print("错误:无法导入 'yolo_config.py'。请确保此脚本与 config.py 在同一目录下。") + sys.exit(1) + +def get_file_info(directory: Path) -> Tuple[Set[str], Set[str]]: + """ + 安全地获取目录中所有文件的基本名(stem)和后缀(suffix)。 + + 返回: + 一个包含 stems 集合和 suffixes 集合的元组。 + """ + if not directory.is_dir(): + return set(), set() + + stems = set() + suffixes = set() + for item in directory.iterdir(): + if item.is_file(): + stems.add(item.stem) + suffixes.add(item.suffix) + return stems, suffixes + +def get_source_labels_dir() -> Path: + """ + 从 dataset.yaml 解析并构建源标签目录的绝对路径。 + 路径的最后一部分 (例如 'train', 'val' 或 'test') 将根据 dataset.yaml 中的 'test' 键动态确定。 + """ + try: + with open(config.DATASET_YAML_PATH, 'r', encoding='utf-8') as f: + yaml_data = yaml.safe_load(f) + + # 1. 获取数据集的根目录相对路径 + relative_path_from_yaml = yaml_data.get('path') + if not relative_path_from_yaml: + print(f"错误: 在 '{config.DATASET_YAML_PATH}' 文件中没有找到 'path' 键。") + sys.exit(1) + + # 2. 【新增】获取 'test' 键的值 (例如 'images/val') + test_path_from_yaml = yaml_data.get('test') + if not test_path_from_yaml: + print(f"错误: 在 '{config.DATASET_YAML_PATH}' 文件中没有找到 'test' 键。") + sys.exit(1) + + # 3. 【新增】从 'images/val' 中提取最后一部分 'val' + # Path(...).name 可以轻松实现这个功能 + source_split = Path(test_path_from_yaml).name + + # 4. 构建数据集的绝对路径 + dataset_dir = (config.DATASET_YAML_PATH.parent / relative_path_from_yaml).resolve() + + # 5. 【已修改】使用动态提取的 source_split 构建并返回标签目录路径 + labels_dir = dataset_dir / "labels" / source_split + return labels_dir + + except FileNotFoundError: + print(f"错误: YAML 配置文件未找到: '{config.DATASET_YAML_PATH}'") + sys.exit(1) + except Exception as e: + print(f"读取或解析 YAML 文件时发生错误: {e}") + sys.exit(1) + + +def main(): + """ + 主函数,用于详细检查源训练标签集和所有预测输出的原始掩码 + 目录之间的文件数量和文件名一致性。 + """ + # --- [新增] --- + parser = argparse.ArgumentParser(description="检查预测掩码与源标签的一致性。") + parser.add_argument( + "--pt_name", + type=str, + default="best.pt", + help="要检查的权重文件名称 (例如 'best.pt' 或 'epoch100.pt')。脚本将查找以此为后缀的掩码文件。" + ) + args = parser.parse_args() + + # 从 'best.pt' 提取 'best' + pt_name_raw = Path(args.pt_name).stem + # 构建预期的后缀, e.g., '_best' + expected_suffix = f"_{pt_name_raw}" + + # [核心修改] 只有 'best.pt' 会兼容检查无后缀的传统文件 + check_traditional = (args.pt_name == "best.pt") + + print(f"--- 开始详细检查预测原始掩码 (predicted_raw_masks) ---") + if check_traditional: + print(f"[i] 本次检查目标: --pt_name='{args.pt_name}' (检查后缀 '{expected_suffix}' 或 无后缀的传统文件)") + else: + print(f"[i] 本次检查目标: --pt_name='{args.pt_name}' (仅检查后缀 '{expected_suffix}' 的文件)") + + # 1. 获取源标签目录并解析文件信息 + source_dir = get_source_labels_dir() + if not source_dir.exists() or not source_dir.is_dir(): + print(f"错误:在 '{source_dir}' 找不到源训练标签目录。请检查 dataset.yaml 中的 'path' 设置。") + sys.exit(1) + + source_stems, _ = get_file_info(source_dir) + source_file_count = len(source_stems) + + if source_file_count == 0: + print(f"警告:源标签目录 '{source_dir}' 为空或不包含任何文件。检查中止。") + sys.exit(0) + + # 在你的日志中,路径是 '1_CholecSeg8k-13Type-1920x1080',但配置文件是 '4_Dresden-11Type-512x512' + # 这里我们以代码逻辑为准,它会动态读取配置文件 + print(f"源标签集: 在 '{source_dir}' 中找到 {source_file_count} 个标签文件 (.txt)。") + print("-" * 60) + + # 2. 获取预测结果的基础目录 + predictions_base_dir = config.PREDICT_BEST_MODEL_DIR + if not predictions_base_dir.exists() or not predictions_base_dir.is_dir(): + print(f"错误:在 '{predictions_base_dir}' 找不到预测结果的基础目录。") + sys.exit(1) + + # 3. 遍历每个模型的运行目录并进行详细检查 + found_predictions = False + global_mismatch_found = False + + # 【已修正】直接查找所有模型的运行目录,例如 'YOLOv8n-seg_2025-09-20_10-00-00' + run_dirs = sorted([d for d in predictions_base_dir.iterdir() if d.is_dir()]) + + if not run_dirs: + print(f"信息:在基础目录 '{predictions_base_dir}' 中没有找到任何模型的预测结果文件夹。") + sys.exit(0) + + # 【已修正】移除外层循环,直接遍历 run_dirs + for run_dir in run_dirs: + target_dir = run_dir / "predicted_raw_masks" + + # 如果目标目录不存在,则跳过 + if not (target_dir.exists() and target_dir.is_dir()): + continue + + print(f"正在检查: '{target_dir}'...") + found_predictions = True + is_current_dir_ok = True + + # 获取目录中所有的stems + all_predicted_stems, _ = get_file_info(target_dir) + + # --- [核心修改] --- + # 1. 提取我们关心的stems + processed_stems = set() # 存储处理后 (去掉后缀) 的stems, e.g., {'fileA', 'fileB'} + stems_we_checked = set() # 存储我们检查过的原始stems, e.g., {'fileA_best', 'fileA'} + + for stem in all_predicted_stems: + # 检查 1: 是否为带后缀的新模式 (e.g., "fileA_best" 或 "fileA_epoch100") + if stem.endswith(expected_suffix): + # 去掉后缀, e.g., 'fileA_best' -> 'fileA' + unstripped_stem = stem[:-len(expected_suffix)] + + # 仅当去掉后缀后的部分在源文件中时才添加 + if unstripped_stem in source_stems: + processed_stems.add(unstripped_stem) + stems_we_checked.add(stem) + + # 检查 2: [条件] 是否允许检查传统文件 (check_traditional == True) + # [条件] 且该文件是否为无后缀的传统文件 (e.g., "fileA") + elif check_traditional and (stem in source_stems): + # 兼容 'best.pt' 检查传统文件 + processed_stems.add(stem) + stems_we_checked.add(stem) + + # 2. 现在,'processed_stems' 只包含与 'source_stems' 对应的基础文件名 + predicted_file_count = len(processed_stems) + + # --- 检查 1: 文件数量 --- + if source_file_count != predicted_file_count: + print(f" [✗ 数量不匹配] 源目录有 {source_file_count} 个文件,但此目录中(匹配检查规则的)有 {predicted_file_count} 个。") + is_current_dir_ok = False + + # --- 检查 2: 文件名 (基于处理后的stems) --- + missing_files = source_stems - processed_stems + if missing_files: + examples = list(missing_files)[:3] + print(f" [✗ 文件名缺失] 预测结果中缺少 {len(missing_files)} 个文件 (匹配检查规则)。例如: {examples}...") + is_current_dir_ok = False + + extra_files = processed_stems - source_stems + if extra_files: + examples = list(extra_files)[:3] + print(f" [✗ 文件名多余] (逻辑异常) 预测结果中多出 {len(extra_files)} 个文件。例如: {examples}...") + is_current_dir_ok = False + + # --- [新增] 检查 3: 提示其他被忽略的文件 --- + other_files = all_predicted_stems - stems_we_checked + if other_files: + examples = list(other_files)[:3] + print(f" [i 信息] 目录中还包含 {len(other_files)} 个其他文件 (例如: {examples}...)。") + if check_traditional: + print(f" [i 信息] (这些文件不匹配后缀 '{expected_suffix}' 且不属于无后缀传统文件,已忽略)") + else: + print(f" [i 信息] (这些文件不匹配后缀 '{expected_suffix}',已忽略)") + + # --- 当前目录的检查总结 --- + if is_current_dir_ok: + # 再次检查目录为空的特殊情况 + if predicted_file_count == 0 and source_file_count > 0: + print(f" [✗ 目录为空] 预测目录中没有找到任何匹配本次检查规则的文件。") + global_mismatch_found = True + else: + print(f" [✓ OK] 所有检查通过 (文件数量: {predicted_file_count},匹配检查规则)") + else: + global_mismatch_found = True + + print("-" * 30) + + # 4. 输出最终的全局检查摘要 + print("\n--- 检查摘要 ---") + if not found_predictions: + print("结果: 没有找到任何 'predicted_raw_masks' 目录进行检查。") + elif global_mismatch_found: + print("结论: 检查不通过。发现至少一个预测目录未能与源标签集完全匹配,请查看上方日志。") + sys.exit(1) + else: + print("结论: 检查通过!所有已找到的 'predicted_raw_masks' 目录均通过了与源标签集的文件名和数量一致性检查。") + + print("--- 检查完成 ---") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/yolo_predict_visualize_nn.py b/Seg_All_In_One_YoloModel/yolo_predict_visualize_nn.py new file mode 100644 index 0000000..d64c7ee --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_predict_visualize_nn.py @@ -0,0 +1,538 @@ +import logging +import sys +import argparse +import shutil +import os +from pathlib import Path +from typing import List, Dict, Any +from ultralytics import YOLO + +# --- [!! 关键 !!] 动态添加项目根目录 (来自之前的讨论) --- +script_path = Path(__file__).resolve() +project_root = script_path.parent.parent +sys.path.insert(0, str(project_root)) +# ---------------------------------------------------- +import yolo_config as config + +# --- [新增] 导入 V2 所需的库 --- +import cv2 +import numpy as np +import torch +from pytorch_grad_cam import ( + GradCAM, GradCAMPlusPlus, XGradCAM, EigenCAM, + HiResCAM, LayerCAM, RandomCAM, EigenGradCAM +) +from pytorch_grad_cam.utils.image import show_cam_on_image +from pytorch_grad_cam.base_cam import BaseCAM + +# --- 日志设置 --- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler()] +) + +# --- [保留] V2 中的 CAM 方法字典 --- +CAM_METHODS = { + "GradCAM": GradCAM, # ok # 速度快 + "GradCAMPlusPlus": GradCAMPlusPlus, # ok # 速度快 + # "XGradCAM": XGradCAM, # ok # 速度快 + "EigenCAM": EigenCAM, + "HiResCAM": HiResCAM, # ok # 速度快 + # "LayerCAM": LayerCAM, # ok # 速度快 + "RandomCAM": RandomCAM, # ok # 速度快 + # "EigenGradCAM": EigenGradCAM, # 这个耗时长 +} + +# --- [保留] V2 中的 ActivationMaximizationTarget --- +class ActivationMaximizationTarget: + def __init__(self, channel=0): + self.channel = channel + def __call__(self, model_output): + if model_output.ndim == 4: + return model_output[:, self.channel, :, :].mean() + elif model_output.ndim == 3: + return model_output[self.channel, :, :].mean() + else: + raise ValueError(f"Unsupported model_output shape: {model_output.shape}") + +# --- [保留] 图像预处理函数 --- +def preprocess_image(bgr_img: np.ndarray, imgsz: int, device: str) -> (torch.Tensor, Dict[str, Any]): + # (函数内容与原版相同,为节省篇幅已折叠) + orig_h, orig_w = bgr_img.shape[:2] + rgb_img = bgr_img[:, :, ::-1] + scale = min(imgsz / orig_w, imgsz / orig_h) + new_w, new_h = int(orig_w * scale), int(orig_h * scale) + resized_image = cv2.resize(rgb_img, (new_w, new_h), interpolation=cv2.INTER_LINEAR) + pad_w, pad_h = imgsz - new_w, imgsz - new_h + pad_left, pad_right = pad_w // 2, pad_w - pad_w // 2 + pad_top, pad_bottom = pad_h // 2, pad_h - pad_h // 2 + padded_image = cv2.copyMakeBorder(resized_image, pad_top, pad_bottom, pad_left, pad_right, + cv2.BORDER_CONSTANT, value=(114, 114, 114)) + img_float = np.float32(padded_image) / 255.0 + input_tensor = torch.from_numpy(img_float).permute(2, 0, 1).unsqueeze(0).to(device) + padding_info = { + "pad_top": pad_top, "new_h": new_h, "pad_left": pad_left, + "new_w": new_w, "orig_h": orig_h, "orig_w": orig_w + } + return input_tensor, padding_info + + +# --- [保留] V1 中的关键修复函数 --- +def reshape_transform(outputs): + if isinstance(outputs, tuple): + return outputs[0] + return outputs + +# --- [保留] V1 中的模型查找函数 --- +def find_trained_models(outputs_dir: Path, model_key: str, pt_name: str) -> List[str]: + # (函数内容与原版相同,为节省篇幅已折叠) + trained_models = [] + if not outputs_dir.is_dir(): + logging.warning(f"输出目录不存在: {outputs_dir}") + return [] + for project_folder in outputs_dir.iterdir(): + if project_folder.is_dir() and project_folder.name.startswith(model_key + '_'): + if (project_folder / 'weights' / pt_name).exists(): + trained_models.append(project_folder.name) + trained_models.sort() + return trained_models + + +# --- [!! 核心重构 !!] 融合 V1 和 V2 的可视化函数 --- +def visualize_nn_comprehensive( + model_path: str, + source_dir: str, + base_save_dir: Path, + pt_name: str, + cam_method_name: str, + target_layer_str: str, + model_key: str # [!! 新增 !!] 接收模型 Key (e.g., "YOLO11m-seg") +): + """ + 使用 V2 的多方法和 V1 的框架进行 CAM 可视化。 + """ + + # --- 1. 加载模型 (V1 逻辑) --- + try: + logging.info(f"正在加载模型: {model_path}") + device = 'cuda' if torch.cuda.is_available() else 'cpu' + model = YOLO(model_path).to(device) + model.model.eval() + imgsz = model.model.args['imgsz'] + logging.info(f"模型加载成功。设备: {device}, 图像尺寸: {imgsz}") + except Exception as e: + logging.error(f"无法加载模型 {model_path}: {e}", exc_info=True) + return + + # --- 2. [!! 关键修改 !!] 解析目标层 --- + target_layers_to_process = [] # 存储 (name, layer_module) 元组 + + if target_layer_str.strip().lower() == "default": + logging.info("检测到 'default' 目标层,正在解析...") + + # 对 YOLOv8 系列模型的 'default' 逻辑 # TODO # 对模型进行修改 + if model_key.lower().startswith("yolov8"): + logging.info(f"检测到 YOLOv8 系列模型 ({model_key})。将使用精选的8个默认层。") + default_yolo8_layers = [ + (1, "model.model.model[1]"), # Layer_01_Conv + (4, "model.model.model[4]"), # Layer_04_C2f + (9, "model.model.model[9]"), # Layer_09_SPPF + (11, "model.model.model[11]"), # Layer_11_Concat + (14, "model.model.model[14]"), # Layer_14_Concat + (19, "model.model.model[19]"), # Layer_19_Conv + (20, "model.model.model[20]"), # Layer_20_Concat + (21, "model.model.model[21]") # Layer_21_C2f + ] + + # 如果使用 EigenCAM 或 EigenGradCAM,则只分析最后两个层 + if cam_method_name in ["EigenCAM", "EigenGradCAM"]: + logging.info(f" -> 使用 {cam_method_name}。将自动裁剪为最后2个默认层。") + default_yolo8_layers = default_yolo8_layers[-3:] # (只保留最后两个) + + for idx, path_str in default_yolo8_layers: + try: + layer = model.model.model[idx] + target_layers_to_process.append((path_str, layer)) + logging.info(f" -> 已添加 YOLOv8 默认层: {path_str} (索引 {idx})") + except Exception as e: + logging.warning(f" -> [!] 无法添加 YOLOv8 默认层 {path_str}: {e}") + + # 对 YOLOv9 系列模型的 'default' 逻辑 # TODO # 对模型进行修改 + elif model_key.lower().startswith("yolov9"): + logging.info(f"检测到 YOLOv9 系列模型 ({model_key})。将使用精选的8个默认层。") + # (这是您新指定的列表) + default_yolo9_layers = [ + (2, "model.model.model[2]"), # Layer_02_Conv + (7, "model.model.model[7]"), # Layer_07_RepNCSPELAN4 + (29, "model.model.model[29]"), # Layer_29_SPPELAN + (31, "model.model.model[31]"), # Layer_31_Concat + (34, "model.model.model[34]"), # Layer_34_Concat + (37, "model.model.model[37]"), # Layer_37_Concat + (40, "model.model.model[40]"), # Layer_40_Concat + (41, "model.model.model[41]") # Layer_41_RepNCSPELAN4 + ] + + # 如果使用 EigenCAM 或 EigenGradCAM,则只分析最后两个层 + if cam_method_name in ["EigenCAM", "EigenGradCAM"]: + logging.info(f" -> 使用 {cam_method_name}。将自动裁剪为最后2个默认层。") + default_yolo9_layers = default_yolo9_layers[-3:] # (只保留最后两个) + + for idx, path_str in default_yolo9_layers: + try: + layer = model.model.model[idx] + target_layers_to_process.append((path_str, layer)) + logging.info(f" -> 已添加 YOLOv9 默认层: {path_str} (索引 {idx})") + except Exception as e: + logging.warning(f" -> [!] 无法添加 YOLOv9 默认层 {path_str}: {e}") + + # 对 YOLO11 系列模型的 'default' 逻辑 # TODO # 对模型进行修改 + elif model_key.lower().startswith("yolo11"): + logging.info(f"检测到 YOLO11 系列模型 ({model_key})。将使用精选的8个默认层。") + # (索引, 路径字符串) + default_yolo11_layers = [ + (0, "model.model.model[0]"), # Layer_00_Conv + (4, "model.model.model[4]"), # Layer_04_C3k2 + (9, "model.model.model[9]"), # Layer_09_SPPF + (12, "model.model.model[12]"), # Layer_12_Concat + (15, "model.model.model[15]"), # Layer_15_Concat + (20, "model.model.model[20]"), # Layer_20_Conv + (21, "model.model.model[21]"), # Layer_21_Concat + (22, "model.model.model[22]") # Layer_22_C3k2 + ] + + # 如果使用 EigenCAM 或 EigenGradCAM,则只分析最后两个层 + if cam_method_name in ["EigenCAM", "EigenGradCAM"]: + logging.info(f" -> 使用 {cam_method_name}。将自动裁剪为最后2个默认层。") + default_yolo11_layers = default_yolo11_layers[-3:] # (只保留最后两个) + + for idx, path_str in default_yolo11_layers: + try: + # 假设 'model.model.model' 是一个列表 + layer = model.model.model[idx] + target_layers_to_process.append((path_str, layer)) + logging.info(f" -> 已添加 YOLO11 默认层: {path_str} (索引 {idx})") + except Exception as e: + logging.warning(f" -> [!] 无法添加 YOLO11 默认层 {path_str}: {e}") + + # 对 YOLO12 系列模型的 'default' 逻辑 # TODO # 对模型进行修改 + elif model_key.lower().startswith("yolo12"): + logging.info(f"检测到 YOLOv12 系列模型 ({model_key})。将使用您精选的8个默认层。") + # (这是您新指定的列表) + default_yolo12_layers = [ + (2, "model.model.model[2]"), # Layer_02_C3k2 + (4, "model.model.model[4]"), # Layer_04_C3k2 + (8, "model.model.model[8]"), # Layer_08_A2C2f + (10, "model.model.model[10]"), # Layer_10_Concat + (13, "model.model.model[13]"), # Layer_13_Concat + (17, "model.model.model[17]"), # Layer_17_A2C2f + (19, "model.model.model[19]"), # Layer_19_Concat + (20, "model.model.model[20]") # Layer_20_C3k2 + ] + + # 如果使用 EigenCAM 或 EigenGradCAM,则只分析最后两个层 + if cam_method_name in ["EigenCAM", "EigenGradCAM"]: + logging.info(f" -> 使用 {cam_method_name}。将自动裁剪为最后2个默认层。") + default_yolo12_layers = default_yolo12_layers[-3:] # (只保留最后两个) + + for idx, path_str in default_yolo12_layers: + try: + layer = model.model.model[idx] + target_layers_to_process.append((path_str, layer)) + logging.info(f" -> 已添加 YOLOv12 默认层: {path_str} (索引 {idx})") + except Exception as e: + logging.warning(f" -> [!] 无法添加 YOLOv12 默认层 {path_str}: {e}") + + # [!! 保留 !!] 非 YOLOv8、YOLOv9、YOLO11、YOLO12 模型的 'default' 逻辑 + else: + logging.info(f"非 YOLO11 模型 ({model_key})。将使用标准 'default' 逻辑。") + try: + layer = model.model.model[8].proto.cv3 # (来自您 V2 文件的 `[8]`) + name = "model.model.model[8].proto.cv3" + target_layers_to_process.append((name, layer)) + logging.info(f" -> 已添加默认层: {name}") + except Exception as e: + logging.warning(f" -> 无法定位 'model[8].proto.cv3'。回退到 'model[15]'。错误: {e}") + try: + layer = model.model.model[15] + name = "model.model.model[15]" + target_layers_to_process.append((name, layer)) + logging.info(f" -> 已添加回退层: {name}") + except Exception as e_fallback: + logging.error(f" -> [!] 无法定位 'model[15]'。跳过 'default'。错误: {e_fallback}") + + # [!! 保留 !!] 处理非 'default' 的自定义层路径 + else: + logging.info(f"正在解析自定义目标层: {target_layer_str}") + layer_paths = target_layer_str.split(',') + for path in layer_paths: + path_cleaned = path.strip() + if not path_cleaned: + continue + try: + layer = eval(path_cleaned, {"model": model}) + if isinstance(layer, torch.nn.Module): + target_layers_to_process.append((path_cleaned, layer)) + logging.info(f" -> 已添加指定层: {path_cleaned}") + else: + logging.warning(f" -> 路径 '{path_cleaned}' 不是一个 nn.Module,已跳过。") + except Exception as e: + logging.error(f" -> [!] 无法解析目标层路径 '{path_cleaned}': {e}。已跳过。") + + if not target_layers_to_process: + logging.error("未找到有效的目标层进行可视化。程序退出。") + return + + # --- 3. 查找源图片 (V1 逻辑) --- + source_path = Path(source_dir) + image_paths = [] + if source_path.is_file(): + image_paths = [source_path] + elif source_path.is_dir(): + image_paths = sorted( + list(source_path.glob("*.jpg")) + + list(source_path.glob("*.jpeg")) + + list(source_path.glob("*.png")) + ) + if not image_paths: + logging.error(f"在源路径中未找到任何图片: {source_dir}") + return + + # --- 4. 获取 CAM 方法 (V2 逻辑) --- + cam_class = CAM_METHODS.get(cam_method_name) + if not cam_class: + logging.error(f"无效的 CAM 方法: {cam_method_name}。") + return + logging.info(f"--- 将使用 CAM 方法: {cam_method_name} ---") + + # --- 5. [!! 循环顺序保留 !!] --- + + targets = None + if cam_method_name != "EigenCAM": + targets = [ActivationMaximizationTarget(channel=0)] + logging.info("已为非 EigenCAM 方法设置 ActivationMaximizationTarget(channel=0)。") + + pt_name_raw = pt_name.replace('.pt', '') # 移到循环外 + + # 外循环:遍历图片 + for img_path in image_paths: + logging.info(f"\n--- 正在处理图片: {img_path.name} ---") + + try: + # --- a. 加载和预处理 --- + orig_img_bgr = cv2.imread(str(img_path)) + if orig_img_bgr is None: + logging.warning(f" -> 无法读取图片: {img_path.name},已跳过。") + continue + + orig_img_rgb_float = np.float32(orig_img_bgr[:, :, ::-1]) / 255 + input_tensor, pad_info = preprocess_image(orig_img_bgr, imgsz, device) + input_tensor.requires_grad_() + + # --- b. [!! 关键修改 !!] 创建新的三层输出目录 --- + image_stem = img_path.stem + + # 1. 创建算法根目录 (e.g., .../train6/HeartMap_Visual/GradCAM/) + cam_root = base_save_dir / "HeartMap_Visual" / f"{cam_method_name}_{pt_name_raw}" + + # 2. 创建该图片专用的子目录 (e.g., .../HeartMap_Visual/GradCAM/image01_GradCAM/) + nn_visual_root = cam_root / f"{image_stem}_{cam_method_name}" + nn_visual_root.mkdir(parents=True, exist_ok=True) + + # 3. 创建该图片专用的 _ORI 子目录 (e.g., .../HeartMap_Visual/GradCAM/image01_GradCAM_ORI/) + nn_visual_ori_root = cam_root / f"{image_stem}_{cam_method_name}_ORI" + nn_visual_ori_root.mkdir(parents=True, exist_ok=True) + + # [!! 关键修改 !!] 更新日志打印路径,使其相对于 base_save_dir + logging.info(f" -> 覆盖图将保存到: {nn_visual_root.relative_to(base_save_dir)}") + logging.info(f" -> 纯热度图将保存到: {nn_visual_ori_root.relative_to(base_save_dir)}") + + + # --- c. 内循环:遍历层 --- + for layer_name, layer_module in target_layers_to_process: + safe_layer_name = layer_name.replace('.', '_').replace('[', '').replace(']', '') + logging.info(f" -> 正在处理层: {layer_name}") + + try: + # --- d. 初始化 CAM --- + with cam_class(model=model.model, + target_layers=[layer_module], + reshape_transform=reshape_transform) as cam: + + # --- e. 运行 CAM --- + cam_output = cam(input_tensor=input_tensor, targets=targets) + + # --- f. CAM 输出后处理 --- + # (此部分逻辑与 V2 相同) + grayscale_cam = None + if isinstance(cam_output, (list, tuple)): + grayscale_cam = cam_output[0] + else: + grayscale_cam = cam_output + if isinstance(grayscale_cam, torch.Tensor): + grayscale_cam = grayscale_cam.detach().cpu().numpy() + if grayscale_cam.ndim == 4: + grayscale_cam = grayscale_cam[0].mean(axis=0) + elif grayscale_cam.ndim == 3: + grayscale_cam = grayscale_cam[0, :] + elif grayscale_cam.ndim == 2: + pass # OK + else: + logging.warning(f" -> [!] 层 {layer_name} 的 CAM 结果维度异常: {grayscale_cam.shape},已跳过。") + continue + + # --- g. 裁剪 Padding 并 Resize --- + cam_cropped = grayscale_cam[pad_info['pad_top'] : pad_info['pad_top'] + pad_info['new_h'], + pad_info['pad_left'] : pad_info['pad_left'] + pad_info['new_w']] + cam_upscaled = cv2.resize(cam_cropped, + (pad_info['orig_w'], pad_info['orig_h']), + interpolation=cv2.INTER_LINEAR) + cam_upscaled = (cam_upscaled - cam_upscaled.min()) / (cam_upscaled.max() + 1e-8) + + # --- h. 定义文件名和路径 --- + save_filename_overlay = f"Layer_{safe_layer_name}_overlay.jpg" + save_filename_ori = f"Layer_{safe_layer_name}_heatmap.jpg" + + save_path_overlay = nn_visual_root / save_filename_overlay + save_path_ori = nn_visual_ori_root / save_filename_ori + + # --- i. 保存覆盖图 --- + visualization = show_cam_on_image(orig_img_rgb_float, + cam_upscaled, + use_rgb=True, + image_weight=0.5) + viz_bgr = visualization[:, :, ::-1] + cv2.imwrite(str(save_path_overlay), viz_bgr) + + # --- j. 保存纯热度图 --- + heatmap_uint8 = np.uint8(255 * cam_upscaled) + heatmap_color = cv2.applyColorMap(heatmap_uint8, cv2.COLORMAP_JET) + cv2.imwrite(str(save_path_ori), heatmap_color) + + logging.info(f" -> (√) {layer_name} 可视化结果已保存") + + except Exception as e_layer: + logging.error(f" -> [!] 处理层 {layer_name} 时发生错误: {e_layer}", exc_info=False) + + except Exception as e_img: + logging.error(f" -> [!] 处理图片 {img_path.name} 时发生严重错误: {e_img}", exc_info=True) + + logging.info("\n--- 所有图片已处理完毕 ---") + + +if __name__ == "__main__": + config.show_config_summary() + + parser = argparse.ArgumentParser(description="使用 Grad-CAM (EigenCAM) 生成 YOLO 神经网络可视化。") + + parser.add_argument( + "--model", + type=str, + required=True, + choices=list(config.MODEL_CONFIGS.keys()), + help="选择一个基础模型类型来筛选其训练历史。" + ) + parser.add_argument( + "--source", + type=str, + default=str(config.TEST_IMAGE_DIR), + help="图片或图片文件夹的路径。" + ) + parser.add_argument( + "--pt_name", + type=str, + default="best.pt", + help="要使用的权重文件名 (例如 'best.pt' 或 'epoch100.pt')。" + ) + all_cam_choices = list(CAM_METHODS.keys()) + ["All"] + parser.add_argument( + "--cam_method", + type=str, + default="All", + choices=all_cam_choices, + help=f"选择要使用的 CAM 可视化方法。默认为: All。" + ) + parser.add_argument( + "--target_layers", + type=str, + default="default", + help="指定要可视化的目标层。 " + "'default': (YOLOv8/YOLO11系列使用精选8层,其他使用标准默认层); " + "'model.model.model[15]': (指定单个层); " + "'model.model.model[10],model.model.model[15]': (指定多个层)" + ) + args = parser.parse_args() + + # --- [!! 关键修改 !!] 移除路径校准 --- + # 假设 yolo_config.py 已被修复,config.PREDICT_BEST_MODEL_DIR 是绝对路径 + logging.info(f"正在搜索模型目录: {config.PREDICT_BEST_MODEL_DIR}") + available_runs = find_trained_models(config.PREDICT_BEST_MODEL_DIR, args.model, args.pt_name) + + run_to_use = None + + if not available_runs: + logging.error(f"错误:在 {config.PREDICT_BEST_MODEL_DIR} 中未找到模型 '{args.model}' (使用 '{args.pt_name}') 的有效训练记录。") + sys.exit(1) + + elif len(available_runs) == 1: + run_to_use = available_runs[0] + logging.info(f"只找到一个训练版本,已自动选择: {run_to_use}") + + else: + # (V1 的模型选择菜单) + print(f"\n为模型 '{args.model}' (使用 '{args.pt_name}') 找到多个训练版本:") + for i, run_name in enumerate(available_runs, 1): + print(f" [{i}] {run_name}") + while True: + try: + choice = input(f"请输入您想使用的版本序号 (1-{len(available_runs)}): ") + choice_index = int(choice) + if 1 <= choice_index <= len(available_runs): + run_to_use = available_runs[choice_index - 1] + break + else: + print("错误:输入无效,请输入列表中的序号。") + except ValueError: + print("错误:请输入一个数字。") + except (KeyboardInterrupt, EOFError): + print("\n操作已取消。") + sys.exit(0) + + # --- 4. 调用 [!! 新的 !!] 可视化函数 --- + if run_to_use: + model_to_use = config.PREDICT_BEST_MODEL_DIR / run_to_use / 'weights' / args.pt_name + project_to_save_in = config.PREDICT_BEST_MODEL_DIR / run_to_use + + if not model_to_use.exists(): + logging.error(f"严重错误:找不到权重文件 {model_to_use}。") + else: + methods_to_run = [] + if args.cam_method == "All": + methods_to_run = list(CAM_METHODS.keys()) # + logging.info(f"--- [!!] 检测到 'All' 选项,将运行全部 {len(methods_to_run)} 种 CAM 方法 ---") + else: + methods_to_run = [args.cam_method] # 列表中只有一项 + + for method_name in methods_to_run: + # (可选) 检查 Eigen* 方法是否与 'default' 一起使用 + is_slow_method = method_name in ["EigenCAM", "EigenGradCAM"] # + is_default_layers = args.target_layers == "default" # + + if is_slow_method and not is_default_layers: + logging.warning(f"--- [警告] 您正在对自定义层运行 {method_name}。") + logging.warning(f"--- 如果目标层是浅层,可能会非常缓慢或卡住。---") + + logging.info(f"\n--- 正在启动 CAM 方法: [ {method_name} ] ---") + + # 将原始调用放入循环中 + visualize_nn_comprehensive( + model_path=str(model_to_use), + source_dir=args.source, + base_save_dir=project_to_save_in, + pt_name=args.pt_name, + cam_method_name=method_name, # [!! 修改 !!] 使用循环中的变量 + target_layer_str=args.target_layers, + model_key=args.model + ) + + logging.info("\n--- [!!] 所有指定的 CAM 方法均已执行完毕 ---") \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/yolo_train.py b/Seg_All_In_One_YoloModel/yolo_train.py new file mode 100644 index 0000000..e70bcdd --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_train.py @@ -0,0 +1,158 @@ +import argparse, shutil, glob +import logging +from pathlib import Path +from ultralytics import YOLO +from datetime import datetime +import yolo_config as config +import gc, torch + +# --- 日志设置 --- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler()] +) + +# 寻找最匹配的 last.pt文件 和 其所在文件夹 +def find_latest_last_pt(model_key: str, outputs_dir: Path): + """根据模型名查找最新的 last.pt 路径,并返回 (last_pt路径, 文件夹名)""" + pattern = str(outputs_dir / f"{model_key}_*" / "weights" / "last.pt") + candidates = glob.glob(pattern) + if not candidates: + return None, None + # 按修改时间排序,取最新的 + latest_pt = max(candidates, key=lambda p: Path(p).stat().st_mtime) + # 提取文件夹名 + save_folder_name = Path(latest_pt).parent.parent.name # weights 的上级就是训练目录 + return latest_pt, save_folder_name + +def train_model(model_key: str): + """ + 根据给定的模型密钥训练YOLO分割模型。 + + Args: + model_key (str): 在 yolo_config.MODEL_CONFIGS 中定义的模型密钥。 + """ + # 生成时间戳以区分不同训练运行 + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + project_folder = f"{model_key}_{timestamp}" + + if model_key not in config.MODEL_CONFIGS: + logging.error(f"错误:模型 '{model_key}' 不在 yolo_config.py 中定义。") + logging.info(f"可用模型: {list(config.MODEL_CONFIGS.keys())}") + return + + model_config = config.MODEL_CONFIGS[model_key] + model_file = model_config['weights'] + model_batch_size = model_config['batch_size'] + model_image_size = model_config['image_size'] + logging.info(f"开始训练模型: {model_key} ({model_file})") + + try: + # # V1:--- 1. 加载模型 --- + # model = YOLO(model_file) # 如果没有模型会自动下载 + # logging.info("模型加载成功。") + + # V2:--- 1. 加载模型(支持断点续训) --- + last_pt, save_folder_name = find_latest_last_pt(model_key, config.OUTPUTS_DIR) + if last_pt and Path(last_pt).exists(): + model = YOLO(last_pt) + logging.info(f"断点续训: {last_pt}") + logging.info(f"续训目录名为: {save_folder_name}") + + model.train( + resume=True, # ✅ 关键参数 + data=str(config.DATASET_YAML_PATH), + epochs=config.EPOCHS, + imgsz=model_image_size, + batch=model_batch_size, + optimizer=config.OPTIMIZER, + lr0=config.LEARNING_RATE, + device=config.DEVICE, + project=str(config.OUTPUTS_DIR), # 指定输出的根目录 + name=save_folder_name, # 指定本次训练的项目名 + workers=config.WORKERS, + exist_ok=True, # 如果项目已存在,则覆盖 + patience=config.PATIENCE, # 提前停止训练的轮数 + save_period=config.SAVE_PERIOD # 每隔多少轮保存一次模型 + ) + else: + model = YOLO(model_file) + logging.info(f"从头训练: {model_file}") + save_folder_name = project_folder + + # --- 2. 模型训练 --- + logging.info(f"数据集配置文件: {config.DATASET_YAML_PATH}") + logging.info(f"训练参数: Epochs={config.EPOCHS}, Batch Size={model_batch_size}, Img Size={model_image_size}") + + model.train( + data=str(config.DATASET_YAML_PATH), + epochs=config.EPOCHS, + imgsz=model_image_size, + batch=model_batch_size, + optimizer=config.OPTIMIZER, + lr0=config.LEARNING_RATE, + device=config.DEVICE, + project=str(config.OUTPUTS_DIR), # 指定输出的根目录 + name=save_folder_name, # 指定本次训练的项目名 + workers=config.WORKERS, + exist_ok=True, # 如果项目已存在,则覆盖 + patience=config.PATIENCE, # 提前停止训练的轮数 + save_period=config.SAVE_PERIOD # 每隔多少轮保存一次模型 + ) + + logging.info("模型训练完成。") + logging.info(f"训练结果保存在: {config.OUTPUTS_DIR / save_folder_name}") + + return save_folder_name + + except Exception as e: + logging.error(f"训练过程中发生错误: {e}") + return None + +# --- 3. 复制结果到硬盘并删除原文件夹 (新增逻辑) --- +def move_results_to_hardisk(project_folder: str): + source_dir = config.OUTPUTS_DIR / project_folder + # 确保目标硬盘目录存在 + outputs_folder_name = config.OUTPUTS_DIR.name + destination_dir = config.HARDISK_DIR / outputs_folder_name / project_folder + destination_dir.parent.mkdir(parents=True, exist_ok=True) + logging.info(f"准备将结果从 {source_dir} 移动到 {destination_dir}...") + try: + # 步骤 1: 复制文件夹 + shutil.copytree(source_dir, destination_dir) + logging.info(f"成功复制结果到: {destination_dir}") + + # 步骤 2: 复制成功后,删除原文件夹 + logging.info(f"正在删除原文件夹: {source_dir}") + shutil.rmtree(source_dir) + logging.info("成功删除原文件夹。") + logging.info(f"任务完成,最终结果已保存至: {destination_dir}") + + except Exception as e: + logging.error(f"移动文件夹时发生错误: {e}") + logging.warning(f"原始训练结果仍保留在: {source_dir}") + +if __name__ == "__main__": + # 创建命令行参数解析器 + parser = argparse.ArgumentParser(description="训练 YOLO 分割模型。") + parser.add_argument( + "--model", + type=str, + required=True, + choices=list(config.MODEL_CONFIGS.keys()), + help=f"选择要训练的模型。可选: {list(config.MODEL_CONFIGS.keys())}" + ) + args = parser.parse_args() + + # 1. 开始训练 + save_folder_name = train_model(args.model) + # 2. 训练完成后,移动结果到硬盘 + if save_folder_name: + move_results_to_hardisk(save_folder_name) + else: + logging.error("由于训练失败,未执行结果移动操作。") + # 3. 释放资源 + torch.cuda.empty_cache() # 释放 CUDA 显存 + gc.collect() # 释放系统内存 + logging.info("已清理 CUDA 显存与系统内存。") \ No newline at end of file diff --git a/Seg_All_In_One_YoloModel/yolo_train.sh b/Seg_All_In_One_YoloModel/yolo_train.sh new file mode 100644 index 0000000..ab171f1 --- /dev/null +++ b/Seg_All_In_One_YoloModel/yolo_train.sh @@ -0,0 +1,137 @@ +#!/bin/bash + +# ================================================================= +# YOLO 模型批量并行训练脚本 +# ================================================================= + +# --- 1. Conda 环境设置 --- +CONDA_BASE_PATH="/home/wkmgc/miniconda3" # <--- 在这里修改为您自己的 Conda 路径 +CONDA_ENV_NAME="${SEG_CONDA_ENV:-seg_smp}" # 可用 SEG_CONDA_ENV=SMP bash yolo_train.sh 临时覆盖 + +# 初始化并激活 Conda 环境 +if [ -f "${CONDA_BASE_PATH}/etc/profile.d/conda.sh" ]; then + source "${CONDA_BASE_PATH}/etc/profile.d/conda.sh" + conda activate "${CONDA_ENV_NAME}" + if [ $? -ne 0 ]; then + echo "错误: 激活 Conda 环境 '${CONDA_ENV_NAME}' 失败!" + exit 1 + fi + echo "Conda 环境 '${CONDA_ENV_NAME}' 已成功激活。" +else + echo "错误: 找不到 conda.sh 脚本。请检查您的 CONDA_BASE_PATH 设置是否正确。" + exit 1 +fi + +echo "正在为当前终端会话设置 HTTP/HTTPS 代理..." +export https_proxy=http://127.0.0.1:1080 +export http_proxy=http://127.0.0.1:1080 +echo "代理已设置为 http://127.0.0.1:1080" +echo "如果模型权重不存在,将通过此代理进行下载。" + +# --- 2. 模型与 GPU 配置 --- +# 根据您的 GPU 硬件和训练计划进行分组 +# yolo_train.py 会自动使用分配给它的所有可见 GPU +GPUS_GROUP_0="0" +GPUS_GROUP_1="1" +GPUS_GROUP_2="2" +GPUS_GROUP_3="3" +# TODO +GPUS_GROUP_4="0" +GPUS_GROUP_5="1" +GPUS_GROUP_6="2" +GPUS_GROUP_7="3" + +# GPUS_GROUP_4="4" +# GPUS_GROUP_5="5" +# GPUS_GROUP_6="6" +# GPUS_GROUP_7="7" + +# 从 yolo_config.py 中选择要训练的模型,并分配到不同的组 +# 建议将计算量/模型大小相似的模型放在同一组 +GROUP_0_MODELS=("YOLO11l-seg" "YOLOv8m-seg") +GROUP_1_MODELS=("YOLO11x-seg") +GROUP_2_MODELS=("YOLO11n-seg" "YOLO11s-seg" "YOLO11m-seg") +GROUP_3_MODELS=("YOLOv9e-seg") +GROUP_4_MODELS=("YOLOv9c-seg") +GROUP_5_MODELS=("YOLOv8x-seg") +GROUP_6_MODELS=("YOLOv8l-seg" "YOLO12-seg") # YOLOv9e-seg # 模型太大 +GROUP_7_MODELS=("YOLOv8n-seg" "YOLOv8s-seg" ) # 训练完毕 + +# 1. 从 config.py 中读取 OUTPUTS_DIR 的值 +OUTPUTS_DIR=$(python -c "from yolo_config import OUTPUTS_DIR; print(OUTPUTS_DIR)") +# 检查是否成功获取了 OUTPUTS_DIR +if [ -z "$OUTPUTS_DIR" ]; then + echo "OUTPUTS_DIR: $OUTPUTS_DIR" + echo "Error 1: Could not read OUTPUTS_DIR from yolo_config.py. Exiting." + echo "Error 2: Or the directory specified by OUTPUTS_DIR does not exist. Please create it first." + # exit 1 +fi +# 2. 定义带有时间戳的日志目录名 +LOG_DIR_NAME="yolo_train_logs_parallel_$(date +%Y-%m-%d_%H-%M-%S)" +# 3. 拼接成最终的完整路径 +LOG_DIR="$OUTPUTS_DIR/$LOG_DIR_NAME" +mkdir -p "${LOG_DIR}" +echo "所有模型的日志将保存在 ./${LOG_DIR}/ 目录中。" +echo "----------------------------------------------------" + + +# --- 3. 训练执行函数 --- +# 定义一个函数来启动一组训练,以避免代码重复 +start_training_group() { + # 使用 nameref (引用) 来传递数组 + local -n models=$1 + local gpus=$2 + local group_name=$3 + + echo ">>> 准备启动 ${group_name} 的训练任务 (后台运行)..." + # 遍历指定组中的所有模型 + for model_key in "${models[@]}"; do + echo " -> 正在后台启动模型: ${model_key} on GPUs: ${gpus}" + # 使用 '&' 将命令放入后台运行 + # 通过 --model 参数将模型名称传递给 yolo_train.py + CUDA_VISIBLE_DEVICES=${gpus} python yolo_train.py --model "${model_key}" > "${LOG_DIR}/${model_key}.log" 2>&1 & + echo " - 模型 ${model_key} 已在后台启动。日志文件: ${LOG_DIR}/${model_key}.log" + echo " - 等待 30 秒,确保 GPU 资源稳定分配..." + sleep 30 + done + echo ">>> ${group_name} 的所有模型均已启动。" + echo "----------------------------------------------------" +} + +# --- 4. 依次启动所有训练任务 --- +# 脚本将快速地按顺序启动每一组任务到后台 +start_training_group GROUP_0_MODELS "${GPUS_GROUP_0}" "第零组" # TODO +start_training_group GROUP_1_MODELS "${GPUS_GROUP_1}" "第一组" +start_training_group GROUP_2_MODELS "${GPUS_GROUP_2}" "第二组" +start_training_group GROUP_3_MODELS "${GPUS_GROUP_3}" "第三组" + + +# --- 5. 等待所有后台任务完成 --- +echo "" +echo "--- 所有模型均已在后台启动。现在等待所有训练任务完成... ---" +# 'wait' 命令会暂停脚本,直到所有由此脚本启动的后台子进程全部执行完毕 +wait +echo "--- 所有后台训练任务已全部完成! ---" + +# 适用于4卡版本 +start_training_group GROUP_4_MODELS "${GPUS_GROUP_4}" "第四组" # TODO +start_training_group GROUP_5_MODELS "${GPUS_GROUP_5}" "第五组" +start_training_group GROUP_6_MODELS "${GPUS_GROUP_6}" "第六组" +start_training_group GROUP_7_MODELS "${GPUS_GROUP_7}" "第七组" +# --- 5. 等待所有后台任务完成 --- +echo "" +echo "--- 所有模型均已在后台启动。现在等待所有训练任务完成... ---" +# 'wait' 命令会暂停脚本,直到所有由此脚本启动的后台子进程全部执行完毕 +wait +echo "--- 所有后台训练任务已全部完成! ---" + +# --- 6. 退出脚本 --- +# 训练完成,取消激活 Conda 环境 +echo "训练流程结束。" +# --- 取消网络代理设置 --- +echo "正在取消当前终端会话的 HTTP/HTTPS 代理设置..." +unset https_proxy +unset http_proxy +echo "代理已取消。" +conda deactivate +echo "取消激活 Conda 环境。" diff --git a/Seg_All_In_One_YoloModel/※2025_9_21_使用手册 b/Seg_All_In_One_YoloModel/※2025_9_21_使用手册 new file mode 100644 index 0000000..d2c5a64 --- /dev/null +++ b/Seg_All_In_One_YoloModel/※2025_9_21_使用手册 @@ -0,0 +1,100 @@ +############## A. 配置环境 ############## +pip install ultralytics grad-cam +修改 /home/wkmgc/miniconda3/envs/SMP/lib/python3.9/site-packages/pytorch_grad_cam/base_cam.py +将 + if targets is None: + target_categories = np.argmax(outputs.cpu().data.numpy(), axis=-1) + targets = [ClassifierOutputTarget(category) for category in target_categories] +变为 + if targets is None: + # 循环解包,直到找到第一个非元组的元素,这可以处理嵌套元组,例如 ((Tensor, ...), ...) + processed_outputs = outputs + print(f"DEBUG: 原始 outputs 类型: {type(processed_outputs)}") + while isinstance(processed_outputs, tuple): + if len(processed_outputs) == 0: + # 如果遇到一个空元组,我们就无法继续,打印错误并退出循环 + print(f"ERROR: 在解包时遇到空元组。原始 outputs: {outputs}") + break + print(f"DEBUG: 正在解包元组... 选择第 0 个元素。") + processed_outputs = processed_outputs[0] # 逐次选择第一个元素 + print(f"DEBUG: 最终选定的 outputs 类型: {type(processed_outputs)}") + + # 现在,processed_outputs 应该是我们需要的张量 (Tensor) + try: + target_categories = np.argmax(processed_outputs.cpu().data.numpy(), axis=-1) + targets = [ClassifierOutputTarget(category) for category in target_categories] + except AttributeError as e: + print(f"ERROR: 最终选定的元素 (类型: {type(processed_outputs)}) 无法处理。") + print(f" 它没有 .cpu() 属性。原始错误: {e}") + print(f" 请检查您的模型输出结构。原始 outputs: {outputs}") + raise e # 重新抛出异常,以便程序停止 + +############## B. 数据集构建(可选) ############## +参考:./Yolo数据集构建 + +############## C. Train 训练程序 ############## +# A. 第一次训练开一个梯子【需要下载内容】 +export https_proxy=http://127.0.0.1:1080 http_proxy=http://127.0.0.1:1080 +CUDA_VISIBLE_DEVICES=0 python train.py --model {YOLOv8n-seg, YOLOv8s-seg, YOLOv8m-seg, YOLOv8l-seg, YOLOv8x-seg, YOLOv9e-seg, YOLOv9c-seg, YOLO11l-seg, YOLO11n-seg, YOLO11s-seg, YOLO11m-seg, YOLO11x-seg, YOLO12-seg, YOLO12-seg} + +# B. 运行单个训练程序 +1. 在 dataset.yaml 中修改 训练数据集;CUDA_VISIBLE_DEVICES修改使用显卡;--model 修改算法; +2. CUDA_VISIBLE_DEVICES=0 python yolo_train.py --model "YOLOv8n-seg" + +# C. 批量运行训练程序 +1. train.sh 修改想让它使用的显卡、算法; +2. bash yolo_train.sh +3. 会生成 ./yolo_logs_parallel_DATE 的终端记录文件;结果先存储在 ../DataSet_Public_outputs/DATASET-Yolo 后自动移动到 ../Hardisk/DATASET_outputs-SegModel 中 + +############## D. Predict 推理程序 ############## +# A. 预先步骤:将模型同步到 Nas_BackUp_Seg 或 ./Hardisk 文件夹中【cd .. && bash ./Back_Up.sh】 +# B1. 将最优模型文件从 ./Nas_BackUp_Seg 移动到 ./BestMode_Predict_Results_DataSet_Public 指定文件夹中 +bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "best.pt" # "epoch100.pt" # 修改里面的路径 +# B2. 如果需要处理自定义数据集,请将模型文件夹手动复制为 ./BestMode_Predict_Results_DataSet_Public/DATASET-Yolo 中 +可以先对原有 ./BestMode_Predict_Results_DataSet_Public/ORI_DATASET-Yolo 临时改名 +运行 bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "best.pt" # "epoch100.pt" # 修改里面的路径 +在将生成的 ./BestMode_Predict_Results_DataSet_Public/ORI_DATASET-Yolo 改为 ./BestMode_Predict_Results_DataSet_Public/DATASET-Yolo +# C. 运行单个推理程序 +1. 在 dataset.yaml 中修改 预测数据集 及 val/test; yolo_config.py 中需修改模型保存路径 PREDICT_ALL_BEST_MODELS_DIR/PREDICT_BEST_MODEL_DIR;CUDA_VISIBLE_DEVICES修改使用显卡;--model 修改算法; +2. CUDA_VISIBLE_DEVICES=0 python yolo_predict_V1_NoColor.py --model "YOLOv8n-seg" +CUDA_VISIBLE_DEVICES=0 python yolo_predict_V2.py --model "YOLOv8n-seg" --conf 0.2 --pt_name "epoch100.pt" # "epoch100.pt" # "best.pt" +# D. 批量运行推理程序(分割图可视化 或 热图可视化) +1. yolo_predict.sh 修改想让它使用的显卡、算法; +2. bash yolo_predict.sh --conf 0.2 --pt_name "epoch100.pt" # "best.pt"(默认)# 分割图可视化 +3. bash yolo_predict.sh --conf 0.2 --heatmap_method "All" --pt_name "epoch100.pt" # "best.pt"(默认)# 热图可视化 +4. 会生成 ./yolo_predict_logs_parallel_DATE 的终端记录文件;结果存储在 ../BestMode_Predict_Results_DataSet_Public/DATASET-Yolo 中 +# E. 横向对比训练结果 +1. 将模型结果进行横向对比(生成color_mask、Yolo_result的横向对比) +python yolo_predict_V2_compare_all.py --pt_name "all" # "epoch100.pt" # "best.pt" # "all"(默认) + +############## E. Predict_raw_img_Check 检测推理输出图片是否齐全 ############## +# 检测 dataset.yaml path/"labels"/test.name 和 yolo_config.PREDICT_BEST_MODEL_DIR/****/predicted_raw_masks 中图片是否匹配 +1. 在 dataset.yaml 中修改 path、test;在yolo_config.PREDICT_ALL_BEST_MODELS_DIR;CUDA_VISIBLE_DEVICES修改使用显卡; +2. python yolo_predict_raw_masks_check.py --pt_name "best.pt" # "epoch100.pt" # "best.pt"(默认) + +############## F. 神经网络热图可视化,目前只支持EigenCAM(无指定类别的方法) ############## +# A. 预先步骤:cd ./Yolo可视化测试; +python yolo_layer_tester.py --model "YOLO12-seg" --cam_method "GradCAM" --pt_name "best.pt" # 测试各个层是否能成功生成热图,选择合适的层 +vim ../yolo_predict_visualize_nn.py # 修改 visualize_nn_comprehensive 中 model_key.lower().startswith("yoloXXX") 的部分,添加对应模型的默认层 +# B. 运行单个热图可视化程序 +# YOLOv8n-seg,YOLOv8s-seg,YOLOv8m-seg,YOLOv8l-seg,YOLOv8x-seg,YOLOv9c-seg,YOLOv9e-seg,YOLO11n-seg,YOLO11s-seg,YOLO11m-seg,YOLO11l-seg,YOLO11x-seg,YOLO12-seg +python yolo_predict_visualize_nn.py --model "YOLO11m-seg" --target_layers "default" --cam_method "All" --pt_name "best.pt" # "epoch100.pt" # "best.pt" + +############## G.※ 快速进行分割实验 ※ ############## +# 1. 修改 dataset.yaml 中 训练数据集、修改 yolo_config.py 中 EPOCHS、PATIENCE +cd ~/Desktop/Seg/Seg_All_In_One_YoloModel +# 2. 批量化训练 +bash yolo_train.sh +# 3. 复制最优模型到预测文件夹 +bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "best.pt" && bash ./Tool_Yolo_Copy_Best_Model.sh --pt_name "epoch100.pt" +# 4. 批量化预测+热度图可视化 +bash yolo_predict.sh --conf 0.2 --pt_name "epoch100.pt" && bash yolo_predict.sh --conf 0.2 +bash ./yolo_predict.sh --heatmap_method "All" && bash ./yolo_predict.sh --pt_name "epoch100.pt" --heatmap_method "All" +# 5. 横向对比结果 +python yolo_predict_V2_compare_all.py +# 6. 打包预测结果(不包含*.pt模型文件) +cd /home/wkmgc/Desktop/Seg/BestMode_Predict_Results_DataSet_Public/ +zip -r /home/wkmgc/Desktop/5_My_Gastric_2025_10_29_938-Yolo.zip 5_My_Gastric_2025_10_29_938*-Yolo -x "*.pt" +# 7. 打包训练结果(只有.png、.jpg、.csv文件) +cd /home/wkmgc/Desktop/Seg/Hardisk/ +zip -r /home/wkmgc/Desktop/5_My_Gastric_2025_10_29_938-Yolo_train.zip 5_My_Gastric_2025_10_29_938-Yolo -i \*.png \*.jpg \*.csv \ No newline at end of file diff --git a/Seg_Predict_Own_Video_V2/1_Save_Frame_V1.py b/Seg_Predict_Own_Video_V2/1_Save_Frame_V1.py new file mode 100644 index 0000000..25a0ba8 --- /dev/null +++ b/Seg_Predict_Own_Video_V2/1_Save_Frame_V1.py @@ -0,0 +1,158 @@ +import cv2 +import os +import argparse + +def extract_frames(video_path, interval_sec, resize_dim=None, base_output_dir=None): + """ + 从视频中按指定时间间隔提取帧并保存,可选择调整大小和指定输出目录。 + + 参数: + video_path (str): 输入视频文件的完整路径。 + interval_sec (float): 截取帧的时间间隔(秒)。 + resize_dim (tuple or None): (width, height) 元组,用于调整帧大小。 + base_output_dir (str): 保存帧的基础目录。 + """ + + # 1. 检查视频文件是否存在 + if not os.path.exists(video_path): + print(f"错误: 视频文件不存在于路径 {video_path}") + return + + # 2. 打开视频文件 + cap = cv2.VideoCapture(video_path) + if not cap.isOpened(): + print(f"错误: 无法打开视频文件 {video_path}") + return + + print(f"正在处理视频: {video_path}") + if resize_dim: + print(f"将调整所有输出帧大小为: {resize_dim[0]}x{resize_dim[1]}") + + # 3. 构建输出目录 + # 从路径中获取不带扩展名的文件名, e.g., "my_video" + video_filename_with_ext = os.path.basename(video_path) + video_name, _ = os.path.splitext(video_filename_with_ext) + + # --- 更新的目录逻辑 --- + # 构建最终的完整输出路径 + # 结构: //images/val + output_dir = os.path.join(base_output_dir, video_name, "images", "val") + # --- 结束更新 --- + + # 4. 创建目录 (如果不存在) + try: + os.makedirs(output_dir, exist_ok=True) + print(f"帧将保存到: {output_dir}") + except OSError as e: + print(f"错误: 无法创建目录 {output_dir}. 错误信息: {e}") + cap.release() + return + + # 5. 循环处理视频帧 + save_count = 0 + interval_msec = interval_sec * 1000 + next_save_time_msec = 0.0 + + while True: + ret, frame = cap.read() + if not ret: + break + + current_msec = cap.get(cv2.CAP_PROP_POS_MSEC) + + if current_msec >= next_save_time_msec: + frame_to_save = frame + + # 调整大小 + if resize_dim: + frame_to_save = cv2.resize(frame, resize_dim, interpolation=cv2.INTER_AREA) + + # 构建保存路径 + # --- 修改文件名逻辑:使用时间戳 --- + # 1. 获取当前毫秒时间戳 + msec = current_msec + + # 2. 转换为 时、分、秒、毫秒 + total_seconds_int = int(msec // 1000) + milliseconds = int(msec % 1000) + + hours = total_seconds_int // 3600 + minutes = (total_seconds_int % 3600) // 60 + seconds = total_seconds_int % 60 + + # 3. 格式化文件名: 格式为 HH-MM-SS-msmsms.jpg (时-分-秒-毫秒) + # 例如: 00-00-01-500.jpg (表示 1.5 秒时) + frame_filename = f"{hours:02d}-{minutes:02d}-{seconds:02d}-{milliseconds:03d}.jpg" + save_path = os.path.join(output_dir, frame_filename) + # --- 结束修改 --- + + # 保存 + cv2.imwrite(save_path, frame_to_save) + + print(f"已保存: {save_path} (时间戳: {current_msec/1000:.3f}s)") + save_count += 1 + next_save_time_msec += interval_msec + + # 6. 释放资源 + cap.release() + print(f"\n处理完成。") + print(f"总共保存了 {save_count} 帧到目录 {output_dir}") + +def main(): + parser = argparse.ArgumentParser(description="视频帧提取脚本") + + parser.add_argument( + "-v", "--video", + type=str, + required=True, + help="[必需] 输入视频文件的路径 (例如: /home/user/videos/my_test.mp4)" + ) + + parser.add_argument( + "-i", "--interval", + type=float, + default=0.5, + help="[可选] 截取帧的时间间隔 (秒). 默认: 0.5" + ) + + parser.add_argument( + "-r", "--resize", + type=str, + default=None, + help="[可选] 将输出帧调整为 'WIDTHxHEIGHT' 格式 (例如: '1920x1080')" + ) + + # --- 新增参数: --output_dir --- + default_output_base = os.path.join("..", "DataSet_Public", "5_Predict_Video") + parser.add_argument( + "-o", "--output_dir", + type=str, + default=default_output_base, + help=f"[可选] 保存帧的基础目录. " + f"最终路径将是: /<视频名>/images/val. " + f"默认: {default_output_base}" + ) + + args = parser.parse_args() + + # --- 解析 resize 参数 --- + resize_dim = None + if args.resize: + try: + width_str, height_str = args.resize.split('x') + width = int(width_str) + height = int(height_str) + if width <= 0 or height <= 0: + raise ValueError("宽度和高度必须为正数") + resize_dim = (width, height) + except ValueError as e: + print(f"错误: 无效的 --resize 格式 '{args.resize}'.") + print("必须使用 'WIDTHxHEIGHT' 格式 (例如: '1920x1080').") + return + + # --- 调用主处理函数 --- + # 传入新参数 args.output_dir + extract_frames(args.video, args.interval, resize_dim, args.output_dir) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Seg_Predict_Own_Video_V2/1_Save_Frame_V2.py b/Seg_Predict_Own_Video_V2/1_Save_Frame_V2.py new file mode 100644 index 0000000..0efed6b --- /dev/null +++ b/Seg_Predict_Own_Video_V2/1_Save_Frame_V2.py @@ -0,0 +1,185 @@ +import cv2 +import os +import argparse +from concurrent.futures import ThreadPoolExecutor +from tqdm import tqdm # 导入 tqdm + +def process_and_save_frame(frame, save_path, resize_dim): + """ + 一个在工作线程中运行的辅助函数,用于调整帧大小并将其保存到磁盘。 + + 参数: + frame (numpy.ndarray): 要处理的帧 (应该是 .copy() 过的) + save_path (str): 完整的保存路径 + resize_dim (tuple or None): (width, height) 调整大小的元组 + """ + try: + frame_to_save = frame + + # 调整大小 (CPU密集型, 但OpenCV通常会释放GIL) + if resize_dim: + frame_to_save = cv2.resize(frame, resize_dim, interpolation=cv2.INTER_AREA) + + # 保存 (I/O 密集型) + cv2.imwrite(save_path, frame_to_save) + + # print(f"已保存: {save_path} (时间戳: {current_msec/1000:.3f}s)") + # 注意:在tqdm循环中打印会打乱进度条,故注释掉 + + except Exception as e: + print(f"错误: 无法保存帧 {save_path}. 错误: {e}") + +def extract_frames(video_path, interval_sec, resize_dim=None, base_output_dir=None): + """ + 从视频中按指定时间间隔提取帧并保存,可选择调整大小和指定输出目录。 + (已更新为使用 ThreadPoolExecutor 和 tqdm) + """ + + # 1. 检查视频文件是否存在 + if not os.path.exists(video_path): + print(f"错误: 视频文件不存在于路径 {video_path}") + return + + # 2. 打开视频文件 + cap = cv2.VideoCapture(video_path) + if not cap.isOpened(): + print(f"错误: 无法打开视频文件 {video_path}") + return + + # --- 获取视频总帧数以用于 tqdm --- + try: + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + if total_frames <= 0: + print("警告: 无法获取视频总帧数。进度条可能无法正确显示。") + total_frames = None # 设为 None, tqdm 会显示为未知进度 + except Exception: + total_frames = None + + print(f"正在处理视频: {video_path}") + if resize_dim: + print(f"将调整所有输出帧大小为: {resize_dim[0]}x{resize_dim[1]}") + + # 3. 构建输出目录 + video_filename_with_ext = os.path.basename(video_path) + video_name, _ = os.path.splitext(video_filename_with_ext) + output_dir = os.path.join(base_output_dir, video_name, "images", "val") + + # 4. 创建目录 (如果不存在) + try: + os.makedirs(output_dir, exist_ok=True) + print(f"帧将保存到: {output_dir}") + except OSError as e: + print(f"错误: 无法创建目录 {output_dir}. 错误信息: {e}") + cap.release() + return + + # 5. 循环处理视频帧 (使用并行和tqdm) + save_count = 0 + interval_msec = interval_sec * 1000 + next_save_time_msec = 0.0 + + # 使用 ThreadPoolExecutor 来并行处理 I/O 密集型任务 (保存图片) + # 使用 tqdm 显示进度条 + + # max_workers=None 会自动使用合理的线程数 (通常是 CPU核心数 * 5) + with ThreadPoolExecutor(max_workers=None) as executor: + + # 使用 tqdm 包裹循环 + with tqdm(total=total_frames, unit="frames", desc=f"Processing {video_name}") as pbar: + while True: + ret, frame = cap.read() + if not ret: + break # 视频结束 + + # 无论是否保存,tqdm 进度条都更新 (表示已读取一帧) + pbar.update(1) + + current_msec = cap.get(cv2.CAP_PROP_POS_MSEC) + + if current_msec >= next_save_time_msec: + frame_to_save = frame + + # 构建保存路径 + msec = current_msec + total_seconds_int = int(msec // 1000) + milliseconds = int(msec % 1000) + hours = total_seconds_int // 3600 + minutes = (total_seconds_int % 3600) // 60 + seconds = total_seconds_int % 60 + + frame_filename = f"{hours:02d}-{minutes:02d}-{seconds:02d}-{milliseconds:03d}.jpg" + save_path = os.path.join(output_dir, frame_filename) + + # --- 并行处理 --- + # 将“调整大小和保存”任务提交到线程池 + # 必须使用 frame.copy()! + # 因为 'frame' 是一个可重用缓冲区,主循环会立即用下一帧覆盖它 + # 如果不复制,所有线程最终可能保存的是同一帧(或损坏的数据) + executor.submit(process_and_save_frame, frame.copy(), save_path, resize_dim) + # --------------- + + save_count += 1 + next_save_time_msec += interval_msec + + # 6. 释放资源 + # 'with' 语句块结束时,executor 会自动调用 shutdown(wait=True), + # 确保所有保存任务完成后才继续。 + cap.release() + print(f"\n处理完成。") + print(f"总共提交了 {save_count} 帧到目录 {output_dir}") + + +def main(): + parser = argparse.ArgumentParser(description="视频帧提取脚本 (并行版)") + + parser.add_argument( + "-v", "--video", + type=str, + required=True, + help="[必需] 输入视频文件的路径 (例如: /home/user/videos/my_test.mp4)" + ) + + parser.add_argument( + "-i", "--interval", + type=float, + default=0.5, + help="[可选] 截取帧的时间间隔 (秒). 默认: 0.5" + ) + + parser.add_argument( + "-r", "--resize", + type=str, + default=None, + help="[可选] 将输出帧调整为 'WIDTHxHEIGHT' 格式 (例如: '1920x1080')" + ) + + default_output_base = os.path.join("..", "DataSet_Public", "5_Predict_Video") + parser.add_argument( + "-o", "--output_dir", + type=str, + default=default_output_base, + help=f"[可选] 保存帧的基础目录. " + f"最终路径将是: /<视频名>/images/val. " + f"默认: {default_output_base}" + ) + + args = parser.parse_args() + + resize_dim = None + if args.resize: + try: + width_str, height_str = args.resize.split('x') + width = int(width_str) + height = int(height_str) + if width <= 0 or height <= 0: + raise ValueError("宽度和高度必须为正数") + resize_dim = (width, height) + except ValueError as e: + print(f"错误: 无效的 --resize 格式 '{args.resize}'.") + print("必须使用 'WIDTHxHEIGHT' 格式 (例如: '1920x1080').") + return + + extract_frames(args.video, args.interval, resize_dim, args.output_dir) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Seg_Predict_Own_Video_V2/使用手册 b/Seg_Predict_Own_Video_V2/使用手册 new file mode 100644 index 0000000..e07754b --- /dev/null +++ b/Seg_Predict_Own_Video_V2/使用手册 @@ -0,0 +1,2 @@ +# 1. 保存视频帧为图片 +python 1_Save_Frame_V2.py --video ./LC_Video_1.mp4 --resize "1920x1080" --output_dir "../DataSet_Public/5_Predict_Video" --interval 0.5 diff --git a/Seg_Predict_YoloModel/yolo_Seg_Video-V1-Visible.py b/Seg_Predict_YoloModel/yolo_Seg_Video-V1-Visible.py new file mode 100644 index 0000000..13b0bc7 --- /dev/null +++ b/Seg_Predict_YoloModel/yolo_Seg_Video-V1-Visible.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- + +import cv2 +import numpy as np +from ultralytics import YOLO +import random +import os +from tqdm import tqdm # 引入进度条库 + +# --- 配置参数 --- + +# 模型文件路径 +# MODEL_PATH = os.path.join('YOLOv9e-seg', 'weights', 'best.pt') +MODEL_PATH = os.path.join('YOLO11l-seg', 'best.pt') +# 输入视频文件路径 +VIDEO_IN_PATH = 'LC_Video_1.mp4' +# 输出视频文件路径 +VIDEO_OUT_PATH = 'output_a_segmented_fps_controlled.mp4' + +# --- 新增配置 --- +# 要处理的视频帧率。例如,设置为 5 表示每秒大约只对 5 帧进行模型推理。 +# 设置为 None 则处理所有帧。 +PROCESS_FPS = None + +# 推理参数 +CONFIDENCE_THRESHOLD = 0.3 # 置信度阈值,低于此值的检测将被忽略 +MASK_ALPHA = 0.4 # 分割掩码的透明度 (0.0 完全透明, 1.0 完全不透明) + +# --- 主处理逻辑 --- + +def main(): + """ + 主函数,执行视频分割的完整流程。 + """ + # 1. 加载预训练的 YOLOv9 分割模型 + # YOLO() 类会自动处理模型的加载、权重初始化以及设备选择(优先使用可用的 GPU) + print(f"正在加载模型: {MODEL_PATH}...") + try: + model = YOLO(MODEL_PATH) + except Exception as e: + print(f"错误: 无法加载模型。请检查路径是否正确以及 Ultralytics 是否已正确安装。") + print(f"详细错误: {e}") + return + + # 获取模型能够识别的所有类别名称 + class_names = model.names + print(f"模型已加载,可识别 {len(class_names)} 个类别。") + + # 2. 初始化视频读写对象 + # 使用 OpenCV 打开输入视频文件 + cap = cv2.VideoCapture(VIDEO_IN_PATH) + if not cap.isOpened(): + print(f"错误: 无法打开视频文件: {VIDEO_IN_PATH}") + return + + # 获取输入视频的属性(帧率、宽度、高度、总帧数) + fps = cap.get(cv2.CAP_PROP_FPS) + frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 获取总帧数用于进度条 + print(f"输入视频属性: {frame_width}x{frame_height} @ {fps:.2f} FPS, 总共 {total_frames} 帧") + + # 定义视频编码器并创建 VideoWriter 对象以保存输出视频 + # 'mp4v' 是适用于.mp4 文件的常用编码器 + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + out = cv2.VideoWriter(VIDEO_OUT_PATH, fourcc, fps, (frame_width, frame_height)) + + # 3. 为每个类别生成一个稳定的随机颜色 + # 这确保了在整个视频中,同一类别的对象掩码颜色保持一致 + random.seed(42) # 固定随机种子以保证每次运行颜色相同 + class_colors = { + name: [random.randint(0, 255) for _ in range(3)] + for name in class_names.values() + } + + # --- 新增:计算帧处理间隔 --- + frame_counter = 0 + frame_skip_interval = 1 + if PROCESS_FPS is not None and PROCESS_FPS > 0: + frame_skip_interval = round(fps / PROCESS_FPS) + # 确保至少为1,避免除零错误 + if frame_skip_interval < 1: + frame_skip_interval = 1 + print(f"处理帧率设置为 {PROCESS_FPS} FPS. 将每 {frame_skip_interval} 帧运行一次模型推理。") + else: + print("将处理所有帧。") + + # 4. 逐帧处理视频,并使用 tqdm 显示进度条 + print("开始逐帧处理视频... (在预览窗口激活时按 'q' 键可随时退出)") + for _ in tqdm(range(total_frames), desc="处理进度"): + # 从视频中读取一帧 + success, frame = cap.read() + + if not success: + # 如果视频读取结束,则跳出循环 + print("\n视频读取完毕或发生错误。") + break + + frame_counter += 1 + + # 5. 对当前帧执行 YOLOv9 推理 (有条件地) + # 仅在达到处理间隔时才运行模型,否则将重用上一次的结果 + if frame_counter % frame_skip_interval == 0: + results = model.predict(source=frame, conf=CONFIDENCE_THRESHOLD, verbose=False) + + # 检查是否有任何有效的 'results' 可供绘制 + # 'results' 可能来自当前帧的推理,或来自之前的帧 + # `locals()` 检查 `results` 变量是否已在当前作用域中定义 + if 'results' in locals() and results is not None: + # ------------------- FIX START ------------------- + # `results` 是一个列表,因为我们处理的是单张图片,所以需要取出第一个元素 + result = results[0] + # -------------------- FIX END -------------------- + + # 6. 可视化:绘制半透明掩码和边界框 + + # 创建一个与原始帧相同的副本,用于绘制掩码。这是实现透明效果的关键步骤。 + overlay = frame.copy() + + # 检查是否存在检测到的掩码 + if result.masks is not None: + # 遍历每个检测到的对象 + # zip() 函数将边界框和掩码一一对应起来 + for box, mask in zip(result.boxes, result.masks): + # 获取类别 ID 和类别名称 + class_id = int(box.cls) + class_name = class_names[class_id] + + # 获取该类别的预定义颜色 + color = class_colors[class_name] + + # --- 绘制分割掩码 --- + # [修正] mask.xy返回的是一个列表的列表,需要用np.array处理 + points = np.array(mask.xy, dtype=np.int32) + # 在 overlay 层上填充多边形(完全不透明) + cv2.fillPoly(overlay, [points], color) + + # --- 绘制边界框和标签 --- + # 获取边界框坐标 + x1, y1, x2, y2 = map(int, box.xyxy[0]) # box.xyxy也是列表,取第一个 + # 在原始帧上绘制矩形边界框 + cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) + + # 准备标签文本(类别名 + 置信度) + confidence = float(box.conf) + label = f"{class_name}: {confidence:.2f}" + + # 计算文本尺寸以绘制背景 + (text_width, text_height), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) + # 绘制文本背景框 + cv2.rectangle(frame, (x1, y1 - text_height - baseline), (x1 + text_width, y1), color, -1) + # 在背景框上放置文本 + cv2.putText(frame, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) + + # 7. 融合原始帧和掩码层 + # cv2.addWeighted() 将 overlay 和 frame 按照指定的权重(透明度)混合 + # 公式为: output = frame * (1 - alpha) + overlay * alpha + 0 + cv2.addWeighted(overlay, MASK_ALPHA, frame, 1 - MASK_ALPHA, 0, frame) + + # 8. 将处理后的帧写入输出视频文件 + out.write(frame) + + # # 9. [新增] 实时显示处理后的帧 + # cv2.imshow('Real-time Segmentation Preview', frame) + + # # 检测 'q' 键是否被按下,如果是则退出循环 + # # cv2.waitKey(1) 对于视频处理至关重要,它等待 1ms,允许 OpenCV 刷新窗口 + # if cv2.waitKey(1) & 0xFF == ord('q'): + # print("\n用户中断处理。") + # break + + # 10. 释放资源 + cap.release() + out.release() + # cv2.destroyAllWindows() + print(f"处理完成!") + print(f"输出视频已保存至: {VIDEO_OUT_PATH}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Seg_Predict_YoloModel/yolo_Seg_Video-V2-UnVisible.py b/Seg_Predict_YoloModel/yolo_Seg_Video-V2-UnVisible.py new file mode 100644 index 0000000..1c304b8 --- /dev/null +++ b/Seg_Predict_YoloModel/yolo_Seg_Video-V2-UnVisible.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- + +import cv2 +import numpy as np +from ultralytics import YOLO +import random +import os +from tqdm import tqdm +import multiprocessing as mp +import time + +# --- Configuration --- +MODEL_PATH = os.path.join('YOLOv9e-seg', 'weights', 'best.pt') +VIDEO_IN_PATH = 'LC_Video_1.mp4' +VIDEO_OUT_PATH = 'output_parallel_segmented.mp4' +CONFIDENCE_THRESHOLD = 0.3 +MASK_ALPHA = 0.4 + +# --- Parallel Processing Configuration --- +# Set the number of parallel processes. Using cpu_count() - 1 is a safe choice. +# You can adjust this based on your system's resources. +NUM_WORKERS = min(5, max(1, mp.cpu_count() - 1)) + + +def process_frame_worker(task_queue, results_queue, model_path, confidence_threshold): + """ + Worker function for parallel processing. + Each worker loads its own model instance and processes frames from the task queue. + """ + try: + model = YOLO(model_path) + except Exception as e: + print(f"[Worker PID: {os.getpid()}] Error loading model: {e}") + return + + while True: + task = task_queue.get() + # A None task is a signal to terminate the worker + if task is None: + break + + frame_index, frame = task + try: + results = model.predict(source=frame, conf=confidence_threshold, verbose=False) + # We only need the first result as we process one frame at a time + results_queue.put((frame_index, results[0])) + except Exception as e: + print(f"[Worker PID: {os.getpid()}] Error processing frame {frame_index}: {e}") + # Put a placeholder to avoid deadlocks + results_queue.put((frame_index, None)) + + +def main(): + """ + Main function to execute video segmentation using parallel processing. + """ + # 1. Initialize video capture and get properties + cap = cv2.VideoCapture(VIDEO_IN_PATH) + if not cap.isOpened(): + print(f"Error: Could not open video file: {VIDEO_IN_PATH}") + return + + fps = cap.get(cv2.CAP_PROP_FPS) + frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + print(f"Input video: {frame_width}x{frame_height} @ {fps:.2f} FPS, {total_frames} total frames") + print(f"Using {NUM_WORKERS} parallel workers for processing.") + + # 2. Initialize video writer + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + out = cv2.VideoWriter(VIDEO_OUT_PATH, fourcc, fps, (frame_width, frame_height)) + + # 3. Generate stable random colors for each class + # We load a temporary model instance just to get the class names + try: + temp_model = YOLO(MODEL_PATH) + class_names = temp_model.names + del temp_model + except Exception as e: + print(f"Fatal Error: Could not load model to get class names. Aborting. Details: {e}") + cap.release() + out.release() + return + + random.seed(42) + class_colors = {name: [random.randint(0, 255) for _ in range(3)] for name in class_names.values()} + print(f"Model recognizes {len(class_names)} classes.") + + # 4. Set up multiprocessing queues and processes + manager = mp.Manager() + # Queue for frames to be processed. Maxsize helps prevent memory overload. + task_queue = manager.Queue(maxsize=NUM_WORKERS * 2) + # Queue for processed results. + results_queue = manager.Queue(maxsize=NUM_WORKERS * 2) + + processes = [] + for _ in range(NUM_WORKERS): + p = mp.Process(target=process_frame_worker, args=(task_queue, results_queue, MODEL_PATH, CONFIDENCE_THRESHOLD)) + processes.append(p) + p.start() + + # 5. Main process: Read frames and dispatch to workers + frame_index = 0 + pbar_read = tqdm(total=total_frames, desc="Reading frames") + while True: + success, frame = cap.read() + if not success: + break + task_queue.put((frame_index, frame)) + frame_index += 1 + pbar_read.update(1) + pbar_read.close() + + # Signal workers to terminate by sending None tasks + for _ in range(NUM_WORKERS): + task_queue.put(None) + + # 6. Main process: Collect results, draw segmentation, and write to video + # This part must be sequential to ensure correct video order. + frames_to_write = {} + next_frame_to_write = 0 + pbar_write = tqdm(total=total_frames, desc="Processing & Writing") + + for _ in range(total_frames): + # Get a result from the queue (this might be out of order) + res_index, result = results_queue.get() + + # If a worker failed, the result might be None + if result is None: + # We need a placeholder frame. We'll re-read it. + # This is inefficient but robust against worker failure. + cap.set(cv2.CAP_PROP_POS_FRAMES, res_index) + _, frame = cap.read() + frames_to_write[res_index] = (frame, None) + else: + # Re-read the original frame to draw on. + # This avoids passing bulky frames through the results queue. + cap.set(cv2.CAP_PROP_POS_FRAMES, res_index) + _, frame = cap.read() + frames_to_write[res_index] = (frame, result) + + # Write all consecutive frames that are now available + while next_frame_to_write in frames_to_write: + frame, result = frames_to_write.pop(next_frame_to_write) + + if result is not None: + overlay = frame.copy() + if result.masks is not None: + for box, mask in zip(result.boxes, result.masks): + class_id = int(box.cls) + class_name = class_names[class_id] + color = class_colors[class_name] + + # Draw segmentation mask + points = np.array(mask.xy, dtype=np.int32) + cv2.fillPoly(overlay, [points], color) + + # Draw bounding box and label + x1, y1, x2, y2 = map(int, box.xyxy[0]) + cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) + + confidence = float(box.conf) + label = f"{class_name}: {confidence:.2f}" + (w, h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) + cv2.rectangle(frame, (x1, y1 - h - 5), (x1 + w, y1), color, -1) + cv2.putText(frame, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) + + # Blend the overlay with the original frame + cv2.addWeighted(overlay, MASK_ALPHA, frame, 1 - MASK_ALPHA, 0, frame) + + out.write(frame) + pbar_write.update(1) + next_frame_to_write += 1 + + pbar_write.close() + + # 7. Clean up all resources + for p in processes: + p.join() + + cap.release() + out.release() + print("Processing complete!") + print(f"Output video saved to: {VIDEO_OUT_PATH}") + + +if __name__ == "__main__": + # On Windows and macOS, 'fork' is not the default start method. + # 'spawn' or 'forkserver' are safer and required on these platforms. + # It's good practice to set it explicitly. + mp.set_start_method("spawn", force=True) + main() \ No newline at end of file diff --git a/Seg_Predict_YoloModel/yolo_config.py b/Seg_Predict_YoloModel/yolo_config.py new file mode 100644 index 0000000..dd89cb7 --- /dev/null +++ b/Seg_Predict_YoloModel/yolo_config.py @@ -0,0 +1,130 @@ +import yaml, sys +import torch +from pathlib import Path + +# --- 1. 核心目录设置 (Core Directories) --- +HARDISK_DIR = Path.home() / "Desktop" / "Seg" / "Hardisk" # 硬盘根目录 +BASE_DIR = Path(__file__).parent.parent # 当前脚本所在目录上级 +DATASET_YAML_PATH = Path(__file__).parent / "dataset.yaml" # 数据集的 YAML 配置文件路径 +OUTPUTS_DIR = BASE_DIR / "DataSet_Public_outputs" # 输出结果目录 # 最终为:OUTPUTS_DIR / dataset_name + +TEST_IMAGE_DIR = None # 初始化 TEST_IMAGE_DIR # 测试文件地址 dataset.path / TODO "val" / "test" TODO +# try: +# 3. 读取并解析 YAML 文件 +with open(DATASET_YAML_PATH, 'r', encoding='utf-8') as f: + yaml_data = yaml.safe_load(f) +# 4. 从解析后的数据中获取 'path' 的值 +relative_path_from_yaml = yaml_data.get('path') +test_path_from_yaml = yaml_data.get('test') +dataset_name = Path(relative_path_from_yaml).name +if relative_path_from_yaml: + # 5. 【核心步骤】构建绝对路径 + # relative_path_from_yaml 是相对于 .yaml 文件本身的路径。 + # 所以,我们需要获取 .yaml 文件所在的目录,然后与这个相对路径拼接。 + yaml_file_directory = DATASET_YAML_PATH.parent + TEST_IMAGE_DIR = (DATASET_YAML_PATH.parent / relative_path_from_yaml / test_path_from_yaml).resolve() # 这里val 或 test在dataset.yaml中定义 + # 使用 .exists() 方法来检查路径是否存在 + if not TEST_IMAGE_DIR.exists(): + # 如果路径不存在,则执行这里的代码 + print(f"警告: 测试图片目录不存在: {TEST_IMAGE_DIR}") + sys.exit(1) + # 6. 获取OUTPUTS_DIR + if dataset_name and dataset_name not in {'.', '..'}: + dataset_name_ = dataset_name + "-Yolo" + OUTPUTS_DIR = OUTPUTS_DIR / dataset_name_ + print(f"设定输出路径为: '{str(OUTPUTS_DIR)}'") + else: + print(f"警告: 提取的dataset_name: '{dataset_name}' 无效(为空、'.' 或 '..')。") + sys.exit(1) +else: + print(f"警告: 在 '{DATASET_YAML_PATH}' 文件中没有找到 'path' 键。") + sys.exit(1) +# 4. 从解析后的数据中获取 'path' 的值 +relative_path_from_yaml = yaml_data.get('path') +# except FileNotFoundError: +# print(f"错误: YAML 配置文件未找到: '{DATASET_YAML_PATH}'") +# except Exception as e: +# print(f"读取或解析 YAML 文件时发生错误: {e}") + + +# ============================================================================== +# --- 新增:模型选择 (只需修改这里) --- +# 你想要使用的模型名称,从下面的 MODEL_CONFIGS 字典中选择一个键。 +SELECTED_MODEL = 'YOLOv9e-seg' +# ============================================================================== + + +# --- 修改:模型、图像大小和批处理大小的集成配置 --- +# 将所有模型的配置(权重、图像大小、批处理大小)集中管理 +MODEL_CONFIGS = { + # YOLOv8 + 'YOLOv8n-seg': {'weights': 'yolov8n-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLOv8s-seg': {'weights': 'yolov8s-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLOv8m-seg': {'weights': 'yolov8m-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLOv8l-seg': {'weights': 'yolov8l-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,12.5GB + 'YOLOv8x-seg': {'weights': 'yolov8x-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,15.5GB # 示例:X模型使用1280分辨率 + # YOLOv9 + 'YOLOv9c-seg': {'weights': 'yolov9c-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,13GB + 'YOLOv9e-seg': {'weights': 'yolov9e-seg.pt', 'image_size': 640, 'batch_size': 8}, # 640,16,内存超了 # 示例:E模型(最大)使用1280分辨率和更小的batch + # YOLOv11 (假设) + 'YOLO11n-seg': {'weights': 'yolo11n-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLO11s-seg': {'weights': 'yolo11s-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLO11m-seg': {'weights': 'yolo11m-seg.pt', 'image_size': 640, 'batch_size': 16}, + 'YOLO11l-seg': {'weights': 'yolo11l-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,12.5GB + 'YOLO11x-seg': {'weights': 'yolo11x-seg.pt', 'image_size': 640, 'batch_size': 16}, # 640,16,19.5GB + # YOLOv12 (假设) + 'YOLO12-seg': {'weights': str(Path(__file__).parent / 'yolo12-seg.yaml'), 'image_size': 640, 'batch_size': 16}, # 640,16,3GB +} + +# --- 新增:根据选择自动加载配置 --- +# 检查所选模型是否存在于配置字典中 +if SELECTED_MODEL in MODEL_CONFIGS: + config = MODEL_CONFIGS[SELECTED_MODEL] + MODEL_WEIGHTS = config['weights'] + IMAGE_SIZE = config['image_size'] + BATCH_SIZE = config['batch_size'] + print(f"--- 模型配置加载成功 ---") + print(f"模型名称: {SELECTED_MODEL}") + print(f"权重文件: {MODEL_WEIGHTS}") + print(f"图像大小: {IMAGE_SIZE}") + print(f"批处理大小: {BATCH_SIZE}") + print(f"------------------------") +else: + print(f"错误: 所选模型 '{SELECTED_MODEL}' 不在配置列表中。") + print("可用模型包括: ", list(MODEL_CONFIGS.keys())) + sys.exit(1) + + +# --- 4. 训练超参数 (Training Hyperparameters) --- +EPOCHS = 300 # 训练轮次 +PATIENCE = 100 # 提前停止训练的轮数 +SAVE_PERIOD = 10 # 每隔多少轮保存一次模型 +# BATCH_SIZE 已在上面根据模型自动设置 +LEARNING_RATE = 0.01 # 初始学习率 +OPTIMIZER = 'Adam' # 优化器 (TODO 'SGD', 'Adam', 'AdamW', 'auto' TODO) +WORKERS = 4 # 数据加载的工作线程数 +# --- 动态设备选择 (Dynamic Device Selection) --- +def get_auto_device(): + """自动检测并返回最合适的设备""" + if torch.cuda.is_available(): + gpu_count = torch.cuda.device_count() + if gpu_count > 1: + # 如果有多个GPU,使用所有GPU + device_str = ",".join(str(i) for i in range(gpu_count)) + print(f"检测到 {gpu_count} 个可用的GPU。将使用所有GPU: {device_str}") + return device_str + else: + # 如果只有1个GPU + print("检测到 1 个可用的GPU。将使用 GPU: 0") + return '0' + else: + # 如果没有可用的GPU,使用CPU + print("未检测到可用的GPU。将使用CPU。") + return 'cpu' +DEVICE = get_auto_device() # 调用函数来设置设备 + +# --- 5. 预测设置 (Prediction Settings) --- + +SHOW_LABELS = True # 是否在预测结果上显示类别标签 +SHOW_CONF = True # 是否在预测结果上显示置信度和边界框 +SAVE_PREDICTIONS = True # 是否保存预测结果图像 \ No newline at end of file diff --git a/Seg_Predict_YoloModel/yolo_train.py b/Seg_Predict_YoloModel/yolo_train.py new file mode 100644 index 0000000..6e4d283 --- /dev/null +++ b/Seg_Predict_YoloModel/yolo_train.py @@ -0,0 +1,113 @@ +import argparse, shutil +import logging +from ultralytics import YOLO +from datetime import datetime +import yolo_config as config +import gc, torch + +# --- 日志设置 --- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[logging.StreamHandler()] +) + +def train_model(model_key: str): + """ + 根据给定的模型密钥训练YOLO分割模型。 + + Args: + model_key (str): 在 yolo_config.MODEL_CONFIGS 中定义的模型密钥。 + """ + # 生成时间戳以区分不同训练运行 + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + project_folder = f"{model_key}_{timestamp}" + + if model_key not in config.MODEL_CONFIGS: + logging.error(f"错误:模型 '{model_key}' 不在 yolo_config.py 中定义。") + logging.info(f"可用模型: {list(config.MODEL_CONFIGS.keys())}") + return + + model_config = config.MODEL_CONFIGS[model_key] + model_file = model_config['weights'] + logging.info(f"开始训练模型: {model_key} ({model_file})") + + try: + # --- 1. 加载模型 --- + model = YOLO(model_file) # 如果没有模型会自动下载 + logging.info("模型加载成功。") + + # --- 2. 模型训练 --- + logging.info(f"数据集配置文件: {config.DATASET_YAML_PATH}") + logging.info(f"训练参数: Epochs={config.EPOCHS}, Batch Size={config.BATCH_SIZE}, Img Size={config.IMAGE_SIZE}") + + model.train( + data=str(config.DATASET_YAML_PATH), + epochs=config.EPOCHS, + imgsz=config.IMAGE_SIZE, + batch=config.BATCH_SIZE, + optimizer=config.OPTIMIZER, + lr0=config.LEARNING_RATE, + device=config.DEVICE, + project=str(config.OUTPUTS_DIR), # 指定输出的根目录 + name=project_folder, # 指定本次训练的项目名 + workers=config.WORKERS, + exist_ok=True, # 如果项目已存在,则覆盖 + patience=config.PATIENCE, # 提前停止训练的轮数 + save_period=config.SAVE_PERIOD # 每隔多少轮保存一次模型 + ) + + logging.info("模型训练完成。") + logging.info(f"训练结果保存在: {config.OUTPUTS_DIR / project_folder}") + return project_folder + + except Exception as e: + logging.error(f"训练过程中发生错误: {e}") + return None + +# --- 3. 复制结果到硬盘并删除原文件夹 (新增逻辑) --- +def move_results_to_hardisk(project_folder: str): + source_dir = config.OUTPUTS_DIR / project_folder + # 确保目标硬盘目录存在 + outputs_folder_name = config.OUTPUTS_DIR.name + destination_dir = config.HARDISK_DIR / outputs_folder_name / project_folder + destination_dir.parent.mkdir(parents=True, exist_ok=True) + logging.info(f"准备将结果从 {source_dir} 移动到 {destination_dir}...") + try: + # 步骤 1: 复制文件夹 + shutil.copytree(source_dir, destination_dir) + logging.info(f"成功复制结果到: {destination_dir}") + + # 步骤 2: 复制成功后,删除原文件夹 + logging.info(f"正在删除原文件夹: {source_dir}") + shutil.rmtree(source_dir) + logging.info("成功删除原文件夹。") + logging.info(f"任务完成,最终结果已保存至: {destination_dir}") + + except Exception as e: + logging.error(f"移动文件夹时发生错误: {e}") + logging.warning(f"原始训练结果仍保留在: {source_dir}") + +if __name__ == "__main__": + # 创建命令行参数解析器 + parser = argparse.ArgumentParser(description="训练 YOLO 分割模型。") + parser.add_argument( + "--model", + type=str, + required=True, + choices=list(config.MODEL_CONFIGS.keys()), + help=f"选择要训练的模型。可选: {list(config.MODEL_CONFIGS.keys())}" + ) + args = parser.parse_args() + + # 1. 开始训练 + project_folder = train_model(args.model) + # 2. 训练完成后,移动结果到硬盘 + if project_folder: + move_results_to_hardisk(project_folder) + else: + logging.error("由于训练失败,未执行结果移动操作。") + # 3. 释放资源 + torch.cuda.empty_cache() # 释放 CUDA 显存 + gc.collect() # 释放系统内存 + logging.info("已清理 CUDA 显存与系统内存。") \ No newline at end of file diff --git a/Tool-可视化/0_图片Labels生成/4_deal_labels.py b/Tool-可视化/0_图片Labels生成/4_deal_labels.py new file mode 100644 index 0000000..9317b17 --- /dev/null +++ b/Tool-可视化/0_图片Labels生成/4_deal_labels.py @@ -0,0 +1,445 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -* +import os,time,sys,threading, colorsys, argparse +import asyncio, cv2, multiprocessing, random +from PIL import Image +import numpy as np +from Tool_deal_labels import edge_detection, detect_connected_regions, Tool_color_connected_array, fill_white_regions, color_connected_regions + +def getFileList(dir,Filelist=[], ext=None, Max_layer=1, layer=0, Donot_Search=['1_边缘检测并膨胀', '2_连通区域检测', '3_分水岭算法填充']): + """ + 获取文件夹及其子文件夹中文件列表 + 输入 dir:文件夹根目录 + 输入 ext: 扩展名 + 返回: 文件路径列表 + """ + newDir = dir + if os.path.isfile(dir): + if ext is None: + Filelist.append(dir) + else: + if ext in dir[-3:]: + Filelist.append(dir) + + elif os.path.isdir(dir): + file_name = os.path.basename(dir) + # 判断是否在禁搜名单中 + if file_name in Donot_Search: + return Filelist + for s in os.listdir(dir): + newDir=os.path.join(dir,s) + if layer <= Max_layer: + getFileList(newDir, Filelist, ext, Max_layer, layer+1) + + return Filelist + +class Deal_image(): + def __init__(self, Annotate_CLASSES = ('肝脏','胆囊'), Annotate_PALETTE = [[255,91,0],[255,234,0]], src_label_fold = "./Label", save_pro_label_fold = "./LABEL_PNG_new", save_GT_label_fold = "./Label_Generate", GT_channel = 1, pro_append_name="_label", GT_append_name="_gtFine_labelTrainIds", ori_img_folder="./ORI_PNG", res_label_folder="./Result_label", save_merge_pic_folder="./Result_merge", back_gnd_color=0, first_class_color=1, pic_type="png", Max_width = 10000, Label_Max_Search_layer=1000, save_process_pics=False, bg_PALETTE = [0,0,0]): + # 背景最好放在最后 + # self.src_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','背景') + # self.src_PALETTE = np.array([[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66],[0,0,0]]) + # self.src_CLASSES_NUM = np.shape(self.src_CLASSES)[0] + self.bg_PALETTE = bg_PALETTE # 背景颜色 TODO + + self.Annotate_CLASSES = Annotate_CLASSES # 待分类的类 + self.Annotate_PALETTE = np.array(Annotate_PALETTE) # 每一类的像素直 + self.Annotate_CLASSES_NUM = np.shape(Annotate_CLASSES)[0] # 类数量 + + self.save_process_pics = save_process_pics # 保存中间过程图片 + + self.src_label_fold = src_label_fold # 原始标签图片 保存位置 + self.save_pro_label_fold = save_pro_label_fold # 优化后标签图片 保存位置 + self.save_GT_label_fold = save_GT_label_fold # GT标签图片 保存位置 + + self.ori_img_folder = ori_img_folder # 最原始手术图片 保存位置 + self.res_label_folder = res_label_folder # 训练出来的label 保存位置 + self.save_merge_pic_folder = save_merge_pic_folder # 融合图像保存位置 + + self.pro_append_name = pro_append_name # 优化后标签图片后缀 + self.GT_append_name = GT_append_name # GT标签图片后缀 + self.GT_channel = GT_channel # GT标签图片通道数 + + self.Max_width = Max_width # 最大图片宽度(匹配时候用) + self.pic_type = pic_type # 图片类型 + self.back_gnd_color = back_gnd_color # 背景颜色 + self.first_class_color = first_class_color # 第一类上的颜色 + self.Label_Max_Search_layer=Label_Max_Search_layer # 文件夹最大搜索深度 + try: + self.labellist_src = getFileList(src_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到ori_label图片 '+str(len(self.labellist_src))+' 张图像') + except: + self.labellist_src = None + print("没有ori_label相关文件") + + try: + # print(save_pro_label_fold) + self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像') + except: + self.labellist_pro = None + print("没有pro_label相关文件") + + try: + self.imglist_src = getFileList(ori_img_folder, [], pic_type, self.Label_Max_Search_layer) + self.reslist_src = getFileList(res_label_folder, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到ori原始图片 '+str(len(self.imglist_src))+' 张图像') + print('本次执行检索到训练train_result图片 '+str(len(self.reslist_src))+' 张图像') + except: + self.imglist_src = None + self.reslist_src = None + print("没有train_result和原始图片相关文件") + + # 获取单张图片各个通路信息 + def get_single_pic_rgb(self, imgpath): + print(imgpath) + image = Image.open(imgpath).convert('RGB') # 转为RGB图片 + # 将 RGB 色值分离 + image.load() + r, g, b = image.split() + r = np.array(r) + g = np.array(g) + b = np.array(b) + return image, r, g, b + + # 将单个pro图片变成GT图片 + def Conver_pro_label_pic_2_GT_pic(self, imgpath, imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + image, r,g,b = self.get_single_pic_rgb(imgpath) + + result_gt = np.ones(np.shape(image))*self.back_gnd_color # 初始化填充内容为back_gnd_color + gt_number = self.first_class_color # 第一类上色颜色确定 + + # PALETTE中排除掉 '背景' [0,0,0] + PALETTE_No_Bg = self.Annotate_PALETTE[~np.all(self.Annotate_PALETTE == self.bg_PALETTE, axis=1)] + + # 遍历所有待识别颜色 + for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in PALETTE_No_Bg: + # 查找三原色匹配位置 + locate_r = np.where( r == Annotate_PALETTE_r ) + locate_g = np.where( g == Annotate_PALETTE_g ) + locate_b = np.where( b == Annotate_PALETTE_b ) + + # 查找都匹配位置(交集) + # 将矩阵换一种表示形式 + locate_r = np.array(locate_r[0]) * self.Max_width + np.array(locate_r[1]) + locate_g = np.array(locate_g[0]) * self.Max_width + np.array(locate_g[1]) + locate_b = np.array(locate_b[0]) * self.Max_width + np.array(locate_b[1]) + + # 用自带函数寻找匹配项 + matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b) + matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0) + result_gt[matched[0],matched[1], :] = gt_number + gt_number = gt_number + 1 + + # 输出GT图片 + if(int(self.GT_channel) == 1): + result_gt = result_gt[:,:,0] + elif(int(self.GT_channel) == 3): + result_gt = cv2.cvtColor(np.float32(result_gt), cv2.COLOR_RGB2BGR) # rgb颜色互换 + else: + print("GT_channel 必须为1或3") + quit + try: # 新建文件夹 + os.mkdir(self.save_GT_label_fold) + except: + print("已有"+self.save_GT_label_fold) + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_GT_label_fold, os.path.basename(imgname).rpartition('.')[0]+self.GT_append_name+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_GT_label_fold, os.path.basename(imgname)+self.GT_append_name+'.'+self.pic_type) + cv2.imwrite(save_dir, result_gt) + print("GT图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将处理好的图片转化为GT图片 + def Conver_pro_label_pic_2_GT_pic_all(self): + print("\033[33m**** 进行转换将Pro_label_pic转换为GT_label_pic ****\033[0m") + print("\033[33mPro_label_pic存储位置为:\033[0m", self.save_pro_label_fold) + print("\033[33mGT_label_pic生成位置为:\033[0m", self.save_GT_label_fold) + try: + # print(save_pro_label_fold) + self.labellist_pro = getFileList(save_pro_label_fold, [], pic_type, self.Label_Max_Search_layer) + print('本次执行检索到pro_label图片 '+str(len(self.labellist_pro))+' 张图像') + except: + self.labellist_pro = None + print("没有pro_label相关文件") + try: + os.mkdir(self.save_GT_label_fold) # 新建存储文件夹 + except: + print("已有"+self.save_GT_label_fold) + + # 指定最大进程数为 3 + max_processes = 20 + # 创建Pool对象 + pool = multiprocessing.Pool(processes=max_processes) + # 创建并启动进程 + args_list1 = [] + args_list2 = [] + + # 遍历整个文件夹 + for imgpath in self.labellist_pro: + imgname = os.path.basename(imgpath).rpartition('.')[0].replace(self.pro_append_name,"") + args_list1.append(imgpath) + args_list2.append(imgname) + args_list = zip(args_list1, args_list2) + # 使用进程池并行执行任务 + pool.starmap(self.Conver_pro_label_pic_2_GT_pic, args_list) + # 关闭进程池 + pool.close() + pool.join() + + def Conver_ori_label_pic_2_pro_pic(self, imgpath, imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + image = cv2.imread(imgpath) + + # 1. 边缘检测并膨胀 + dilated_image = edge_detection(image) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_Edge'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀', os.path.basename(imgname)+self.pro_append_name+'_Edge'+'.'+self.pic_type) + cv2.imwrite(save_dir, dilated_image) + print("中间态-边缘检测并膨胀 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 2. 检测连通区域 + filtered_labeled_array, _ = detect_connected_regions(dilated_image) + colored_image_filtered = Tool_color_connected_array(filtered_labeled_array) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '2_连通区域检测', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_Region'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '2_连通区域检测', os.path.basename(imgname)+self.pro_append_name+'_Region'+'.'+self.pic_type) + cv2.imwrite(save_dir, colored_image_filtered) + print("中间态-连通区域检测 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 3. 分水岭填充白色区域 + filled_labeled_array = fill_white_regions(filtered_labeled_array) + colored_image_filled = Tool_color_connected_array(filled_labeled_array) + # 如果需要存储中间态图片 + if(self.save_process_pics == True): + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, '3_分水岭算法填充', os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'_FillEdge'+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, '3_分水岭算法填充', os.path.basename(imgname)+self.pro_append_name+'_FillEdge'+'.'+self.pic_type) + cv2.imwrite(save_dir, colored_image_filled) + print("中间态-分水岭算法填充 图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 4. 对连通区域最终上色 + ori_labeled_image = image + result_pro = color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, self.Annotate_PALETTE) + if imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_pro_label_fold, os.path.basename(imgname).rpartition('.')[0]+self.pro_append_name+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_pro_label_fold, os.path.basename(imgname)+self.pro_append_name+'.'+self.pic_type) + print("Pro图片已保存", save_dir) + cv2.imwrite(save_dir, result_pro) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将原始src图片转化为处理好的pro图片 + def Conver_ori_label_pic_2_pro_pic_all(self): + print("\033[33m**** 进行转换将Ori_label_pic转换为Pro_label_pic ****\033[0m") + print("\033[33mOri_label_pic存储位置为:\033[0m", self.src_label_fold) + print("\033[33mPro_label_pic生成位置为:\033[0m", self.save_pro_label_fold) + # 输出颜色预处理图片 + try: + os.mkdir(self.save_pro_label_fold) # 新建存储文件夹 + except: + print("已有"+self.save_pro_label_fold) + if(self.save_process_pics == True): + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀')) # 新建存储1_边缘检测并膨胀文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '1_边缘检测并膨胀')) + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '2_连通区域检测')) # 新建存储2_连通区域检测文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '2_连通区域检测')) + try: + os.mkdir(os.path.join(self.save_pro_label_fold, '3_分水岭算法填充')) # 新建存储1_边缘检测并膨胀文件夹 + except: + print("已有"+os.path.join(self.save_pro_label_fold, '3_分水岭算法填充')) + + # 指定最大进程数为 20,多参数函数并行 + max_processes = 20 + # 创建Pool对象 + pool = multiprocessing.Pool(processes=max_processes) + # 创建并启动进程 + args_list1 = [] + args_list2 = [] + + # 遍历整个文件夹 + for imgpath in self.labellist_src: + if imgpath.lower().endswith(('.jpg', '.png')): + imgname= os.path.basename(imgpath).rpartition('.')[0].replace(self.pro_append_name,"") + else: + imgname= os.path.basename(imgpath).replace(self.pro_append_name,"") + try: + print("Processing: ", imgname, "...") + # self.Conver_ori_label_pic_2_pro_pic(imgpath, imgname)s + # args_list.append({'imgpath': imgpath, 'imgname': imgname}) + args_list1.append(imgpath) + args_list2.append(imgname) + except: + os.system("echo "+imgname+" >> error_1.txt") + args_list = zip(args_list1, args_list2) + # 使用进程池并行执行任务 + pool.starmap(self.Conver_ori_label_pic_2_pro_pic, args_list) # 使用starmap进行多参数并行 + # 关闭进程池 + pool.close() + pool.join() + + # 图片堆叠 + def Merge_ori_pic_and_label_pic(self, res_img_path, res_imgname): + time_start=time.time() # 记录开始时间 + # 获取单张图片各个通路信息 + ori_img_path = os.path.join(self.ori_img_folder, res_imgname+'.'+self.pic_type) + if not os.path.exists(ori_img_path): + print("****照片不存在:****", ori_img_path) + return -1 + ori_image, ori_r, ori_g, ori_b = self.get_single_pic_rgb(ori_img_path) + res_image, res_r, res_g, res_b = self.get_single_pic_rgb(res_img_path) + + merge_img = np.array(ori_image) # merge图片初始化,默认图片背景为0.0.0 + + # 遍历所有待识别颜色 + for [Annotate_PALETTE_r, Annotate_PALETTE_g, Annotate_PALETTE_b] in self.Annotate_PALETTE: + # 查找三原色匹配位置 + locate_r = np.where( res_r == Annotate_PALETTE_r ) + locate_g = np.where( res_g == Annotate_PALETTE_g ) + locate_b = np.where( res_b == Annotate_PALETTE_b ) + + # 查找都匹配位置(交集) + # 将矩阵换一种表示形式 + locate_r = np.array(locate_r[0]) * self.Max_width + np.array(locate_r[1]) + locate_g = np.array(locate_g[0]) * self.Max_width + np.array(locate_g[1]) + locate_b = np.array(locate_b[0]) * self.Max_width + np.array(locate_b[1]) + + # 用自带函数寻找匹配项 + matched = np.intersect1d(np.intersect1d(locate_r, locate_g), locate_b) + matched = np.concatenate(([matched // self.Max_width], [np.mod(matched, self.Max_width)]), 0) + merge_img[matched[0],matched[1], 0] = Annotate_PALETTE_r + merge_img[matched[0],matched[1], 1] = Annotate_PALETTE_g + merge_img[matched[0],matched[1], 2] = Annotate_PALETTE_b + + # 转成cv2形式 + merge_img = cv2.cvtColor(np.float32(merge_img), cv2.COLOR_RGB2BGR) + + try: # 新建文件夹 + os.mkdir(self.save_merge_pic_folder) + except: + print("已有"+self.save_merge_pic_folder) + if res_imgname.lower().endswith(('.jpg', '.png')): + save_dir = os.path.join(self.save_merge_pic_folder, os.path.basename(res_imgname).rpartition('.')[0]+'.'+self.pic_type) + else: + save_dir = os.path.join(self.save_merge_pic_folder, os.path.basename(res_imgname)+'.'+self.pic_type) + + + cv2.imwrite(save_dir, merge_img) + print("Merge图片已保存", save_dir) + time_end=time.time() # 输出结束时间 + print('time cost',time_end-time_start,'s') + + # 将label图片与原图片重合 + def Merge_ori_pic_and_label_pic_all(self): + # 遍历整个文件夹 + for res_img_path in self.reslist_src: + if res_img_path.lower().endswith(('.jpg', '.png')): + res_imgname = os.path.basename(res_img_path).rpartition('.')[0].replace(self.pro_append_name,"") + else: + res_imgname = os.path.basename(res_img_path).replace(self.pro_append_name,"") + print("Processing: ", res_imgname, "...") + self.Merge_ori_pic_and_label_pic(res_img_path, res_imgname) + +if __name__ == "__main__": + + Annotate_CLASSES = ('肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','背景') # 待分类的类 + Annotate_PALETTE = [[255,91,0],[255,234,0],[85, 111, 181],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66],[0,0,0]] # 每一类的像素直 + bg_PALETTE = [0,0,0] # 背景的RGB + + # 创建参数解析器 + parser = argparse.ArgumentParser(description='Process some files.') + # 添加参数选项 + parser.add_argument('-src_fold', dest='src_label_fold', default='./', help='source label folder') + parser.add_argument('-save_pro_fold', dest='save_pro_label_fold', default='./save_pro_label_fold', help='processed label folder') + parser.add_argument('-save_GT_fold', dest='save_GT_label_fold', default='./save_GT_label_fold', help='ground truth folder') + parser.add_argument('-fold_search_depth', dest='Label_Max_Search_layer', default='1000', type=int, help='Folder Search Depth') + parser.add_argument('-pro_suffix_name', dest='pro_append_name', default='_label', help='Pro file suffix') + parser.add_argument('-GT_suffix_name', dest='GT_append_name', default='_gtFine_labelTrainIds', help='GT file suffix') + parser.add_argument('-GT_channel', dest='GT_channel', default='1', type=int, help='GT file channel(1 or 3)') + parser.add_argument('-back_gnd_color', dest='back_gnd_color', default='0', type=int, help='Color of "Back ground"(0 or 255)') + parser.add_argument('-first_class_color', dest='first_class_color', default='1', type=int, help='Color of "First Class"') + parser.add_argument('-pic_type', dest='pic_type', default='png', help='type of picture(Do not add ".")') + parser.add_argument('-Max_width', dest='Max_width', default='10000', type=int, help='Max width of picture') + parser.add_argument('-Rebuild_from', dest='Rebuild_from', default='label', help='Source to Rebuild Labels(label/pro)') + parser.add_argument('-Rebuild_to', dest='Rebuild_to', default='GT', help='Destination of Rebuild Labels(pro/GT)') + parser.add_argument('-save_process_pics', dest='save_process_pics', default='False', help='Save the processed pics(e.g.Gray_pics,Color_pics) in generating pro_pics') + + # 解析命令行参数 + args = parser.parse_args() + + src_label_fold = args.src_label_fold + save_pro_label_fold = args.save_pro_label_fold + save_GT_label_fold = args.save_GT_label_fold + Label_Max_Search_layer = args.Label_Max_Search_layer + pro_append_name = args.pro_append_name + GT_append_name = args.GT_append_name + GT_channel = args.GT_channel + back_gnd_color = args.back_gnd_color + first_class_color = args.first_class_color + pic_type = args.pic_type + Max_width = args.Max_width + Rebuild_from = args.Rebuild_from + Rebuild_to = args.Rebuild_to + save_process_pics = args.save_process_pics + + + try: # 遍历文件深度,最小为1 + Label_Max_Search_layer=int(Label_Max_Search_layer) + except: + Label_Max_Search_layer=1000 + try: # GT标签图片通道数 + GT_channel=int(GT_channel) + except: + GT_channel=1 + try: # 背景颜色(背景选择0或255) + back_gnd_color=int(back_gnd_color) + except: + back_gnd_color=0 + try: # 第一类上的颜色(如果背景为0,选择1;) + first_class_color=int(first_class_color) + except: + first_class_color=1 + try: # 最大图片宽度(匹配时候用) + Max_width=int(Max_width) + except: + Max_width=10000 + if(save_process_pics.lower() == 'false'): + save_process_pics = False + elif(save_process_pics.lower() == 'true'): + save_process_pics = True + else: + save_process_pics = False + + D = Deal_image(Annotate_CLASSES=Annotate_CLASSES, Annotate_PALETTE=Annotate_PALETTE, src_label_fold=src_label_fold, save_pro_label_fold=save_pro_label_fold, save_GT_label_fold=save_GT_label_fold, GT_channel=GT_channel, pro_append_name=pro_append_name, GT_append_name=GT_append_name, back_gnd_color=back_gnd_color, first_class_color=first_class_color, pic_type=pic_type, Max_width=Max_width, Label_Max_Search_layer=Label_Max_Search_layer, save_process_pics=save_process_pics, bg_PALETTE = bg_PALETTE) + # print(D.src_CLASSES_NUM) + if Rebuild_from == 'label': + # 1.先将所有原始图片转为pro图片 + D.Conver_ori_label_pic_2_pro_pic_all() + pass + if Rebuild_to == 'GT': + # 2.再将pro图片转为GT图片 + D.Conver_pro_label_pic_2_GT_pic_all() + pass \ No newline at end of file diff --git a/Tool-可视化/0_图片Labels生成/Tool_deal_labels.py b/Tool-可视化/0_图片Labels生成/Tool_deal_labels.py new file mode 100644 index 0000000..a1184f9 --- /dev/null +++ b/Tool-可视化/0_图片Labels生成/Tool_deal_labels.py @@ -0,0 +1,185 @@ +import cv2 +import random +import numpy as np +from scipy.ndimage import label, distance_transform_edt + +########################### 1. 超参数 ########################### +Annotate_CLASSES = ('背景','术中超声','吻合器','乳胶管','推结器','肝带','钳夹','超声刀','脂肪','双极电凝','棉球','血管阻断夹','肿瘤','针','线','韧带','胆囊静脉','肝脏','胆囊','分离钳','止血海绵','肝总管','胆总管','吸引器','剪刀','止血纱布','生物夹','无损伤钳','喷洒','胆囊管','胆囊动脉','电凝','标本袋','引流管','纱布','金属钛夹') # 待分类的类 +Annotate_PALETTE = [[0,0,0],[138,251,213],[136,162,196],[197,83,181],[202,202,200],[113,102,140],[66,115,82],[240,16,116],[155,132,0],[155,62,0],[146,175,236],[255,172,159],[245,161,0],[134,124,118], [0,157,142], [181,85,105], [42,8,66], [255,91,0],[255,234,0],[85, 107, 179],[181, 227, 14],[72, 0, 255],[0, 155, 33],[255,0,255],[29, 32, 136],[160, 15, 95],[0,160,233],[52,184,178],[90,120,41],[255,0,0],[177,0,0],[167,24,233],[112,113,150],[0,255,0],[255,255,255],[0,255,255]] + + +def skeletonize(image): + """骨架化函数,确保线条连通性并缩减为1像素宽""" + skeleton = np.zeros_like(image) + temp_image = np.copy(image) + + while True: + eroded = cv2.erode(temp_image, None) # 腐蚀操作 + temp_dilate = cv2.dilate(eroded, None) # 膨胀操作 + temp = cv2.subtract(temp_image, temp_dilate) # 提取边缘 + skeleton = cv2.bitwise_or(skeleton, temp) # 将边缘加入骨架 + temp_image = np.copy(eroded) + if cv2.countNonZero(temp_image) == 0: + break + return skeleton + +# 1. *** 边缘检测并膨胀 *** +def edge_detection(image): + """对图像的各个通道进行边缘检测并进行膨胀处理""" + b_channel, g_channel, r_channel = cv2.split(image) + gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + + edges_b = cv2.Canny(b_channel, 100, 200) + edges_g = cv2.Canny(g_channel, 100, 200) + edges_r = cv2.Canny(r_channel, 100, 200) + edges_gray = cv2.Canny(gray_image, 100, 200) + + # 合并所有边缘检测结果 + edges = cv2.bitwise_or(edges_b, edges_g) + edges = cv2.bitwise_or(edges, edges_r) + edges = cv2.bitwise_or(edges, edges_gray) + + # 创建膨胀核并进行膨胀操作 + kernel = np.ones((3, 3), np.uint8) + dilated_image = cv2.dilate(edges, kernel, iterations=1) + + return dilated_image + +# 2. ** 检测连通区域 *** +def detect_connected_regions(dilated_image): + """检测图像中的连通区域并过滤掉小区域""" + _, binary = cv2.threshold(dilated_image, 1, 255, cv2.THRESH_BINARY_INV) + binary[binary > 0] = 1 # 转换为二值图像 + + # 标记连通区域 + structure = np.ones((3, 3), dtype=int) + labeled_array, num_features = label(binary, structure=structure) + + # 清除掉小于100像素的区域 + filtered_labeled_array = np.copy(labeled_array) + for label_num in range(1, num_features + 1): + area = np.sum(labeled_array == label_num) + if area < 100: + filtered_labeled_array[filtered_labeled_array == label_num] = 0 + + # 重新标记过滤后的连通区域 + filtered_labeled_array, num_features = label(filtered_labeled_array, structure=structure) + return filtered_labeled_array, num_features # 返回过滤后的labeled_array和未过滤的labeled_array(用于寻找颜色) + +# 3. *** 分水岭填充白色区域 *** +def fill_white_regions(filtered_labeled_array): + """使用分水岭算法填充白色区域""" + # 准备三通道图像作为分水岭算法的输入 + color_image = np.zeros((filtered_labeled_array.shape[0], filtered_labeled_array.shape[1], 3), dtype=np.uint8) + + # 将过滤后的 labeled_array 转化为 32 位整型,作为分水岭的 markers + markers = np.copy(filtered_labeled_array).astype(np.int32) + markers[markers == 0] = -1 # 背景标记为 -1 + + # 执行分水岭算法 + cv2.watershed(color_image, markers) + + # 更新分水岭结果 + filled_labeled_array = markers.astype(int) + filled_labeled_array[filled_labeled_array == -1] = 0 + + # 使用距离变换,计算边缘像素(0)最近的非零值 + non_zero_mask = filled_labeled_array != 0 + distance, nearest_indices = distance_transform_edt(non_zero_mask == 0, return_indices=True) + nearest_values = filled_labeled_array[tuple(nearest_indices)] + filled_labeled_array[filled_labeled_array == 0] = nearest_values[filled_labeled_array == 0] + + return filled_labeled_array + +# 4. *** 对连通区域上色(使用“filtered_labeled_array”作为颜色判断,给“filled_labeled_array”上色) *** +def color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, Annotate_PALETTE): + """根据原始图像的颜色和注解调色板给连通区域上色""" + + # 初始化一个三通道的彩色图像 + colored_image = np.zeros((*filled_labeled_array.shape, 3), dtype=np.uint8) + + # 遍历filtered_labeled_array中的每个标签 + unique_labels = np.unique(filtered_labeled_array) + + for label_num in unique_labels: + if label_num == 0: + continue # 跳过背景标签 + + # 找到filtered_labeled_array中等于当前标签的区域 + mask_filtered = (filtered_labeled_array == label_num) + + # 获取ori_labeled_image中对应区域的RGB值 + region_rgb_values = ori_labeled_image[mask_filtered] + + if len(region_rgb_values) == 0: + continue + + # 计算区域的RGB平均值 + average_rgb = np.mean(region_rgb_values, axis=0) + + # 找到Annotate_PALETTE中与average_rgb最接近的颜色 + closest_palette_color = find_closest_palette_color(average_rgb, Annotate_PALETTE) + + # 将该颜色赋给filled_labeled_array对应区域的元素 + mask_filled = (filled_labeled_array == label_num) + colored_image[mask_filled] = closest_palette_color + + return colored_image + +# 寻找最近邻颜色 +def find_closest_palette_color(average_rgb, Annotate_PALETTE): + """根据平均RGB值找到Annotate_PALETTE中最接近的颜色""" + Annotate_PALETTE = [[color[2], color[1], color[0]] for color in Annotate_PALETTE] + + average_rgb = np.array(average_rgb) + min_distance = float('inf') + closest_color = None + + # 遍历调色板,计算每个颜色与平均RGB的欧几里得距离 + for palette_color in Annotate_PALETTE: + palette_color = np.array(palette_color) + distance = np.linalg.norm(average_rgb - palette_color) # 欧几里得距离 + + if distance < min_distance: + min_distance = distance + closest_color = palette_color + + return closest_color + +# 5. 对Array区域上色(4的简化版) +def Tool_color_connected_array(Array): + colored_image = np.zeros((*Array.shape, 3), dtype=np.uint8) + for label_num in range(1, np.max(Array) + 1): + color = [np.random.randint(0, 254) for _ in range(3)] + colored_image[Array == label_num] = color + return colored_image + +if __name__ == '__main__': + """超参数""" + image_path = './2023_02_03_09_13_48.00_08_04_21.Still085.png' + + """主函数,处理图像并保存结果""" + # 读取图像 + image = cv2.imread(image_path) + + # 1. 边缘检测并膨胀 + dilated_image = edge_detection(image) + cv2.imwrite('./1_1_range_image.png', dilated_image) + + # 2. 检测连通区域 + filtered_labeled_array, _ = detect_connected_regions(dilated_image) + colored_image_filtered = Tool_color_connected_array(filtered_labeled_array) + cv2.imwrite('./2_colored_image_filtered.png', colored_image_filtered) + + # 3. 分水岭填充白色区域 + filled_labeled_array = fill_white_regions(filtered_labeled_array) + colored_image_filled = Tool_color_connected_array(filled_labeled_array) + cv2.imwrite('./3_colored_image_filled.png', colored_image_filled) + + # 4. 对连通区域上色 + ori_labeled_image = image + colored_image_final = color_connected_regions(filled_labeled_array, filtered_labeled_array, ori_labeled_image, Annotate_PALETTE) + cv2.imwrite('./4_color_image_Final.png', colored_image_final) + + + print("处理后的图片已保存。") diff --git a/Tool-可视化/Tool_Check_and_Gen_Txt_Label_ori_label.py b/Tool-可视化/Tool_Check_and_Gen_Txt_Label_ori_label.py new file mode 100644 index 0000000..6dfe131 --- /dev/null +++ b/Tool-可视化/Tool_Check_and_Gen_Txt_Label_ori_label.py @@ -0,0 +1,93 @@ +import os +import cv2 +import numpy as np +from collections import Counter +from PIL import Image + +# ※ 需要修改输入输出路径 ※ # +input_dir = 'Data/ann_dir' +output_dir_1 = 'Data/labels/train' +# output_dir_2 = 'Data/labels/val' +output_dir_2 = 'Data/labels/train' + +os.makedirs(output_dir_1, exist_ok=True) +os.makedirs(output_dir_2, exist_ok=True) + +# 全局统计颜色频率 +global_color_counter = Counter() +global_class_counter = Counter() + +# 自动提取颜色映射(跳过背景) +def extract_color_mapping(img_path): + img = Image.open(img_path).convert('RGB') + pixels = list(img.getdata()) + counter = Counter(pixels) + color_map = {} + for color, count in counter.items(): + global_color_counter[color] += count + if color != (0, 0, 0) and color[0] == color[1] == color[2]: + class_id = color[0] - 1 + if class_id >= 0: + color_map[color] = class_id + global_class_counter[class_id] += count + return color_map + +# 处理单张图片 +def process_image(img_path, save_path_list): + color_to_class = extract_color_mapping(img_path) + if not color_to_class: + print(f"[跳过] {os.path.basename(img_path)} 无有效目标") + return + + img = cv2.imread(img_path) + h, w = img.shape[:2] + img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + + lines = [] + for rgb, class_id in color_to_class.items(): + mask = np.all(img_rgb == rgb, axis=-1).astype(np.uint8) * 255 + contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + for contour in contours: + if contour.shape[0] < 3: + continue + norm_pts = contour.squeeze(1).astype(np.float32) + norm_pts[:, 0] /= w + norm_pts[:, 1] /= h + flat = norm_pts.flatten() + line = f"{class_id} " + " ".join([f"{x:.6f}" for x in flat]) + lines.append(line) + + if lines: + for save_path in save_path_list: + with open(save_path, 'w') as f: + for line in lines: + f.write(line + "\n") + print(f"[✔] 转换成功: {os.path.basename(save_path)},共 {len(lines)} 个实例") + else: + print(f"[⚠] {os.path.basename(img_path)} 没有轮廓") + +# 主执行逻辑:批量处理 +for fname in os.listdir(input_dir): + if fname.lower().endswith('.png'): + img_path = os.path.join(input_dir, fname) + base_name = os.path.splitext(fname)[0] + txt_path_1 = os.path.join(output_dir_1, base_name + '.txt') + txt_path_2 = os.path.join(output_dir_2, base_name + '.txt') + process_image(img_path, [txt_path_1, txt_path_2]) + +# 打印颜色统计和类别映射 +print("\n📊 所有图像颜色统计:") +for color, count in global_color_counter.most_common(): + if color == (0, 0, 0): + print(f"背景颜色 {color} 出现次数: {count}") + elif color[0] == color[1] == color[2]: + class_id = color[0] - 1 + print(f"颜色 {color} → class {class_id},出现次数: {count}") + else: + print(f"[⚠] 非灰阶颜色 {color},跳过") + +print("\n✅ 有效类别统计(class_id → 总像素数):") +for class_id in sorted(global_class_counter): + print(f"class {class_id}: {global_class_counter[class_id]} pixels") + +print(f"\n✅ 全部图像处理完毕。标签输出目录:{output_dir_1}、{output_dir_2}") diff --git a/Tool-可视化/Tool_Check_and_Gen_Txt_Label_sort_label.py b/Tool-可视化/Tool_Check_and_Gen_Txt_Label_sort_label.py new file mode 100644 index 0000000..2bcc01d --- /dev/null +++ b/Tool-可视化/Tool_Check_and_Gen_Txt_Label_sort_label.py @@ -0,0 +1,109 @@ +import os +import cv2 +import numpy as np +from collections import Counter, defaultdict +from PIL import Image + +# ※ 需要修改输入输出路径 ※ # +input_dir = 'Data/ann_dir' +output_dir_1 = 'Data/labels/train' +output_dir_2 = 'Data/labels/train' + +os.makedirs(output_dir_1, exist_ok=True) +os.makedirs(output_dir_2, exist_ok=True) + +# 全局统计颜色频率与 class 像素频率 +global_color_counter = Counter() +global_class_counter = Counter() +color_class_counter = defaultdict(int) # (R,G,B) → count +color_to_old_class = {} # (R,G,B) → class_id +remap_class_dict = {} # old_class_id → new_class_id + +# 自动提取颜色映射(跳过背景) +def extract_color_mapping(img_path): + img = Image.open(img_path).convert('RGB') + pixels = list(img.getdata()) + counter = Counter(pixels) + color_map = {} + for color, count in counter.items(): + global_color_counter[color] += count + if color != (0, 0, 0) and color[0] == color[1] == color[2]: + class_id = color[0] - 1 + if class_id >= 0: + color_map[color] = class_id + global_class_counter[class_id] += count + color_class_counter[color] += count + color_to_old_class[color] = class_id + return color_map + +# 处理单张图片 +def process_image(img_path, save_path_list): + color_to_class = extract_color_mapping(img_path) + if not color_to_class: + print(f"[跳过] {os.path.basename(img_path)} 无有效目标") + return + + img = cv2.imread(img_path) + h, w = img.shape[:2] + img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + + lines = [] + for rgb, old_class_id in color_to_class.items(): + mask = np.all(img_rgb == rgb, axis=-1).astype(np.uint8) * 255 + contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + for contour in contours: + if contour.shape[0] < 3: + continue + norm_pts = contour.squeeze(1).astype(np.float32) + norm_pts[:, 0] /= w + norm_pts[:, 1] /= h + flat = norm_pts.flatten() + new_class_id = remap_class_dict.get(old_class_id, old_class_id) + line = f"{new_class_id} " + " ".join([f"{x:.6f}" for x in flat]) + lines.append(line) + + if lines: + for save_path in save_path_list: + with open(save_path, 'w') as f: + for line in lines: + f.write(line + "\n") + print(f"[✔] 转换成功: {os.path.basename(save_path)},共 {len(lines)} 个实例") + else: + print(f"[⚠] {os.path.basename(img_path)} 没有轮廓") + +# 第一次遍历图像,仅提取颜色信息用于构建 class 映射 +for fname in os.listdir(input_dir): + if fname.lower().endswith('.png'): + img_path = os.path.join(input_dir, fname) + extract_color_mapping(img_path) + +# 构建 class_id 重映射表(按像素数从大到小排序) +class_pixel_count = {color_to_old_class[c]: count for c, count in color_class_counter.items()} +sorted_classes = sorted(class_pixel_count.items(), key=lambda x: x[1], reverse=True) +remap_class_dict = {old_cls: new_idx for new_idx, (old_cls, _) in enumerate(sorted_classes)} + +# 第二次遍历图像,正式处理并生成标签 +for fname in os.listdir(input_dir): + if fname.lower().endswith('.png'): + img_path = os.path.join(input_dir, fname) + base_name = os.path.splitext(fname)[0] + txt_path_1 = os.path.join(output_dir_1, base_name + '.txt') + txt_path_2 = os.path.join(output_dir_2, base_name + '.txt') + process_image(img_path, [txt_path_1, txt_path_2]) + +# 打印颜色统计和类别映射 +print("\n📊 所有图像颜色统计:") +for color, count in global_color_counter.most_common(): + if color == (0, 0, 0): + print(f"背景颜色 {color} 出现次数: {count}") + elif color[0] == color[1] == color[2]: + class_id = color[0] - 1 + print(f"颜色 {color} → class {class_id},出现次数: {count}") + else: + print(f"[⚠] 非灰阶颜色 {color},跳过") + +print("\n✅ 有效类别统计(原 class_id → 新 class_id → 总像素数):") +for old_id, new_id in remap_class_dict.items(): + print(f"class {old_id} → {new_id}: {class_pixel_count[old_id]} pixels") + +print(f"\n✅ 全部图像处理完毕。标签输出目录:{output_dir_1}、{output_dir_2}") diff --git a/Tool-可视化/Tool_Gen_8_Bit_PNG[没用,不认].py b/Tool-可视化/Tool_Gen_8_Bit_PNG[没用,不认].py new file mode 100644 index 0000000..f25cb09 --- /dev/null +++ b/Tool-可视化/Tool_Gen_8_Bit_PNG[没用,不认].py @@ -0,0 +1,25 @@ +import os +from PIL import Image +import numpy as np + +input_dir = 'Data/ann_dir' # 输入标签图所在目录 +output_dir = 'Data/labels/train' # YOLO 格式输出目录 + +os.makedirs(output_dir, exist_ok=True) + +for filename in os.listdir(input_dir): + if not filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp')): + continue + + input_path = os.path.join(input_dir, filename) + output_path = os.path.join(output_dir, os.path.splitext(filename)[0] + '.png') + + # 打开图像并转为 RGB,再提取 R 通道(等价于 G、B) + img_rgb = Image.open(input_path).convert('RGB') + r_channel = img_rgb.split()[0] # 或使用 .getchannel('R') + + # 保存为 8-bit 单通道 PNG + r_channel.save(output_path, format='PNG') + print(f"Saved: {output_path}") + +print("✅ 所有标签图已转换为 8-bit 单通道灰度图。") diff --git a/Tool-可视化/get_FPS.py b/Tool-可视化/get_FPS.py new file mode 100644 index 0000000..64202c5 --- /dev/null +++ b/Tool-可视化/get_FPS.py @@ -0,0 +1,92 @@ +import warnings +warnings.filterwarnings('ignore') +import argparse +import logging +import math +import os +import random +import time +import sys +from copy import deepcopy +from pathlib import Path +from threading import Thread + +import numpy as np +import torch.distributed as dist +import torch.nn as nn +import torch.nn.functional as F +import torch.optim as optim +import torch.optim.lr_scheduler as lr_scheduler +import torch.utils.data +import yaml +from torch.cuda import amp +from torch.nn.parallel import DistributedDataParallel as DDP +from tqdm import tqdm + +import os +os.environ["HTTP_PROXY"] = "http://127.0.0.1:2089" +os.environ["HTTPS_PROXY"] = "http://127.0.0.1:2089" + +from ultralytics import YOLO +from ultralytics.utils.torch_utils import select_device +from ultralytics.nn.tasks import attempt_load_weights + +def get_weight_size(path): + stats = os.stat(path) + return f'{stats.st_size / 1024 / 1024:.1f}' + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--weights', type=str, default='yolov8n.pt', help='trained weights path') + parser.add_argument('--batch', type=int, default=1, help='total batch size for all GPUs') + parser.add_argument('--imgs', nargs='+', type=int, default=[640, 640], help='[height, width] image sizes') + parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') + parser.add_argument('--warmup', default=200, type=int, help='warmup time') + parser.add_argument('--testtime', default=1000, type=int, help='test time') + parser.add_argument('--half', action='store_true', default=False, help='fp16 mode.') + opt = parser.parse_args() + + device = select_device(opt.device, batch=opt.batch) + + # Model + weights = opt.weights + if weights.endswith('.pt'): + model = attempt_load_weights(weights, device=device, fuse=True) + print(f'Loaded {weights}') # report + else: + model = YOLO(weights).model + model.fuse() + + model = model.to(device) + example_inputs = torch.randn((opt.batch, 3, *opt.imgs)).to(device) + + if opt.half: + model = model.half() + example_inputs = example_inputs.half() + + print('begin warmup...') + for i in tqdm(range(opt.warmup), desc='warmup....'): + model(example_inputs) + + print('begin test latency...') + time_arr = [] + + for i in tqdm(range(opt.testtime), desc='test latency....'): + if device.type == 'cuda': + torch.cuda.synchronize() + start_time = time.time() + + model(example_inputs) + + if device.type == 'cuda': + torch.cuda.synchronize() + end_time = time.time() + time_arr.append(end_time - start_time) + + std_time = np.std(time_arr) + infer_time_per_image = np.sum(time_arr) / (opt.testtime * opt.batch) + + if weights.endswith('.pt'): + print(f'model weights:{opt.weights} size:{get_weight_size(opt.weights)}M (bs:{opt.batch})Latency:{infer_time_per_image:.5f}s +- {std_time:.5f}s fps:{1 / infer_time_per_image:.1f}') + else: + print(f'model yaml:{opt.weights} (bs:{opt.batch})Latency:{infer_time_per_image:.5f}s +- {std_time:.5f}s fps:{1 / infer_time_per_image:.1f}') \ No newline at end of file diff --git a/Tool-可视化/inference.py b/Tool-可视化/inference.py new file mode 100644 index 0000000..01c54a3 --- /dev/null +++ b/Tool-可视化/inference.py @@ -0,0 +1,31 @@ +import os +from ultralytics import YOLO +from pathlib import Path + +# === 设置路径 === +weights_path = './runs/segment/train6/weights/best.pt' # 模型权重 +source_dir = './Data/images/train' # 输入图片目录 +output_dir = './Data/result/train' # 推理结果输出目录 +os.makedirs(output_dir, exist_ok=True) + +# === 加载模型 === +model = YOLO(weights_path) + +# === 推理参数 === +results = model.predict( + source=source_dir, # 可为单图像路径、目录、视频、摄像头索引等 + save=True, # 是否保存图像 + save_txt=False, # 是否保存标签(分割任务不常用) + save_conf=True, # 是否保存置信度 + save_crop=False, # 是否保存目标裁剪图 + project=output_dir, # 输出根路径 + name='', # 子文件夹名(为空即直接输出到 output_dir) + exist_ok=True, # 允许覆盖已有文件夹 + imgsz=640, # 推理分辨率(默认640) + conf=0.02, # 置信度阈值 # 设置的越高,分类越少 + iou=0.45, # NMS IOU 阈值 + # device='cuda:0' # 改为 'cpu' 如果没有GPU + device='cpu' # ✅ 修改为 CPU 模式 +) + +print(f"✅ 推理完成,结果保存在:{output_dir}") diff --git a/Tool-可视化/my_dataset.yaml b/Tool-可视化/my_dataset.yaml new file mode 100644 index 0000000..e0cef03 --- /dev/null +++ b/Tool-可视化/my_dataset.yaml @@ -0,0 +1,28 @@ +# my_dataset.yaml +path: ./Data # 数据集根路径 +train: images/train # 训练集图片目录 +val: images/train # 如果没有验证集,可暂用训练集代替 +# ※ label放置位置 ※ +# 放在 labels 目录下,格式为: +# - labels/train/*.txt +# 或者还放在images/train/*.txt中 + +# 针对8bit PNG格式 +# 类别名,按你的实际颜色映射定义(可继续补充) +names: + 0: class_0 + 1: class_1 + 2: class_2 + 3: class_3 + 4: class_4 + + +# # 针对TXT格式 +# names: +# 0: background # 默认第0类是背景 +# 1: class_0 +# 2: class_1 + +# 分割任务专用字段 +# 如果你的 mask 是单通道 PNG(如 labelTrainIds.png),确保它们是 8-bit 灰度图 +# Ultralytics YOLO 会自动根据 names 映射类别 \ No newline at end of file diff --git a/Tool-可视化/train.py b/Tool-可视化/train.py new file mode 100644 index 0000000..2ffe31e --- /dev/null +++ b/Tool-可视化/train.py @@ -0,0 +1,7 @@ +from ultralytics import YOLO + +# Load a pretrained YOLO11 segment model +model = YOLO("yolo11n-seg.pt") + +# Train the model +results = model.train(data="./my_dataset.yaml", epochs=100, imgsz=640) \ No newline at end of file diff --git a/Tool-可视化/yolov11_heatmap_V1.py b/Tool-可视化/yolov11_heatmap_V1.py new file mode 100644 index 0000000..1a78c84 --- /dev/null +++ b/Tool-可视化/yolov11_heatmap_V1.py @@ -0,0 +1,139 @@ +import os +import cv2 +import numpy as np +import torch +from ultralytics import YOLO +from pytorch_grad_cam import GradCAM, GradCAMPlusPlus, XGradCAM, EigenCAM, HiResCAM, LayerCAM, RandomCAM, EigenGradCAM, KPCA_CAM +from pytorch_grad_cam.utils.image import show_cam_on_image +from pytorch_grad_cam.base_cam import BaseCAM + +class ActivationMaximizationTarget: + def __init__(self, channel=0): + self.channel = channel + + def __call__(self, model_output): + # model_output: [B, C, H, W] + return model_output[:, self.channel, :, :].mean() + +# 1. 中间封装类:只返回 [B, C, H, W] 特征图 +class YoloFeatureExtractor(torch.nn.Module): + def __init__(self, model): + super().__init__() + self.model = model + + def forward(self, x): + out = self.model(x) + + if isinstance(out, (list, tuple)) and len(out) > 1: + feat_candidates = out[1] + + if isinstance(feat_candidates, (list, tuple)): + for i, feat in enumerate(feat_candidates): + if isinstance(feat, torch.Tensor) and feat.ndim == 4: + print(f"[DEBUG] 找到特征图: item[1][{i}] -> shape={feat.shape}") + feat.requires_grad_() # 🔥 关键修复点 + return feat + else: + print(f"[DEBUG] item[1][{i}] 类型: {type(feat)}") + + raise RuntimeError("未找到可用于 CAM 的特征图") + +# 2. 加载 YOLOv11 模型 +# model_path = r"runs\segment\train2\weights\best.pt" +model_path = "yolo11n-seg.pt" # 替换为你的模型路径 +model = YOLO(model_path) +model.model.eval() + +# 3. 提取 CAM hook 层(最后一个 Conv2d) +target_layers = [] +for module in model.model.modules(): + if isinstance(module, torch.nn.Conv2d): + target_layers.append(module) +if not target_layers: + raise RuntimeError("未找到卷积层") +target_layers = [target_layers[-1]] # 使用最后一层 Conv2d + +# 4. 输出文件夹初始化 +output_root = "result_CAM_Method" +os.makedirs(output_root, exist_ok=True) +cam_methods = { + "GradCAM": GradCAM, + "GradCAMPlusPlus": GradCAMPlusPlus, + "XGradCAM": XGradCAM, + "EigenCAM": EigenCAM, + "HiResCAM": HiResCAM, + "LayerCAM": LayerCAM, + "RandomCAM": RandomCAM, + "EigenGradCAM": EigenGradCAM, + "KPCA_CAM": KPCA_CAM +} +for method_name in cam_methods: + os.makedirs(os.path.join(output_root, method_name), exist_ok=True) + +# 5. 遍历图像 +input_dir = "Data/img_dir" +for img_name in os.listdir(input_dir): + if img_name.lower().endswith((".jpg", ".jpeg", ".png", ".bmp")): + img_path = os.path.join(input_dir, img_name) + orig_image = cv2.imread(img_path) + if orig_image is None: + continue + orig_image = cv2.cvtColor(orig_image, cv2.COLOR_BGR2RGB) + orig_h, orig_w = orig_image.shape[:2] + + # letterbox resize + padding to 640x640 + target_size = 640 + scale = min(target_size / orig_w, target_size / orig_h) + new_w = int(orig_w * scale) + new_h = int(orig_h * scale) + resized_image = cv2.resize(orig_image, (new_w, new_h), interpolation=cv2.INTER_LINEAR) + pad_w = target_size - new_w + pad_h = target_size - new_h + pad_left = pad_w // 2 + pad_right = pad_w - pad_left + pad_top = pad_h // 2 + pad_bottom = pad_h - pad_top + pad_color = (114, 114, 114) + padded_image = cv2.copyMakeBorder(resized_image, pad_top, pad_bottom, pad_left, pad_right, + cv2.BORDER_CONSTANT, value=pad_color) + + # 图像归一化 + tensor 转换 + padded_image_float = padded_image.astype(np.float32) / 255.0 + device = next(model.model.parameters()).device + input_tensor = torch.from_numpy(padded_image_float.transpose(2, 0, 1))[None].to(device) + + # 6. 用 wrapper 包装模型以兼容 CAM + wrapped_model = YoloFeatureExtractor(model.model) + + # 遍历每种 CAM 方法 + for method_name, cam_class in cam_methods.items(): + with cam_class(model=wrapped_model, target_layers=target_layers) as cam: + target = [ActivationMaximizationTarget(channel=0)] + cam_result = cam(input_tensor=input_tensor, targets=target)[0] + + # 如果输出为 3 维(如 [C, H, W]),取通道平均为 [H, W] + if isinstance(cam_result, torch.Tensor): + cam_result = cam_result.detach().cpu().numpy() + if cam_result.ndim == 3: + cam_result = cam_result.mean(axis=0) + elif cam_result.ndim != 2: + raise ValueError(f"[CAM ERROR] Unexpected CAM shape: {cam_result.shape}") + grayscale_cam = cam_result # 安全赋值 + + cam_cropped = grayscale_cam[pad_top:pad_top + new_h, pad_left:pad_left + new_w] + if cam_cropped.size == 0: + continue + cam_resized = cv2.resize(cam_cropped, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR) + cam_resized = cam_resized - cam_resized.min() + cam_resized = cam_resized / (cam_resized.max() + 1e-8) + + orig_image_float = orig_image.astype(np.float32) / 255.0 + overlay_image = show_cam_on_image(orig_image_float, cam_resized, use_rgb=True) + heatmap = cv2.applyColorMap(np.uint8(255 * cam_resized), cv2.COLORMAP_JET) + heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB) + + base_name = os.path.splitext(img_name)[0] + overlay_path = os.path.join(output_root, method_name, f"{base_name}_overlay.jpg") + heatmap_path = os.path.join(output_root, method_name, f"{base_name}_heatmap.jpg") + cv2.imwrite(overlay_path, cv2.cvtColor(overlay_image, cv2.COLOR_RGB2BGR)) + cv2.imwrite(heatmap_path, cv2.cvtColor(heatmap, cv2.COLOR_RGB2BGR)) diff --git a/Tool-可视化/yolov11_heatmap_V2.py b/Tool-可视化/yolov11_heatmap_V2.py new file mode 100644 index 0000000..a61b757 --- /dev/null +++ b/Tool-可视化/yolov11_heatmap_V2.py @@ -0,0 +1,189 @@ +import os +import cv2 +import numpy as np +import torch +from ultralytics import YOLO +from pytorch_grad_cam import ( + GradCAM, GradCAMPlusPlus, XGradCAM, EigenCAM, + HiResCAM, LayerCAM, RandomCAM, EigenGradCAM, KPCA_CAM +) +from pytorch_grad_cam.utils.image import show_cam_on_image +from pytorch_grad_cam.base_cam import BaseCAM + + +class ActivationMaximizationTarget: + def __init__(self, channel=0): + self.channel = channel + def __call__(self, model_output): + if model_output.ndim == 4: + # [B, C, H, W] + return model_output[:, self.channel, :, :].mean() + elif model_output.ndim == 3: + # [C, H, W] + return model_output[self.channel, :, :].mean() + else: + raise ValueError(f"Unsupported model_output shape: {model_output.shape}") + + +class YoloFeatureExtractor(torch.nn.Module): + def __init__(self, model): + super().__init__() + self.model = model + + def forward(self, x): + out = self.model(x) + if isinstance(out, (list, tuple)) and len(out) > 1: + if isinstance(out, (list, tuple)): + feat_candidates = out[1] if len(out) > 1 else out[0] + if isinstance(feat_candidates, (list, tuple)): + for i, feat in enumerate(feat_candidates): + if isinstance(feat, torch.Tensor) and feat.ndim == 4: + feat.requires_grad_() + return feat + raise RuntimeError("未找到可用于 CAM 的特征图") + + +# ------------------------------ 设置路径 ------------------------------- +# model_path = "yolo11n-seg.pt" # ← 替换为你的模型路径 +model_path = r"runs\segment\train6\weights\best.pt" +model_name = os.path.splitext(os.path.basename(model_path))[0] +output_root = f"result_CAM_Method_{model_name}" +input_dir = r"Data\images\train" + +# ------------------------------ 加载模型 ------------------------------- +model = YOLO(model_path) +model.model.eval() +device = next(model.model.parameters()).device + +# ------------------------------ 提取所有卷积层 ------------------------------- +conv_layers = [] +for idx, layer in enumerate(model.model.modules()): + if isinstance(layer, torch.nn.Conv2d): + conv_layers.append((idx, layer)) +# conv_layers.reverse() +if not conv_layers: + raise RuntimeError("未找到卷积层") + +# ------------------------------ 设置 CAM 方法 ------------------------------- +cam_methods = { + "GradCAM": GradCAM, + "GradCAMPlusPlus": GradCAMPlusPlus, + "XGradCAM": XGradCAM, + # "EigenCAM": EigenCAM, # 风险较高,容易炸内存 + "HiResCAM": HiResCAM, + "LayerCAM": LayerCAM, + "RandomCAM": RandomCAM, + "EigenGradCAM": EigenGradCAM, + # "KPCA_CAM": KPCA_CAM # 风险较高,容易炸内存 +} + +# 创建目录 +for method in cam_methods: + os.makedirs(os.path.join(output_root, method), exist_ok=True) + +# ------------------------------ 处理图像 ------------------------------- +for img_name in os.listdir(input_dir): + if not img_name.lower().endswith((".jpg", ".jpeg", ".png", ".bmp")): + continue + + img_path = os.path.join(input_dir, img_name) + orig_image = cv2.imread(img_path) + if orig_image is None: + continue + orig_image = cv2.cvtColor(orig_image, cv2.COLOR_BGR2RGB) + orig_h, orig_w = orig_image.shape[:2] + + # Resize + padding + target_size = 640 + scale = min(target_size / orig_w, target_size / orig_h) + new_w, new_h = int(orig_w * scale), int(orig_h * scale) + resized_image = cv2.resize(orig_image, (new_w, new_h), interpolation=cv2.INTER_LINEAR) + pad_w, pad_h = target_size - new_w, target_size - new_h + pad_left, pad_right = pad_w // 2, pad_w - pad_w // 2 + pad_top, pad_bottom = pad_h // 2, pad_h - pad_h // 2 + padded_image = cv2.copyMakeBorder(resized_image, pad_top, pad_bottom, pad_left, pad_right, + cv2.BORDER_CONSTANT, value=(114, 114, 114)) + padded_image_float = padded_image.astype(np.float32) / 255.0 + input_tensor = torch.from_numpy(padded_image_float.transpose(2, 0, 1))[None].to(device) + input_tensor.requires_grad_() # ★★★ 加这行! + + wrapped_model = YoloFeatureExtractor(model.model) + + # -------------------- 遍历每一层 + 每个方法 --------------------- + for layer_idx, layer in conv_layers: + print(f"\nProcessing Layer {layer_idx}: {layer.__class__.__name__}") + for method_name, cam_class in cam_methods.items(): + try: + # 方法执行前预检查特征图尺寸 + if cam_class in [EigenCAM, KPCA_CAM]: + # 特征图太大提前跳过 + try: + # 临时 forward 一次拿特征图大小 + with torch.no_grad(): + feat = layer(input_tensor) + feat_shape = feat.shape # [B, C, H, W] + numel = feat.numel() + if numel > 4096 ** 2: # 超过 16M 元素就跳过 + print(f"[SKIP] {method_name} on Layer {layer_idx}: 特征图过大 shape={feat_shape}") + continue + except Exception as e: + print(f"[SKIP] {method_name} on Layer {layer_idx}: 特征图检查失败: {e}") + continue + print(f" Using {method_name}...") + + # Way 1: 使用 wrapper 包装模型 + # with cam_class(model=wrapped_model, target_layers=[layer]) as cam: + # targets = [ActivationMaximizationTarget(channel=0)] + + # Way 2: + with cam_class(model=model.model, target_layers=[layer]) as cam: + targets = [ActivationMaximizationTarget(channel=0)] + + cam_output = cam(input_tensor=input_tensor, targets=targets) + + # 正确顺序:先判断类型,再使用变量 + if isinstance(cam_output, (list, tuple)): + cam_result = cam_output[0] + else: + cam_result = cam_output + + # Tensor → Numpy + if isinstance(cam_result, torch.Tensor): + cam_result = cam_result.detach().cpu().numpy() + + # 处理不同维度 + if cam_result.ndim == 4: + cam_result = cam_result[0].mean(axis=0) + elif cam_result.ndim == 3: + cam_result = cam_result.mean(axis=0) + elif cam_result.ndim == 2: + pass # OK + else: + print(f"[SKIP] {method_name} on Layer {layer_idx}:CAM 结果维度异常 {cam_result.shape}") + continue + + # EigenCAM 特征图过大保护 + if cam_class in [EigenCAM, KPCA_CAM] and cam_result.size > 4096**2: + print(f"[SKIP] {method_name} on Layer {layer_idx} 特征图太大,跳过") + continue + + # CAM 后处理 + cam_cropped = cam_result[pad_top:pad_top + new_h, pad_left:pad_left + new_w] + cam_resized = cv2.resize(cam_cropped, (orig_w, orig_h), interpolation=cv2.INTER_LINEAR) + cam_resized = (cam_resized - cam_resized.min()) / (cam_resized.max() + 1e-8) + + overlay_image = show_cam_on_image(orig_image.astype(np.float32) / 255.0, + cam_resized, use_rgb=True) + heatmap = cv2.applyColorMap(np.uint8(255 * cam_resized), cv2.COLORMAP_JET) + heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB) + + layer_name = layer.__class__.__name__ + base = os.path.splitext(img_name)[0] + fname = f"{layer_idx}_{layer_name}_{base}" + overlay_path = os.path.join(output_root, method_name, f"{fname}_overlay.jpg") + heatmap_path = os.path.join(output_root, method_name, f"{fname}_heatmap.jpg") + cv2.imwrite(overlay_path, cv2.cvtColor(overlay_image, cv2.COLOR_RGB2BGR)) + cv2.imwrite(heatmap_path, cv2.cvtColor(heatmap, cv2.COLOR_RGB2BGR)) + + except Exception as e: + print(f"[ERROR] {method_name} on Layer {layer_idx} failed: {e}") diff --git a/Tool-可视化/使用流程.txt b/Tool-可视化/使用流程.txt new file mode 100644 index 0000000..60d473c --- /dev/null +++ b/Tool-可视化/使用流程.txt @@ -0,0 +1,7 @@ +0. cd 0_图片Labels生成; python 4_deal_labels.py 对label进行处理;# 放入Data\ann_dir中 +1. 【推荐】使用 Tool_Check_and_Gen_Txt_Label_sort_label.py 生成数据【按照从大到小顺序0~N个class】 +使用 Tool_Check_and_Gen_Txt_Label_ori_label.py 生成数据【原始图片中的class】 +2. 修改 my_dataset.yaml 修改路径、分类【分类数和步骤1程序生成数量相同】; +2.1. 删除label下的.cache文件 +3. python train.py 、 python inference.py 【修改pt模型】 进行训练、推理 +4. python yolov11_heatmap_V2.py 进行热图可视化 diff --git a/Tool-图片堆叠/1_check_picture_pair.py b/Tool-图片堆叠/1_check_picture_pair.py new file mode 100644 index 0000000..6bce7c0 --- /dev/null +++ b/Tool-图片堆叠/1_check_picture_pair.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import argparse +import sys +from pathlib import Path +from typing import Set, Dict + +# 定义支持的文件扩展名 +VALID_EXTENSIONS = {'.png', '.jpg', '.jpeg'} + +def process_directory(path: Path, + prefix: str = "", + suffix: str = "") -> (Set[str], Dict[str, str]): + """ + 处理单个目录,提取所有有效文件的文件名(stem),并根据前缀和后缀进行规范化。 + + 返回: + normalized_stems (Set[str]): 规范化处理后的文件名集合。 + original_to_normalized_map (Dict[str, str]): 原始文件名到规范化文件名的映射。 + (这里我们反过来,用 规范化 -> 原始,更方便后续查找) + """ + if not path.is_dir(): + print(f"错误: 路径 '{path}' 不是一个有效的目录。", file=sys.stderr) + return set(), {} + + normalized_stems: Set[str] = set() + normalized_to_original_map: Dict[str, str] = {} + + for file_path in path.glob('*'): + # 确保是文件,并且扩展名在我们的有效列表中 + if file_path.is_file() and file_path.suffix.lower() in VALID_EXTENSIONS: + original_stem = file_path.stem + normalized_stem = original_stem + + # 仅当提供了前缀/后缀时才进行处理 + if prefix and normalized_stem.startswith(prefix): + normalized_stem = normalized_stem[len(prefix):] + + if suffix and normalized_stem.endswith(suffix): + normalized_stem = normalized_stem[:-len(suffix)] + + # 检查处理后是否重名,如果重名则发出警告 + if normalized_stem in normalized_to_original_map: + print(f"警告: 规范化后文件名发生冲突。") + print(f" '{original_stem}' 和 '{normalized_to_original_map[normalized_stem]}' 都变成了 '{normalized_stem}'") + + normalized_stems.add(normalized_stem) + normalized_to_original_map[normalized_stem] = original_stem + + return normalized_stems, normalized_to_original_map + + +def main(): + parser = argparse.ArgumentParser(description="比较两个文件夹中的文件名是否匹配。") + parser.add_argument("-i", "--image", + type=Path, + required=True, + help="Image 文件夹路径") + parser.add_argument("-l", "--label", + type=Path, + required=True, + help="Label 文件夹路径") + parser.add_argument("-p", "--prefix", + type=str, + default="", + help="在 Label 文件名中要忽略的前缀") + parser.add_argument("-s", "--suffix", + type=str, + default="", + help="在 Label 文件名中要忽略的后缀") + + args = parser.parse_args() + + # 1. 处理 Image 文件夹 (不需要前缀后缀) + print(f"--- 正在处理 Image 文件夹: {args.image} ---") + image_stems, _ = process_directory(args.image) + if not image_stems: + print(f"未在 Image 文件夹中找到任何 .png 或 .jpg 文件。") + + # 2. 处理 Label 文件夹 (需要前缀后缀) + print(f"\n--- 正在处理 Label 文件夹: {args.label} ---") + print(f"(忽略前缀: '{args.prefix}', 忽略后缀: '{args.suffix}')") + label_stems_normalized, label_norm_to_orig_map = process_directory( + args.label, + args.prefix, + args.suffix + ) + if not label_stems_normalized: + print(f"未在 Label 文件夹中找到任何 .png 或 .jpg 文件。") + + # 3. 执行比较 (使用集合运算) + matching_stems = image_stems.intersection(label_stems_normalized) + + # Image 文件夹中多余的 (在 Image 中,但不在 Label 中) + extra_in_image = image_stems.difference(label_stems_normalized) + + # Label 文件夹中多余的 (在 Label 中,但不在 Image 中) + extra_in_label_normalized = label_stems_normalized.difference(image_stems) + + # 将 Label 中多余的文件名转换回原始名称 + extra_in_label_original = {label_norm_to_orig_map[stem] for stem in extra_in_label_normalized} + + # 4. 输出结果 + print("\n" + "="*30) + print(" 匹配结果报告") + print("="*30) + + print(f"\n匹配的文件总数: {len(matching_stems)}") + + print("\n--- Image 文件夹中多余的文件 (共 {} 个) ---".format(len(extra_in_image))) + if not extra_in_image: + print("(无)") + else: + for file_stem in sorted(extra_in_image): + print(file_stem) + + print("\n--- Label 文件夹中多余的文件 (共 {} 个) ---".format(len(extra_in_label_original))) + print("(显示的是原始文件名,非规范化名称)") + if not extra_in_label_original: + print("(无)") + else: + for file_stem in sorted(extra_in_label_original): + print(file_stem) + + print("\n" + "="*30) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Tool-图片堆叠/2_TOOL_stack_pics.sh b/Tool-图片堆叠/2_TOOL_stack_pics.sh new file mode 100644 index 0000000..9d200e6 --- /dev/null +++ b/Tool-图片堆叠/2_TOOL_stack_pics.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +usage() { + echo "Usage: $0 -i -l -r [ -a -p -s -h]" + echo "对image图片和label图片进行匹配(-i、-l -r均不能为空)(-p -s默认为空"" -a默认为\"0.3\") " + echo "-i:原始image的路径,-l:原始label的路径,-p:前缀内容,-s:后缀内容(不用管文件后缀名),-h:帮助" + echo "e.g. bash 2_TOOL_stack_pics.sh -i ./ori -l ./label -r ./result_0.3透明度 -a 0.3 -p Prefix -s _label" +} + +ori_image_directorys="" +ori_label_directorys="" +stack_result_directorys="" +prefix="" +suffix="" +alpha="0.3" + +while getopts "hl:i:r:p:s:a:" opt; do + case $opt in + h) + usage + exit 0 + ;; + i) + ori_image_directorys=$OPTARG + ;; + l) + ori_label_directorys=$OPTARG + ;; + p) + prefix=$OPTARG + ;; + s) + suffix=$OPTARG + ;; + r) + stack_result_directorys=$OPTARG + ;; + a) + alpha=$OPTARG + ;; + *) + echo -e '\033[31m!!! Error, Illegal input !!!\033[0m' + usage + exit 1 + ;; + esac +done + +# 判断输入地址是否为空 +if [ -z "$ori_label_directorys" ] || [ -z "$ori_image_directorys" ] || [ -z "$stack_result_directorys" ]; then + echo -e "\033[31m输入地址 -i -l -z 存在空地址\033[0m" + usage + exit 1 +fi + +# 地址转化 +ori_image_directory=$(readlink -f "$ori_image_directorys") +ori_label_directory=$(readlink -f "$ori_label_directorys") +stack_result_directory=$(readlink -f "$stack_result_directorys") +if [ -z "$ori_label_directory" ] || [ -z "$ori_image_directory" ]|| [ -z "$stack_result_directory" ]; then + echo "image、label、result存在无法解析地址,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directorys" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + echo -e "\033[31mori_label_directory\033[0m: $stack_result_directorys" + exit 1 +fi +if [ ! -d "$ori_label_directory" ] || [ ! -d "$ori_image_directory" ]; then + echo "image、label两目录有一个不存在,程序退出" + echo -e "\033[31mori_image_directory\033[0m: $ori_image_directory" + echo -e "\033[31mori_label_directory\033[0m: $ori_label_directorys" + exit 1 +fi + +# 获取当前脚本的路径和名称 +script_path=$(dirname "$0") +# 将当前目录更改为脚本所在的路径 +cd "$script_path" + +# 激活conda环境 +source /home/"$USER"/miniconda/bin/activate Deal_Data + +echo -e "\033[32m_____ 2_TOOL_stack_pics.sh _____\033[0m" +echo -e "\033[33mimage所在文件夹为$ori_image_directory\nlable所在文件夹为$ori_label_directory\033[0m" +# 遍历label目录 +for file_path in "$ori_label_directory"/*; do + # 判断是否是文件 + if [[ -f "$file_path" ]]; then + file_name=$(basename "$file_path") + # 判断文件名是否符合规范 + if [[ "$file_name" =~ .*\.(jpg|png|bmp|JPG|PNG|BMP) ]]; then # 判断是否有为图片 + # if [[ "$file_name" =~ "$prefix".*"$suffix".*\.(jpg|png|bmp|JPG|PNG|BMP)$ ]]; then # 判断是否有满足要求的文件名 + # 抽取文件名(有前缀、后缀的抽取前缀、后缀里面的,没有的返回整个) + if [ -z $prefix ];then + file_name_extract=$(echo $file_name | sed "s/"$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + else + file_name_extract=$(echo $file_name | sed "s/".*$prefix"\(.*\)"$suffix".*/\1/" | sed "s/\(.*\)\.\(jpg\|png\|bmp\|JPG\|PNG\|BMP\)$/\1/") + fi + # 从label目录中看是否有此文件 + file_name_other=$(ls $ori_image_directory | grep $file_name_extract) + file_name_other=$(echo "$(echo "$file_name_other" | sed '/^$/d')" | head -n1) # 提取出文件名 + # 如果另一个目录没有此文件的话 + if [ -z "$file_name_other" ]; then + echo "$file_name label中对应内容未在$ori_image_directory搜索到" + # 建立相关存储文件夹 + if [ ! -d "$ori_label_directory/Not_pair_pics" ]; then + mkdir -p "$ori_label_directory/Not_pair_pics" # 建立存储文件夹 + fi + # 移动相关文件 + cp "$ori_label_directory/$file_name" "$ori_label_directory/Not_pair_pics" + echo "$file_name" >> "$ori_label_directory/Not_pair_pics/not_pair.txt" + else # 如果另一个目录有此配对文件的话,则运行相关程序 + echo "image中的$file_name_other,与lable中的$file_name" + mkdir -p "$stack_result_directory" + python 2_stack_picture.py "$ori_image_directory/$file_name_other" "$ori_label_directory/$file_name" "$stack_result_directory" "$alpha" + echo "" + fi + + fi + else + echo "$file_path不是文件" + fi +done \ No newline at end of file diff --git a/Tool-图片堆叠/2_stack_picture.py b/Tool-图片堆叠/2_stack_picture.py new file mode 100644 index 0000000..7fcf130 --- /dev/null +++ b/Tool-图片堆叠/2_stack_picture.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +import cv2, os, sys + +def Stack_pic(Background_path, Overlay_path, Result_dir, alpha=0.3): + # 读取两张没有alpha通道的图片 + img1 = cv2.imread(Background_path) # 底层图片 + img2 = cv2.imread(Overlay_path) # 顶层图片 + + Result_name = os.path.splitext(os.path.basename(Background_path))[0] + + # 将img2调整为与img1大小相同 + img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0])) + + # 将img2的透明度调整为20% + overlay_alpha = alpha + + # 将img2叠加到img1上 + overlay = cv2.addWeighted(img1, 1 - overlay_alpha, img2, overlay_alpha, 0) + + # 保存结果 + if not os.path.exists(Result_dir): + os.makedirs(Result_dir) + cv2.imwrite(os.path.join(Result_dir, Result_name+'.png'), overlay) + print("堆叠图片写入地址:", os.path.join(Result_dir, Result_name+'.png')) + + +if __name__ == '__main__': + Background_path = sys.argv[1] # 背景所在路径 + Overlay_path = sys.argv[2] # 上层图片所在路径 + Result_dir = sys.argv[3] # 结果所在目录 + # 透明度,默认为0.3 + try: + alpha = float(sys.argv[4]) + if(alpha > 1 or alpha < 0): + print("alpha 透明度输入不正确,其值应该在0~1之间") + alpha = 0.3 + except: + alpha = 0.3 + # 进行对叠程序 + Stack_pic(Background_path, Overlay_path, Result_dir, alpha) \ No newline at end of file diff --git a/Tool-图片堆叠/※使用手册 b/Tool-图片堆叠/※使用手册 new file mode 100644 index 0000000..6cbebea --- /dev/null +++ b/Tool-图片堆叠/※使用手册 @@ -0,0 +1,5 @@ +0. 将原始图片移动到./ori文件夹、标注图片移动到./label文件夹下 +1. 判断两个文件中文件名是否匹配 +python 1_check_picture_pair.py -i ./ori -l ./label # -p "mask_" -s "_seg" +2. 图片堆叠 +bash 2_TOOL_stack_pics.sh -i ./ori -l ./label -r ./result_0.3透明度 -a 0.3 -s _label \ No newline at end of file diff --git a/requirements-seg_smp.txt b/requirements-seg_smp.txt new file mode 100644 index 0000000..c39da9f --- /dev/null +++ b/requirements-seg_smp.txt @@ -0,0 +1,24 @@ +# Install PyTorch first: +# pip install torch==2.8.0+cu129 torchvision==0.23.0+cu129 --index-url https://download.pytorch.org/whl/cu129 + +albumentations==2.0.8 +grad-cam==1.5.5 +matplotlib +mmcv==2.1.0 +mmengine==0.10.7 +mmsegmentation==1.2.2 +numpy +opencv-python==4.12.0.88 +pandas +scikit-learn +scipy +seaborn +tensorboard +tqdm +ultralytics==8.3.203 +wandb +ftfy +regex +fvcore +weave +segmentation-models-pytorch @ git+https://github.com/qubvel/segmentation_models.pytorch@4d20629756005085ca0f1f21605c796298ca0c16

MmcrSAt1=dxs^0NPn3)$^cQvvQS(EUa{e-?IM!u0w4r ziGxQ?2CwIks@Ft(1p}EB_1lk1KWmgj1&O@+2SB zKHQco$38o6_iOcmG8j!@KS_@meaLF?S9Fo>565B%dHUfm$g8{K6O{`+dJW z6#)ND%8ZyIL|`JpJowaXo}A6*+E5y>vzb8VG^`MDk!*qmASwVNlv z515=6tG*<XH!USOY-|FmZuaOXJ{@-W_L$$pfds2sHW(+_ z2O;Y^u7kgH(Nb@&?FR?@?)#Ia9w%~ z7^N<(Yfch^`DZQzAO?IkLtnC;r+(!vp6t%?vGV5&y;(cp5efZ8mvzj?p*daAcwV-o zy;7d7qtgW-)2jKlB3WElfX=U2+727p;#rSoiqMywFW<1fY_+;uxq5$}4$sekZpJiO zmaYnT^rgPn<=U^;tS%yj+l(>bz0fnZs828P#oL$UuYGAp`#Zb;t1>Zdrdrs+fhSg4 z2zi1FfcfSvJllW?k-`r=9Hb-Z19obdAWQbz0pGgqN)PZeiIzPW=NJr8j_In)A3@t6 z=?LOzxSH=Jy!KAL-A1yT?dYj}zaD7mAz6EXpu#@q26WGK(IJ&2o{reIR=HfxGkQWG zC@x*k!2`PzCZaWk&Zb{cAy35YZCd8jV53kU?VZ9*weFXzDt%p^!0#P;OM#)Yu(Bu`W-lV$GZ2=}L zm#c%eC3zo{xe!qw+n!QL3sYB2F)mj?buDE=YsN&U0hI^7#5OjBqZ@DhOdoO^v<$fQ zrLCmD&w$$*uq#x3Yd`2F;T}N`D{X}r%p9Q+bhDWr=Il!!=_Arh2w3Rv(e&=msII5` zX@URx_0LIr_cIuvzy0vkrzQdBV>^EoW@IM>`rZ$z-c-tCa~xQCTxKlY`7)^8ZbR5I zAo|&kaY;_ukgukMPP!P#Qrj02zvP3nt-2j0`pP8#w92&0;K5?&*zJyPkG$`~!h+K* z;83hkgs0ia;y0?m6-Q4gt`)!HFn%1Ee z@Y^ay9uC*q>S)0Y2qB`L1rR#ls19(2CwwOq2`}3+Idq=Cy`0fN(E{4tF_ghdlE@)*@Sa!IM@3>fISFu-hGH$87-|1PzjGy)(?4 z58UhaWL>*_z!x8`4{y&RHMR9Jt9B;(mwAFO$KvIkiYHh)y!1-u((oiPlW+L$bBAd<4f!uF~(QeT_ncrsaL=vl;%kz z*x+}vc`hX@=$*aJprj1z`OP48cdW{kgW?J17LzNHM`W19#%P1`8DRjda}1!m)1Hw) zv7?r8cTz0KZ9mH)bO~i5QB^YIu`mNT!sxL9cnx*L!;HL1;Q!nDP^L4~;o&n+upn@h z?Lkyyi|2*bp1O{zgya-_@ciVOzpai_U@c#`hE7+pV|4*V6<$Ld|#*gv}tq|%CBoCms&jY{UpZ55A&sB!r+nHZ|55q0B z+v9>s`RiBz!au`b{{{clpMKz^mdJu42Bo_8_vrHka+I875`9&2nW)QU*7KZpF*>Ht zC>;Gsh)O-I`!R3eh^=h2)6a8P4V(on(}c!;{Up48p;;c}Qu|#h?`Uo<7QC)%^QFcH zs#Z+giME>oPn!qYfxrLN+h4-lZ_yZ-ur}>{l#K9^4M2CKoR14EO{CU7>pZt77z>f& zzdQx+X&48^+k@t zf(~d_j2E!jVFyJ)shT)gnQ_m5Ms>7Fs_CG3Z@W+#5eo;sk0_;524ZOE0*BbITDs)s5@0Ur7rzt!bGmInS)jhj z>h{`$lwi;_&Xkg3%fMeBzx@y=&c|<`sHo8dN7q62?)fD66@g|-5oS4$*^!+)SK2Fp z*#uME^e#aaj3{9Awea`pdG%Rw-kjj9gy_Lk*En;W1cM&QQy_s(e@FD`?<_EY-9~Pe zi||!YiFWxNpU-t{3^htI_pWts$je`ZEH z39A&cFZ&Y%3b{4k5p!~iJT3g)7KOyYbQ|oWD98F`eVmZFYaGu7_KgF~5#2W{HmacX zB)hf=T@b350;3BCxv>(O;saKK4ht?)BPS}S{XX80w8jDXC|EKr=UCL-1Fw0ocpIMq ziS))@NiM_Do_vIH`Y#uiKC@IiI$@)oF=wl0O7(-mV{%5=`)YnZ=F5Ue{-$Fr*&y|i zrDit>oQJEt4!eI4{fPD9P4R1Z=u8l&tk3*Ed%1_$WULd2UmS1{o_?@N5Zqt@=*hXsmbpYm?2l!daEi-ib(dkb2?392SNwkyE^4%l}y1VbqsD3|`YFF8!jjM5SdIoT+03V^juXo0RIb^h~-1fSDnR{d} zyuO(%8%TXyc?1aEYLO3YW_Pz(Ag8>!>7v=I_*>7c5=JUn#u{o;Lm}z%x}B|0Fp@)zq0q!J$QBv~ zblA~NRfHfBLuy{76=haA&HXv1Q78#9&rtD&ieX#j>z#KDha#GvK&5HDK*8P)D6C|f zOG+}@*uw#QcvZ@wwSV>RUjhMMRE~o5JXX-gD9>S;G5a^}BTEhMSqOfE9^AaP0J}@D z=jMJW&P<*;m?o}&dJrijfq)=T!%i#c-02E;SEIolF}MgkKMU*yi3J^P7&~t#pYdT= z*)1C_l;&0a6DE>c8WnRGod62p-~}pl8>}|=_>2P`CVEsw2#VURl*L{%ELqodi+bc! z5y@o4KBG6_pKXa)2P-P-z~bah4dc_tjGBigS~BYoDId&55T0p=?DETh;`|#x2cLKU zN`9hE%21ArS)C20_5e1Vp7k>zDS|#3`r~PqstA4ujgMiJ|D_G8wA#R%hweo$D+;># zzBOkmZ31FMmJnvM*@YAGaCe4x;8H3sFSK4Dj1ZI62?_J}@UT(KmP@Or`4QwMz~Uo^ z>ic7Y%}S6xu-IarSR7xW#VPw22Fh-hL{REhJZn8TsVE2gZ7r6(EfjT!agvFgYn%ft zlt>z=St{2PnjO&{E=S&SeR;v=m#5{oqL+j)EnFxQ0X1Qe(omQj-^3L?A{3zBzEfCw zg5ZT0?$`K1iyqlKl~UxKD;_8JG#19Uq@wICNG&b3P-wBn<ru zl2QG?fv5V<7JZ!A7ok+D-mDw!((P?hAL}>hNu%h+g--O2s(`t!hJJMJSoDNEHw_tV{nQ*h-L^`lmAuih%@3NOOYm?is{Pet)=odiKynt(m9wq^ z)wZ~U(S3TjLj*c}tuJXLV|#}R^T)EN42Pq58>u#i#M^df0@X(MyriU>!tICyo4_OF zS6~=g&@6L_UQSJ=B&Y+7aT?|+;l_W%Oy_B)bS3%`E3G&xig;Hm{H!!<`p}LPH0W}(rtobF!Wjkr)QWvzy9FR+6s^`` z0s*|B-tLNtZFJ+3G~sIsJBZEK#OnL+Qq%k1*K+IaB~XPXe+mp6IMI>A-ga3Jpb$Ai zMeT093n5=xK6}eeClNQOP)hc+L^51fAHG8-+h%^j_;~6HE?EIwF{&%Ht%htejXtBK z(w+_K&{icNR98MiQnSzSZo~&0OHzo$#EN3>6R$dj_7i!|-n56LO)Xa|>c<0inm%&R!!dfd7MP123XhQFNw!jeB3@1K zYF2tl?T4^i(9zOGLf2sM&mKqc9Rq5$Ng&ySakHmoQsaG)1P5@KAobIx!)=5(7 zwLJ!}*}0LkP9>tIlogn$lOCViuq_LIeC-fQs)Bad4s^NY5Wu^_!5Meo9@%~i&+qK4 zDPRL@ebm$i(Eo<>H};(2c;OpuJln8>30lXJ2b@8YZN>mtjGR0755q4ue1xK!ZFD)u z4Dial39RtVHXWR9`^0=p8El5e)O2Zlq-G0V3)CPeHCUQl!TbPcA-yb$bdSs=-g=uZ zD4}EO=G3=&{eF1+5~z182i5t@q9gPWOClmrMROhuVpu6hX;LzJBov3?K*H|QrmDD> z0PhQNM+(mc?pF0zdU(_%5`WEQta>hab2)6QoonR@@O6e*AT3geD+WLuo?bK63`RO6 zCaQ{+RzIO)QAr4)B1F8;U#lq-u|0eRV0@&;?Y9n%TEdTjmUM~eMV%+`2-5<-yOK8N zzAH>bJy6^f_c6^kLUdpgikf*=(5OiYSVaIZ7bHxww&m7G9IxcQ+{{8@N`8!yx3mh4 z?}0Dgew_Xr&flb{mGBj8=YTltOBF+Is^`jAt(sS#y`1eL{@%*79Wv<J^Yl9Uz|5bgk^=NV>HpQ8z&P|4w~AfG&Chcg!|oSqZ(3MTi?`J9wC&;wM-SO^6h zB%!OWBO3&+XBqL|zJ5k~na^x_%BcmW`p~eGr-rx~*X~(FTJgKqTOKqY0k(O9=S=T~ z@05mNNqnF>tk(vKHlV`O&3U{a1oET3J7TWdT8-hbX&O#KM4ft4+qR!iBO{y$rDVsd zx@BnP|0AR!>3LsFAG9DhJSXl4rfo0Jzou4<@}guTfX(3tlH@47#{GonhY1I{ z^Hlw5`aGYC568it$hHd<0Sac6BSwowWDG7D=8C#FX=N>}uokre-W?%DscxVVjZg7d zX6T1dGOb?*MS0qrcMpt33QS;14u`;n+idr#K1^XN`ID8T@mHy59@DEbHs(UHJ zx`Z_O{VKY79XJ(*;R&-%o_~`*hR!H{W+%%K2mrzve-dBhbaN>RG#274<|<;GhCa()Ia(m~PpX04HVjTNG9{HA8QNKb#de9~jJ8 z(|>ZnLj&!m;WXktviNNpH*WdNkDIKm+hWfRvpr;J=~?tIFX+rr$$5oZu6=M&8=43( zMeQf$UJfdg;ivp2kKgttA_>=H+h?kzS6=S3hWgUfB}FbS%%vI$yoUHswS3o z4hmHlgaTj5#3qoDph9?(gLHj~YR^)a4Q+)gk3I;`e|`?n&(HkDEZjY>CoWM^8?n!_ zw#dgSPU>n90G&Ut#6P$Gg9Q8)!;WG#o+xIwHV}dAQ(lxutCGyL;?Rw?=bqwb9oD%z zRCetoT-{m0i$GNzq$N`&@#=d&a*nEn3Fa@MK4{}pXwnA>R2ZG7pLf;hUGbGtM}aZn zLPex=5l(ICsTZ@}_Nh=oTu(r~&p9E)-KynR42B~&pqp4F*gIaSoq`F%3d>EG)q%s%L23FXx+*US;2Yzqd2gqaF1-H9 z9v9N;z2DkmO{e-lnIlcI$0m0S**`K$#f0QTmt2=hu`cLUTiKXZ^wF4$IVKC>RS&$s$ z>paLl?qdJpbN)iHYHMHR>ClE#J1dj#^2y;vv&vc+@>IJgolCs~YcOJQvz`xzbV- zGZxPo!G<6gS~_mdiQuBJujypH_uKIH>E->WP%~EqLp1deG%hy6ZU3EcThk&#Xpnjj zgdmg1wt5eC5P_kvr}}byt_BF>k^B|L9`mKW1Ejr|j~TDH;e=e?l}%_haj{O0O@oE{ z&usnTQTy=6GZ4trN^!=mse%4%k5~ZgZX}X$Qhia^RZBdS99#2%c92K~%RjsVL$rEt zS$H>9B_0n>z(fetaX_e*#jg!@gk^$q%cGGRVTRzHZP@oelC19a=M;hc#N>nzXi2Lu zKwo*qvwHg)M#tpaVN=+26~y~FTTITtDbT=8*@+62o7&4tDQ!3ixa{)HwyiCwF;KF1 zU%e-YdmOxJaO=1JtbD4?j?oUzQd{vrJIx->Aw8ZFp8e43XQtGnYBO6V>I#~8I__%u z&eno<$GrWnf_0#p#2bOQs>Cy*#W@c(O59n3Rd*n7(N3cR;w8@6aVUFX^L~ybjl(JT z?_5i(fl?kEf-OuzcyVVC(LF&dqWlaLh&lW$`Z~40(t`y)NYynRwAIZS#4qa=x3Ez( zi`_<~CVhooSDm3{#nf7IwJ3A(Nhugm$m}6kok2Wu)aq=86#NPdGTDj+xuN%P5UvC9 zXIZ$lA1CT;;{a;7MUDx{(TDod!YtuKf*BdXClM-%Jb{D^IwAK7w{GCg?=sO=H7P2~EHllbEoG?@Lu3}$b$%Eg1( z5-&QuLowepNlpbv+q_6hbI@$;!Bf1PCA@^ExtoZrxEH<0-hPxLFl2iTq0K1~An|VD zCE}vIjHh-RhKjNvV+>C&OoyKL?vpX}0PB1c|3o^;(x2FPvQ^Use?9z1TT${r6%@q& zz@at;W6~m4aG?95Ny4*Gq7c6bL${cDswg!IbrXRt?pUhn8j=+n8a!qzYL-k;>au{) zvKiJv9qH5CcnLT)`pJvt}dwuM4nOkv{y|0q3N zZ?d)O;gp_F4|keI9v4qlq_gqnpZ5_C=mAAQeY^oH+o0=}5=gjxj|SG7dfh+BO&$LR zK;3jNGBvruGO@-ns=|)gsLqX0js;C9RH^ofy;8o<;YE?Le@SIRW)-;lMR+<;Zq+#) zIfMo3Q?@rxc}cStD#gQGYDAw(C9f?Y0+Nhh0k#haGU%7{4s zPN;s?Nu?23nCRy~+Y#V^VMXL1(zT;h4e*e}xao%MmLrK28G?kq19DD$`O0WkwQq|1E z1XyC$0VV`HJfPxycM-zftzVIc?x|3vYzGLW_yJD@y-LU4r1UDN_lW0eL^iADG8;3g z7{O=+9bX>$@J=$g!eNK6RNHi_7=~o^>Qo@;H5a?emm0d))81KeM0V(X`uZ8$A$Qar zw#eY#hBtZw4QQ977{F1?I(PLRpftULH8J|;)JE&7jP|pPVBa2K02+D(Y^KOUlrTNX zoQ;q&oRJ)o4cG~{l^X-rLmr{WzwwZcp+dO~?p`Tv% zS^}50>UG*@1-C;VB|q08B8rG3LiSQpQMxY|E5{io}Iov2gcL+w8iXdlW zbMTql9$5H+b)ejZE5~$_9wY@Xj@C4-MgoZZl0Blh-a?k$1j4+B=mmWPF}rQSX((#a zb0L0UPv%qfr=4!Lqd|Jx{?~QsFJw${$E~ylfL;V zd_BF^{VcMGPRzy)_XG_Q30-y~$LB>=XdL~bGLG0$Dg`WshSc%!(xCtw$vIE4!Oy_t z2XIF&Ar)$1Ceb5mZHp|)p3OpeL1iE5H#dw`ZIrCzZtp1;!@lbH(q&z-(njr^rijxf znazvVroPpdBO^M%m9^m~lJ7w-9`j_o+t~VRtcIS3RiFdAf1E4v7T`EA+DLT9fF9lX zfm>uy(3>}kgUO`bl{@TppM++ebsm#vjD{3```zUQd(n!Z{p_*=w+;y_IOc8J$SoR9 zXPO$Kq|yfTG@XCKv_j40*+V$UDU!P|K#klYnD~cw&)Y+l;t^9R^tz)VWby8 zHfYgd9sn+d>0i3NkI-Iu#4(nP6G%Qb?g3tYJ2ByK#oI$RR;3OnY8B)7#iu^VL$*B| zM^(cu)x5^j86;_T<~DMu)-1nEv_CG5(iUg}b}(1y32>TvHMOwK&5C#o6InNy$*dCQ z2lvjA8hIfoiPHDEiF2qoA?0BK1l=@QNuM??QnoCCwL<%q6B4NwAyO_!G-LU-kn;y{ zX4nE+H3HdeRTIq?8{o%FPPg{9)vuT@+X4MFd%LYqj5yAO(b_1o$dOd~&`7cx8(o%L z?@(HmT>3mIFC_s1wFY$)KA3!6-Vtu|bf6GpPa-*wK;$D+EbigD0dSm4CNvL7Lx9%L zBtc^IWtIvi%s9eOn*h*UBHMQ7m#9QT7p;(c2H!7(rfJtlYN>lHF-2tG6}3f+Q*AoC zt!|1qO+Ns%uE(&A4CONv9sNuxQsyBwn+M9%5b>5NEc+>3sxF#G-ec=vS}L1^t(vv- zx&p~$p=~w-|6}$*HUD;MAvia5QNtA-(%x)J56mjr`%}2lCa#2T&aForsU+~o=UpF~ z1g|q$)ELBQEG1ghtN8lx)^6pj!m&_a@3-Hd$As^H3WxLE9aX!Bc0wSum9iqqG*LBb z0;kqU_PQ$9+N&Qf8_x^m=^BOq6|zw7>bp_8C>3g^x(^TQ8x>KFv$O?YA+3u(?xzj= z=)5&3TR&*sBU`6|L(vdU;R@P-1+0mo-1b!TI}1-`u)73osZu~d6Gm1}_i#HdeJuwq zP^_k4tDbj~RrnC3NggWrhcUbUq)l{V9#RwF;lqKFlIj}u)WxFn*K2?In`3OY!J&B-!J zREa7f@(*+YTTMS$#Zoh0Ya`0FJxSI;mB(>4I<}QEfYLVGXqf!*XlS``?en1yA4vcu zPL;)Wg}Z@!nX;pAI#T#aFkT?4f*@o1$|mf0l3YM4l6dIq#PRM>RpJ*HLYJqN+R7;G z8IP+96~u?`N7Ue@$Ax)xETlEC#mdBg$0@aa05ojgG;Zop2C3sd>_4%U6Fp5XJvM12Y2b&4? ziX}4pL$d+894{q`T$~ObIVPt}zq2TlG}68qKKiyElX~KA6cI)kt9;bZLvS5XPM8KA0i*qU-27M%D^gRTfws9c2%v<29u%d%jCJSxk^0(K+)<@M+$!(H4;(L z9(OgiH#|e+@1Qrc*mX1{7Qtc=TNISMfyWTAlU@UOhLXGtih+?IK*k9 zdz`fCQK%9&kqp_HdE{d~O&*m(9+^_b5AFwCC-$Jqx#VC`WQ0o7UvXWp>JZL3v{Zl4rp5*28uUlb1AjabUj+4SP4>fqdE) zH2{< zGTS%bmH!3&N1kMtUI-nDhlK0S$M#Ssf?5Ep$o1TQL9??oLc-KnXnf*~os2AcSBnSv zw3~K%Y40b8EK+8jOISqu`0bO|-#}>RhbGuL7VFiGGaiGgiv~zSroVwxNaVA1c$#cm zD4R9VpR$zd3cLvxB&dyU=!ap9MT4X4W-Ca%XE0y+@(@_5-9PkWnTM zc2avGI`M|$$u1GS!OK-`0_AkfIEJ(Rc-`Mr#ja-6Nqu){a&<~j)8>XZALV>q3zaA@ zZILU=QLc654c{C}B;_0YmRf695#g8E?rH2W^cVn00`d)4ZN>({1Gk;?`Z1K=M&u71 zvw;$iOif?G^%elwmd~f^=S%<&X!pdhrhLqG{p8c^Nz1?i2$F=jKFUt&K~!1pkyuI) zBVF!C%~**QmRR^m!lqmkvrV!OToslfpjc(kB(l!xogMsk;KB@7#Z^ypSu>a=QDUoj z05C1-6ZIq$Z(U^ifjr`dJfohecT3GZZ= zEuD()dUvuqLHA*}HyxLOA_l6PsUm=FRCx#stCe@}MhK3hah9RKU{}3R{Vk2fGr8Y|H}XJa5Qxa&QdT#irtSJiM`?7PE0^)xvm2 zoX}yNMVz8Ow^d==&GB(tsJ`Yy?=kQV8%>vCyk^%<^oUa6JdI_wp=u*!X-KlPoG_mg zyzH~2Z<+fRPE>mi zPyIMTnhdi&{@m{ZrSzRp)IvSb7An(hs5hfJ>L@=a-)fx)C`%un4z9UPF^#TnASpnV z5zEN$s=-|f1MksD-C#Lh?Cf8Iw{@{GFa^lL-V_fCrw^{gLp0!NFBhnAT-~niB|LQC zR?~(a&h55P7mNnxvnO>;kqlKC9-J+R8CZ7=Z17hxS(sVus?qbXoh9qZ*=kx3vb(M7 ztG@0lISnO?;~qqok|+9F658_svcsMOPcXT~#;Gk>l{Og}2@}!-K?s&>cE%2_ZHeV+ zdd@f}vn+KdImM}mpl*#>z9{YvUm0ek>2B*NMaiTU+vBqIk3&v^c7Cv-x+a)z#au?p zvNVE8==Z}5IV^c?8)Oy_z+C!I3(%MvRw9eKC`a@-c3jG@0d7k3u=K9HQ?|For8|I z2S7RLOVjSO+Y5yD#GBsOg9q}!Iz^Zz^e0$)GpJf@x_v9it37eT2w(HcU_jIe0NN61 z(f6>xzoV&@fmNK?Pj!*FHf$N7Vx^{_sn4cch!OG+AzljTn*ny z@@Puib3_uR54~iEhSgQNC9U0CBr_bER(AIGedsO!JHW|52nujSgZ+x)kU@;pOiCLu*d~jC}el8$y ze9X<6?vsRJ=w@g)d~)Z7C^xd1U6HLAnb91zg@E~v>Y`3biR${1T%s(SAv+8Wew3q0lUDpAT4^@|cI(FWNk8I*KK%{gc}Xhl zOGfQagTY|OqV@?sOl=GKfUsex08C0Rh-wbVZCl8NoKJf}u`kRW(Id;zGHDCA__C}z zyy1_iuM0%g3uTT#L6~_>f0iFQP1fdTi3(O1Lif^xxt!noi{r`>J!WR#)sXQ}NI~|) z8|V_6@Iib^&>c(%FrYW!&JdQSM>Z$>VYZtO8X6hT$^1}`WLNhn51rp(jgRUHXzO@T zFe^I$sCKC@pnN#0O6br{TcYX4^ipeXRK=4efw7^YceFdQff}xpF#Ftbw4 z)E$X4;D&K-|NeWg-^Xz8`*wk;Da)ViCmJL$O({_(4>*(+Yp?KXd;cO_XWL}JQ!MtN z!;_Q>$*^EtHm5sxDtR5l6}S(bicgI1CxtAW{IbA_MN<1qse3QaS=?`6t-q@~i|38e zv1>!0eI`m*%iRV812;uyIvMcYG({`=ygdVr1JtRK&g=n8ZQNroQG%*Va{ufX92&TY z=tbLQuFpuxI$!znc8zF;swHc8F%Gh*HodBDqQ{w+_2BUelnYR3<|HgtG#f3 zI(I>BGD%yRZGl3z7DgH)h{g~VIjY?;kZ}(r>vUH%&MgTq$f4Pq@cEShAw+ z+?*sHLy|>eM{COsVt_a;0glf*nu>0zjsb)Vi!@5%CS;k2ms&^Zp2Mo;ds@sJU}Y-= zSXSa;wm4tI6HVMv71}}4$gXttcMiR!dT6wLT{&?ldXFJRkcm{C5o`5y8mQ0PPM5$# zef2V!#DSc~3;l!C>cN^evNx>O({9M>TY^y}E3A92M_8Bv4wF_2X~G(N z4y&|#z?6tg0s^~8_VA<{`6Df|-HC>I7D3R98XKg@iS3xCF4Q zU9jB)rSz0+rnb_+)uvFFj&Bk_Ti=P0Om&)x)LxmOz^3obtuTGv$wN9w!=rCehZ8V zHRg*ni)N&}Ln7j^n5JiK*)+lk^C+&=?3 z5Ma1#d|=S&spXO35^zlnWUZIQFz;#s$$+$;6-W8$a`CSNG47C6Hm!XkU5!d7S9m(G z$YXipv~KF6!!!!|_xZDLKv`kDfe()5WFKW$vs&Ckd%vseti1gqO%>5slKhb4r@*Y$ zYTdI8Qh59QrJaW+RKsDTGReqSJuj*SGz?+GsI`G^Kl#)wDJAVUVnv4-jac@`y#BWkA(3J zQ;bo{r*xT^CwX@oPGMFp(sfgX@|qdy<%f|FZHCRNo@eW#?hi8D zR9#H@Ei1fSUr;EMh)q6a-JKsWgVV`(a-J7unccMy=W&4IR_f*pJ3Q|SM@D&@o_tW!acY~gY$VT(* z3oX`}IvGlZvYGg?+ffQNfz1s-0nc1#_aXa@6dwvm2lPzx5IAXC((k~%c~aG%NZ5jR zz!oVW;p=*1rQO2P1_X@vzkukK$v?_+W#hTXxm2&e2Fc_p@k=MRUf}8_DWdVnFhQlE zfhLrR`Nh^|esWhJ+!iJb#=E3jBVu>SMv&6*xp`R#q}_InK;Wo0mydtwsA@#MS(sbKC1ixe2g(%*iMx& zOf-=HcmgpTQ%0Rp93RznaO<{aWqV$}xv!b-Cf{!vs!|LPY#P-SkK<~&#gK*1?%dF= zY%_vrS8e{pLAVn4+B8bcyOiBg!r>4h+=Bs^40NJmP&61YzG0=OMAv&;7wTSeTo85`Q&N6p?UtX(TmRx5{;}|$j5+4{!r;xIf)z#uz~*H zuLmTf&SSj&+@Y4_gB)f_RUs88M<%jBOE6U$?)zt*xbL4jf&ewF7#-P}V#)@)3b%T| z10iB|(ZoB3cJ|at)N2$OP-P>FcJF`u`tRZG7tpN&G^4nJTKfL`I6?ON37I6TPC^3X zs$6Vt?t+sCtZiR9u^nX2BU05Vn zI@h!`M8dP$0%esJpbgSdbl53=gxovDV8Q+wC_wbB#uN}#_1xrCDMy_=BS9%lD`#-% zF~;A;nof$`Tah1V)J)=7TeZlD!(-wmuV91w&|{jluKE>2HEzn(BPP{vztpVn?dSSn zif0Ei!ZdJpyL0(%bX5t)Xkoyft_5IsqDz7I+31mu_6sG`o6ySMDi0!oY9!1bO!|TK zR_o=HmVmEp*QH<&{ubuOSRHi-5;ca29dkOm9Z;sy&lcP5d^nK8ywJBsevN% zm9PF8%kks4q&c!$hGe@$@(j+kB;L;pNO|@#=RG6->zJhOA>FeXc6Zcn2g!5=G#Xs7 zaDT~h8Rmafy-?|?CRVQTz?N+VcOUP&0Sf7+ZfF3QmM0!97$bpGV{D?RqIDa)TR;$6 z*`GMja!wU0I01(b)#ZU>C8Vm}CL|MiQI{;=(bq>-p$wB}bI>yMyA zb1XP#%{Z`gUep~uLhYzQ@s}8R?(1qQA)i}A>kGZJ4h;IiSKoX4!TyGZ&x33NIgwDf8k{A72#o5ErXafr3OVOYE6;ZJ$BR$u{*T~oZ=r| ze+|zxw8>Dz{hFXCaD?IoU~URanPOHTd|}Q$vTD{>v=Lv`xe$7BHoFR3(7^IoRZK<^ z;f2I>3kXj-ze^if;!nD}SBbaOH1+DSA?M>59@$kv33)EC_ai7En^?Ab;aX;mCs$CY z82LD{Jdk7Z{=*}@GsxMjt$s9WAq!%QE-b~1oh-dJ=m|pf4p;zWyO<9n(2C% zj}y5%suoyAO@;NKtF+Vxu%Y)LE63QF;8=C9OzZL}`I;qOQ%$Ig(NLTjqJr^4Q}j$< zcG8OGd&t70PJvMyP1mp;(Y?xr|LYIKTN)wAK?d@y9C;%I4zftc;HkPYtnbWzaDtYIw;t6)Qt^qJvf9Jb#y6yQGPckO zkOCB8pY-z63_u6iH~~w799tfYSM(W7#^O)>le!j3bmWGy`PxgmKIV=I7E251p#Kh7 zwrc&QGJit!dkIF!^&r$($!)2M1)OCS4x;Y!mNcL(I-XhCXV#dkqkyKu&US?ZRE?51IWclnXt0fH$Ml_TB3b?9%`1zcs3nUAjDE&3 zR1iIALU(pB0OXjwE;6JT^El`ynv*-MdQS<*uW?F4Kj1TizvKbbOr^@J9HQ)}!-?>r zHe1_HpkRa9%}x(2^bw%A4Mmy4?xm!4)tMEwI(BPO{O-WM184wAK(@c6#RgB$s6o`W zM&c^~;Zuyo3%A`<#IV}It~i$H?gp~~-q^SWV9=+@XSnk^rGbq-hHrm6eEZwVVyjOY zSI|y|!h~-W5O*ctSP6PaVT<^b9u0LKxstrwtqHk{>V^2m(^MPeD>0aAPr(fXba4&j zCS34ll{yQNP=Yigc>VXlFZ4c6Bz*3gA=vpk5`rl^V+TSHO&ULc8;sI|Lq?QbCo!9x z9uSldrQeYN8sRV4%}}kmONjaN&fw>5(+o9+2TAn#A~vg zGE*QaT}+4UDa0{NTdgtEwoYEDDAQhy>R*J#;5^8($o&NLM|_a*KhU#oK}Pk)9!EY3 zu1tVon0%pVBTF$k3$faiSa&HIi@@Bsy^SKma2wi>m@fAYSdC4+H?GPp0#-a@d*m6z zDq8z%ceWW4r1$L?7A%8WTV`tzMQOBpSh8$njA;~ zCkfe2$rYAsCtcSs&;-QewV6=&fc6*d9V<(?F}JYAfw*cklaXIZ;yj1!&No$=Ji9_2 z!421e8T~81v4qO=VW8qes_mfq(&ee-(p4r`EV07wTsgayrbO9y;K+TB!J`B;wKvX} zF{v1>qNM04+AjTZ8qEIKR^oOvjfASJGFj)m6J?zA8jv5q6G?$0xbhpi%Q++Df&-pNJy$=*g#^8Pe1v&fTnh1JLn z+v79n{!y1F5F?7aWBU`u9+e2zp4jybF7O@#M>f;^k=5mE zwFR-|qaq*l5r%$(b=)AO9WOVe9+qXytC}j31h4?8njUq{K?Ja75BAaTCX#DgJz&J+ z4Q=GfMV@YDp_ z*4=?o7rAu=jv(ekK9K^~lJ8h=s|gmE%d9D3I{VRja1{@-9Z+X~i?odfmEG591Wgl@`HB!fLoJH_l!)6q=|+09y9 zdTh6vmgZ8id>r0BJ+&9F=wD0P=EE8TCL*>FD?s1mdGqF-97H>P=6#T$2p064*Q3u| zy5f*!*juX#6n{SmR?wm*1$9uEag9*uP?E`icwJSe6`Rw=^Q?mP(3PUWF2JT za@AWQ;plC1$Qm0S>^f1_)h)$BY?7hTZWx(}G1huQoBS@k{y-^cvTal(?*cmPVwPQq z`5lzIb>x8K$`%tQdc8XYsGSzQXCL_5bMGtj`~WQG-NK@lvez`e)a zIr9(-=2?EY(=xpAZhrxZst4&_cWtvU;RJl*TNKMm)}5;NcZDm8EnZoA;AN2&tl&}! zv+@k3U~wZx@S?q(J7{uMt8t%Ys?ek$4zyv9Wm{yu-eDb(zJGyC2+;vrtlK00diZ+k zvGedipwchD5&pwBEezHt6IsaF$pf(?vVh_&JXtp>hO9_*{f}^q|^zA{V^=h6Sg(Rs-olN^0ur=kVOo}|o&OW`s00ubX8f9$h%BiQh zV+EEi9()KT%S$kgyeL_TpF`>N)xV?y>ZizZZSz!g)c_g_mZoYrn6(^L>$U*NEIo=V zNq?{nQ!1G|g`suM?r&c|2G{b5aOK7P14+4$C?knKJI)s=UyIs1trW+&_ruAbT`Mo5 z8XL9K;Fu_x=s_?K6HN!L*n&IRW8(yb5`wj!b3du6W)J!K>nFBeR0u8Iq1|2& zP;8rGG_v4f!`Mk}lkAG^dT=-Gwx|dMyhD+ zrD}O@I&*>YgrduPV@gQmx;lXA{ijkNy#DZVy`wQ`Axg>u;2&;Yq4nktr6*OD?I;{| zI`-bHlcctm;6Ulr+V$m!x!K*9O76L7cB@F#)GVI*YTljo zAgLp~Ax2BSW*h>t!O?0CUbVl>W2y5=>?R8WIO0}J$~NlDDGhbMJY+*)SnjjIzDlmN7rBR48{dEm zQGy;FOHTG%904IC@ME(jp{^?F=M$~O0J-Q??bqr_-8wC%E8OR=Ml)nU-K3J#QWm>6 z#Jpw_ELcuM&aZD0e5}@lpR5A5FtncUa-PAq$)+{i2fC&5h(>wl{UFif8h0b8Em4vK zIa5sY9f~1xsT&F6P~nLN-BH@9#I7w{Re{*W#peCvBnGTb@=)Hv-ePLntLi<>WB_>W zhkKD_Lkz``TE^*b&{nY=<_JZxK)2koR@}0xKlyK<^GKY}v0TbS4dn4)-J55FbvVqT zGl>oIt;EfB;b7d1Y#*#+pB$)8Ymn4VIAk8vC97B0$@RwNO;U~d`g^Z2Q{x=7ZhA-9 zxig^)v|T|kQZNj`i?foG9e1H#OeWOgtoRm#s>yz1zlRJal#qyiNES&#$Wv{_la`wz zHz@a|k?G3X1wb}BOsHmF+PY16P8FCej7ecL_5{FKbk>Q=vio;MlfcRiQA~=&VTd)c z92abzp?rv0tB2Xvqv=L;rD*zQ1?MB_f*cxd8UJKwn!;j?dXW{CpO4bEp7}JnC+Y7}_)s;Yo z9h8xu0RU~zeu3EEJK1A)#NwRq5ushK{YV0~{^M-(ZIW5soj@{@v?yu3LTVkLSK3_4 zPG$1AwdX{65Y!p=5aHhj=P2V@vNYFe!%lLIi;$^yrfsJnduPBE9wUk^<2_^GPe#?Q zod)RBAtTL80y2n!bSIS@$FBfLdrSb6`HV>a4o)XteiJaxz_ zFuvfyVRe*q}o4zdnsH^HS2wl>8@whaNqAhZ+%sS9|p;IOW-r|*gc-)o27I-gwe zV$)Q}<}Zu@*BIIKrIhleLsORG7OIM8$Jn?8S!#zpIFvV!e5Jjnv>m0or%X# z9PFXhcT1SpsICivYjo0LL;hPE0}v<8qX`Hng%cR{@t2({{}xQ2O* z7lZdFSJQq{dpok-)u4>^{{~8P`sy82V{DGI=5hfQiR7?PYED*RQVoE9MI9q9CQQxC zi^6QdxXzbpk4P@S;vE8`)CI<{!+v4Lr<|PuBVVRAYUCi#`Z2)+k`(zDI72fO)~bI7 z%tiXEw_j#JyX=q?oLCLUa;B`V*XtuRj7;eKEN;ReHFTS!H%fb}+WJ+Fn%U$SF`4`7 zd+ER7{EghgC))y;cpiEZDu+>iyH(FvKl0^)(?W>xd7f~DRg7h&&iY#wPlK~7GbVVFCCH&~4@Ex9S`1{`VFA{q_s;#Y0VgQ>s*$9+u z=Ev;m47>sn^~*5Bvrxs=I!wJsACYy7W&0e1|GM~fn9i{Z7 zn=KOX^|kQbuYH#>I6oHPeX{4>j+tiIY}&i+#3QK;r^2%kp`f`W1+b|TNd4{;K;TWU zDh}nAEC2?e#Pq?il8EVbC`WoOA>ed1$w4A#`34Oh1DS%H6z3+a1lNw0-YY1O!E%Ac z+d{um*oU3zm*Mr(q$SOfSrG$;1*DqNgX8_LaH#zbVoFhOZ2E9RQ~@(@CM_;AmPe#2 zEia2m_A+j|PMH+n1H0AJi4MR>X;t}bzuTySlggALkl%Xh#7YSfj>Aaj9qor`BpR}`mOW&8}TuT61sgtCpS1lvP?7O<)%vK}T zn;eJgUcrCgl7h&C!IC`>bS3Lqj(ShIP`ph5{YO^73y)LX9#FV=?y$CHBU z5*VV6*kr>xokxcRszXd{w2ImLy;~q?nb;K|PQW`B?kf@6J%+HxOXPd{>(?*CsloKa zQ-euSDp6rkQvnm;yd|CftD}*l#0avMc)2PAXirH zrV=L-RS^!&H$4+7&T|t1^?wrfCQG(t*LC3he#HS~RTM}eHAhh(Kcq}BZ_ai1xZb5V z_lCznCK9u%8fZwFCQ`OYW}-(DAdm#i;ywIV?X}Nd`*@^86RU*xGV{HN8}8@qb~Tzl z)LJU@K9;@gQ}c(-+6Mi#cUZOhZ&>~ho?zpw?fPDa#Mdi>vEQ9_U%}5 zZZSLuYD~`bxc=Wf0w; z7yO*uIzZL9&!b6mz+RARGk<=hd&2F2qT>^~QOj^T06Reh7RSV?Y-Pnp$Dom!lDlR# z=w#%2chXp&{81HQC5`>mjHn%*SfQv8`aNO(1mRFXx$Ks7Nogb5Qgs`xt0W{IZ17P= z`y_1iaeJd|I5%u7ds|Lbs@uZG;``qZKm1|(ntSLsDRCfGO@`+VRNkQ`CN`x4xm}}9 zdehrIl#8@(sCf>KwL#_9vGI((gaM5j=ky)&U(trGE9`5qU%RTPDm7$lqAaw{NxcDw zMkiG(2%g6dx2@};wj28l7ARcHH7Zr_f_`AqSo4WR4+i-op{8jj?6!5-y{aN}I!VL07(ieYluWY1D zJa))KnW2nXE?Y_o1*gX*7wlRMs)#k&@?o5O`dtGzoqlfVW?p|1K&Cb>bxZ$igo$(j z=CE&@R%x`{t&K#n z^hF^GRFqVQwVq!wwv$=;qI8tch()4;5pW$#^s8CzC?lI+SKjf?DoxI>B)#NSPA1YR6$W{&j;kUmyr%C|B*rn~c?6z}4)8tN%V z!L&5o`fx{SS}=@Hm#gFHqIP|(niV{q75Iv)M4DaMD3<$43!1^7cIsO2fDB#$mR2hX zGjL|%(WtYvt7*jHba#ZqCz-bC(NmU|k~3OKgWX&gJpCAuX9kY#M^aQk2+Yk@OBl=sbxhHi;?xj$T zeFeuLsYV}w#jns-ac_-|u2I{bu-UX(NO)07n4?KKp;W#XzVjW+z!_XExx}V;GyDN< zWTX=#l_09!+e1pawYnVSTe?VRl+Vx}Mx9*?^}o@9KM*E9tpZSs5qKt$2>NUzw=@}C zIb!ZZ#C0!!aox1*=eXvX@nky;ez^Ian0r@GRw|4YI-oxk-fe~(lrpD5$~y%IadrjZ zziA2C=&{doEE4Ea3`#)$i$fbG?}V!5Fn?;rq+4B!-B2xOv!7I|#VqADpb%68%9xc9WLFK6r!vAGC;M)1$DMM4)3!D24-$P$c92YDu!S0yb zNeRu5qO`Lqhs;DvHnh-$rp=w&xM$6aMO~w7UymvX_aA}K_TIAE6Q7HtiFf}cNadv{ zP{lzMo!IQNgxGOLy+9t_rzR zBxbL;WuEn>+&~ulhu6>F{nzmRbE$r4o1KXHc8x=C5@rC_$@yDywx-==grXK3*sGd8 z4Jxe%u~-lb9I}c{zOG$oZ`tZN!4>c_txDdaGQ|srcILLdwVc3UIlow}A~kA;bH$X! zOE3s^M2oEwRQji6h8WJFv0t1fX}-< zhqmD+rXQ|qvSs*Sqvbtml`MpPNsyOh|=jXVkT!ClU){Z}b=zO<7bh|5=)l4LzpFb7KD&~(7% zRCX*UId>7FIn#at?Bq~z{@g4vFQxe%_ko26ESq&gu`HEVvLX;?pJ)YFp(xo0SGu7cT4H&YF%Na zSH>~{+-!kAJ_C(t)zA?}7D6axsEwTE1VNFp=a`vY)@tl(0op1*R?=uQDVy?CkGRKE z(UB3s`FJg4$6&U70yzE(oNr}5^nl?z7H%`+WV&E?No~qTuANRqmZJ7~FZ3PDXdE?= zd-2$$l*5pb!N&14Aj_bDSAK+Ts^ma3CCDq4jDztgs6>qvah7&=PfZo@JD22F3a*X4 zDk)r0LfYHdrl6~q9E|VXq}{R$Phq*&6<(!j+w8TAiY91&gri&jQFo29wrVb2bP5~I zUa0`Z$@l>Hse|J7o$3VwCEN}0rbbWZbmg{NFu9`R-d4Dfqhi+8n*q;I0_q^ytmD*F zYWdzp(^O*?AIk2%7C4^;a|pe|!~we#AgpYb^O#;>v9V5hjsThFxGb=(Up5eS>Ml?T zyD9o&D|Gm0<0YwPURX8bAbZ(tO1bYpwP%ktj}EO@z*8hQQGYzFd6gtR6|^W|6x2EG zoXd09B1f3`Re1fAy>*VQq_vtQ7N=j(kk6X>F9y#arK!^u7kl8|zF=as|}Ku=$Q z7&WKh#RZ6`;@L$}B$i`hanfj$6jtfr4UiuQfriOGJ%IJdm6%v<(l9DzMd3Wh<+fA) z=c&k)8fj+w?CVudz??R`z0`_GwxZSiGG~4M*61jKf z@6vs=yJq1gH512@-yv91&g{e_t6@adxh3PddQbM*KJtHgv6uettM_s({-j%KJ5Ks3 zED()5;!a;=JO}tCw*Ew;-knCTzb0Q6Vg~Pj8{Yl&{cqp@URx|QnI+CoO(=q<1=kXl zOILntdPw&?U71T0&3k}0Clx$ES=;U-3IYCL7)ib zVnHzjDd{$8=&#t`Hv{pXMF2k6P@Kt9r3CqOpA9=nYwc8h_a(q>t z=&GW$BZq!b5(l5)JKLboQU`DOnEf8jvi*kHZcp3$m42d7E+o4E6TeZc96)owu+X#X_^!^M_>$KG=yi%ZEAHdogGp~ zNoVybqAg^~tv+UujQ3Q`ydDw}k}*Y!iVIuj=xbvXvkWlM1#deA5lgv1V>#twjFbU_ z7Mr}yyqS=A)m^zb+q@sC70f9&&-PmzaIjfeT5g;Yy&zl?TpCWjl-$PwfY0*a87MG> z?Jr}83A=Tp5SFB$ul&~+%mh|D!Bg+>tlFn{_YUSDYuwuXG7jNkx-Kh67dIVA7|IuN z$&h+AF8!(!faH=*uDNqV4u!4P&WU83i~-BA8d&wiko_Nw*D7J_Mnt$ee5W6JvBtEx zgDCDG(IV4Rclf%d&t0nJ56=dcsP6V}FlwxhFJZ^ikenMT%B)0 zSfa=#4ye(u7GE4C7zbSyDTy1yJ-u zvaKgRxD?&LMPF6NyU#oT=5h>^`ULBIYT===)S()YM3UhJ>~d>Ch@BYLRY#voe3cQ& zr|lQ-P0;BV#uBMJ1)oJVC>iAKx=@+Cdw%doK>{dtvQi{uw^3lM#<4&GbGKjZ=0{xI zY-?bS zj`QLkUZpAG37ePo21t`1r43gt0o*Uw9k%n#nKgKX7YwiLs2j6{Kf;M4FZCli9WTLm zR`RxX>c}1GXe2kRnzBMok?g;j8EaJqf+r}J@=M-3YbLWSwYkPgtt!H>50JC^6e$ce zEw`96gi%x7{ck{NcxV7X1D_dja&;d(#}L|;$W-u-hv_11ryDLwp5olxrUIq~#*uDO zhVr;18?>6lMR=;(Tf+oQj+~-C z_LW#vbvw67-Z4vZC+8wUV4g2)2GEW<^r6IYP*X1hRGeJWaz~H2C0u18AM%PBh<%Xh z*##d92(5-;S8YN{6NYn{=2hCj;T0sXVQf5R>(pX_ce$1S9oQ&xaV-6HCsG4Q^!@3s zj~%xjeRq2!wS?IZxXV%*s{YIp6<@m`NP!E4+J|f9gK{4$mf=mdO%L7W35|ujssqVy zns)H*mas*^FHQG<{?B&V?T;tDIRZ{$-D9A#CrA#-&tk;{HXU7)1xu$Y*4dY5=uAST zRlLnboiZACbm34jv+*#69JqB>8ZxT-7-3X#yzw4+n?iz*HhcdO~ z1aRW0u8$8ckTFQ5e&*I~;M+2L0ibHWaf|(cK-|_@ztXh@NfiLLpF|Pt3Zr?uwa%JS z^Fk@HMqrg~429vDFC*RP(-uvWI)M}VRAbzRE5TViLJ9Kr7dbEIjqV*gtZm1Qg9NtkE4Kq^r%l5 zIdn{m%GLr(x}6qtg&L|YD-i~>asqQ{#L@h_|B52yA6|c{KsH}4sh0U0YAAjV&uN*)(~n^Vn$bsQBA#@&xi5-x|giWBe(dI@cOd^K`AH?wLUse zzIT;lclIIV0x>MHTJ<>$lU<}2-{z|Xn?1LBuTImZK9Bw(nwgp2#D}w3{W84%3do|z z!#u-^6>7(>jnYA+6hPAhR(Pz5fqdjt6S50a)?}dr+q`fi4l%|O|3<^{f? z1xgkD>FdYgZ_|k$`Nc3VrBuZA8nc46lvq}x-4*uf^Am+k;vQ|CQKnk+0&T)LRqR9| zTG}*LHeozVEw9aYsSL}r; z1)+0z8-mCI=K!i;$y6WIRor605+%jJ zyQs>F@wE>_U$US22CX3S{}UW`*`$Vg|0ft`p!KhDNmeLbjt&8=r5d%JKvAD+JQZ{G zqE{aDQ@q@crIB2(!u((AzDTtabEJ}D{{hmJVmw01plKucc-=L=KSQi428)^@SfdTi3@ zmU5f%Br_oS$3=rGBI*jIfJMhEnEg^(jPc=nG(&E$kmRyik6YvbwU+FO zGATABbrCy)5IVu)J%Tz1B?Kqa5+gsT(|lcQAR~IxnCw3*R2MRF4nUaTpEotkm-uT_ z4;|Y!B+HE)#j;5KS!*W^bF*2HfvWLbEVn-B{M#MgdNoTI)s=_=ZaSl&zm_xk1AAhN zZ-Fr{@2HLODSr*${ugh=*BbXUGIv)(?-bc1Jy6dMI5>QHi5f(FW_%E$v=p8eP3lbz ziT-3LSmV`Lv%}bLc z_R@!+kglYDT`OA;BpOl3k|4uQoGnlX08n(W<;xouT{zZWy+PrS$kliY06_v3THK4S z_lP`47)XOFe^KUyrYD=+=>6@yOE=k>a72uCi<1hMgai`MCm*AxK7>2?MMH%C{y&B9 zrcbqOsn*h_Mw=EX7tzAGwq0}?9v!W9W3u2q%ujdOXX=ojIU7|W0qowLa27qZC>dd{ zoUK+MM)RdAddYeKgmE8jVWEoh&*yjR1Q37dNI4&`Dmy;ImAZr~1q=l+a z`4kQc#d-v3_Ji|iw*a@$y(EqDO!{X;qi1xx^7 zR!{)z1UH^;&3Gkxgd`yu%eTE8Xvk3&n5R*>>?R3D-UIE_$l1c3q(E)C^CyNz+KoL@ zr)c28Ol;ILz3U~W4%+Wa+#q;^T0?quMxgc_9zW~-^q&t@uD`py`v(|`tt$Uw<-V@! zT1&O36O>(;fhMYl8T;ecloI@P*6Dd6X>9~_iSs8#iI=+J zHHuT$*ZP^?4nR=mQIu+SFC9g!reif=_(h@Tf&=^odD7kq7Ol4U(4u|6aikwfLax)n|w?&~F`rz_J>!*JrejgY||a~TnzXUO&Yw8J>@K#h^fP%(lR zkQe8u;7%!}wSxjAx$eL*+jg{=foHioG(J*y08zEH&oev-H;ggieCJS>kS6GSUcYWC z_GMpI{)|+$L3KsA(89dIia=;=`}K)L0`hrwH5OV+*FccP#+IaB-1x^p;l)bs4JHB_ zstITFa9h;*wA4Gv>$mX2#i-rJKavY9`>I$QhxCIg2wFCs2@G1!%mnD$RqaIuVqprs zV^4GdFK?l>l?Nrolblx<1}%t5UehjT%#@U#r>@kNn;SVr^~pB|s*H+{Dp7ynJvcT1 z*~Mjx##P+LG>+!)BH8ZKA(bkNHSt_3L}Oo8TC${A zQ^_GcpnTvjz0)~@?}#bDIE2Qm2|5)PWfaZ!I+glSH_f8XI8MdH{cx1myC2KDMe?gD zzkm_7eclhTT_r&^o~S0Zl&+k#D2v){5%4gqxSCM0UXz@A30PNsQD{`T#AjbjVbupU zqdc@yXY4oe>|gse{O|riLS6%#f6Fm%S3urXU7v{Gt<)2CkRo!-yVWNx)->#7kl~Q= z$=jau>xco2tHVek@MKC%a$qMsPy3R`^hVk9=Mq-ZpHt{H5t=jVT50%Za2Vy&x5>8Q zN|4|O-%ZV=fmVKuks=2CgTtsQc&z0$Qs+K(un0Br#Wd1y-Z3~A=-#||2CM^=mf`YTvr1N2uWEa0~ zO_v2np;%o{rpDU|Lg2V!0ic+D|&bZKf~zgoF4j+r8*q)npXCGdjrrPD;oDlLzW z>!d^pRM;Nyb7HW?2mgPVn8nV9eA}fY=qVJW1VzF#VI2(j8jJ2d-&k^dWTxQV0su6$ zPAzyiK3%ec%6nKCmYBJ^G}Id);EagwPGJ)`8}9q7WH@=RUG195hTQn-ErerHr2R-T z{V?ak{0jXJ{q3{-%Ou%+VtJylg^N)7jzGt{clp-Mu8*W}Q?>6f zR%8hVDcAV0vxLRxMY_CERZEQA1VCH1G@(C&p267+rJ|C<^XTSI@dcO~)a%^kB}bY!HgrjxhQ@n&^ILN~!nWxZ~ zxhB;Dn@YI`Y=h9c`XyL(c#E)6?2!L`Bc9R?Gx+$@z}g%Oq!Nrppc4km-$@-lOwS#= ziAPu4KSGvwgWD*J&Z&iVxSjEnA;RENqjJvSgV%91c=Of!YLNs473OIPgzeOWum+(U zxUS0mH8GKd2w#e;JyJ}ME4AaY725^9Yup@p34xGVWu@5QzzANxH1OWx0oa!P6!IAZ zjV`4iFw{BNr@ZpRY2^tr*MzS=!@FV4EwMUsbzWLxBuo%apFC9^28-Uy%=SLzuYtee z1-Eq+%FC7Uk+e~#yfpWSqZ6EVl4bfnN?1OTaUtwEGT-5ZkAReeG*^Q^*$SdGIr z&C<;c%S6T1$~A0yY@L?Wy6$utE0t-Qzg(kKGisj9CV^`l9^QVZRZS(3s9sWlHNg%U zBOGZ+-+lc4)9~m240eWz2Nn23C7Xev;J&=eMuAt=9jjXXjgE>eu1w(xsIM{E>8_lc zo@V#RhmPKJdr935S_qtr6C-WM-i~i^E{wL|vsb!tA++SX-Uzl71 zTUoYLhECQ4HP@6XB(wS;Qsv{*t>Orekyo#Z-U zdc)wH^-&VxQN;yKfwctg8rA{ZetKt+hJZBu>gRq_4}tMAKFO=jpcq&7;O+^)xWe_C zF>6{UsEy9k3_9>i*EztK`u|CR=Nl+-&r4Lhp6-mr{yMz>oq33d_uojyM7RL|E^}}8 z5+En`b$ICmrh6_Q3*84BHinx!&RWk6#o5XMI$bE~5CC0WR0MYq{!&GndIv<`%8Ak` zW6X+&*gQ)3*Z&RYe}LaP-1+1QCl!bEak<)9!4J<6H`Usq{x_ zYc{RPTkMBRvSCM++IIl*RYk;5?wrQ`@4tC(ED!n3WrjrGsOqfJwM*)?Tm#ryS=1^< zH$u%fF7VK?C18{~5g;~KIFf$@(cgz6XrNMMWwZM`)=QCX+*A(Gy%egwM2&gRBZS`T z^qOcd)RV5b^l)q)T6x3VV-^|uy<#K%paEB|m@UbKr$Pq;k`VZGuTbCSd=w_(aq-9t z>oCrg04y+0D3p4gRCjLFU%%k5B-5lBM558itEhmE9!Cxh1Ludt-ee7EiLPkHR(`B$ zRt-b8v5J1?9flN(1F@8@eB|(V+Ly0tNXFqA_*BMnfoi2>@(W7qkp#ryj95j79t}kJ zgm6Bm(TByM3Y?)Lp*ww&9U-(4}q~>7Q)7Tev86jj?fr-W|8{Al_;l zrjYn&b$PJOr&@Bs*oY`Llu2@UgN@#iWy+{#15!Ch7k)o2%O?4Q+#`MMA zv869wFx}y@4E~{RtD!MKE^4d$Fn;b1Y@F%BqRn3P;zkZl3%PHqB==G?W_OZOm6i-( zEe)MDa*r0%EsxKtg||hrC*gq&k;FwIPTZ;un<~HK@PlY*e-od$oo?YK{qDs0a8voU zzVUIN!r0jtl&ShBZxlq)qL8|glN2d!=@UO6cmFXb4W%t9f>cKMK3Q&2!-^@0vn9`A0WX-uGNmY3a__GxciFky6JRHkan zq<}6>kxEw9Qyv5*e!LkLVLGQ)q!t!>Iw~~ebE!ZOPrp@&7RGe1%g$2nttGE+gn3e2 za=5A0iR;wHWAAR0#t8l$U8u45XbO9OI2bP~x6FN2EuY(x#BP@brkB>aYM)$`EjW8a z^f0j9?S{>)3lIsbYF}ImR*J(UVkqg(f zP&xprh$_i@y{z6|mhcp*fu}rKVJ%#M%xRli{xN2c&q;bP%t_Mq6Fk9~ZQ6 zfYBB`v@KF4=F>(wJx*m!)W2WYhXnD1Ds^aPJ1XrSK^dmjBZB8kFxbRAfUs#YeH|a$ z`p9&^xV*8C16#!BrPnJ_%E0|g0>K1mM4jUKrZw^n1lPd;XWrEsv7NF&1IAd%!VY>A z#gH{1)}(vM%Q+{rT?qM%z_R$tP93!!kforFISUDNXedr2QBUq;Poku`I&tbUqXfKM z2|KO|%8x7eaf?_%w+>7SEw54180Z1VBG{C&pKxVaCZgcBL@?a|c=iXTSJ)>%i8r^! zMiO8Eyvd1eOENh~jBa3j+?WKIn3)6F7hufOgX}86NEYT0Oa$;8%^i+@9r80DN~Ex- z-6~zd?03R&=e_8#G|yatN6U5@smGmm0bZNgt4e{PA_dfhj;H2zR1XyJ!q*+)aBy@O zgrz9O#NRbqif7@a^$q_HN)0dmS*#8Kzv8yj1m;>SScv!p;0l3FCxlu}{2La>zu`NU zn+pOZcec`#E=Ptd&kEJUoQsCUV0}E2G|Ds6ot!eCt|jHo7+DnrpQ}B~t0K*mO1+S2 zkdO%NVRfUj&j7C3q^co{D*W}{X#Z4~U}S#6)FmXZY?hG)#1%c4tKJiYCmn+MI6S89 zgiWNwnspO3qNb-kg=orbCvZ(UI+&&y6^zL3OiI9)@7Vu^@0iM>lK2XL6bLT3Of{{e z?h&$(D;Di827;z{3vlF1Z}6-4-!MJ`1Zc&}5FD_dg*}@C9xS7#t+;e2gewlHDa5Bd+g@4+i)c7d*ki~aAldhz^KFGGRtck> z)A;T66F_Ir;3~CdMm*5vUV}MR=c{^M^G^E)JvjIoEk>Vw6y^;JS^o$yV$FkYNQ#Bc zc8c&)-cXryqZRWN5PUWolFRrkhWUgn=a}WhMP=!&YwwmUgAc-A{Ds}62_n=!DbQW6 zJxTRUYgCnMd$GW@kx?Z-BUP4^o=|slHUoXZ$j}PVe)X*t2h6|b3$~yx$1bj>N@Xpc z{PyMS83J6Qg2cMX>$2<+41;Y@4f2+ZB4iyBD3!FK`0bO@q+>r_6Hw9j>PJvZs@SDcWQhmt%*dHJu8ve`=>-oy zuTflt5@aZDj_>7WF`d_-SM@Z2H=4!0m6|&`^p-vXero!B$d7=9PuhWuh1=F7i5UI;UB1PK zyD_Rh+zWWlT7fiZJ+d4ma2bzBU>D&CxDIhOQ zY11`pOZ_N(hjAU11VIv#ua=4UCaW!nF$bQs!H>d-bhrV2vs(%6sd$t(1bhQOGan4K*t{%J#^HS`fM)dOq+(*8|2sZA76^`rE@d{KxDd# zJM}OX2M@+yxt)k-NJ%K#VX=$kz}z4K;Xc8m6qBJ51>(ps=1M9Es3hI0s`J2~?eKjH zz!Ur|B`{V}H`m&bTD&d+nw%6BzHs{7Z6S+9U3FLeGSS-*%q~2D=JJS10^;nVtqa7} zc@veIL3Qiuk~vpm>))&j+?NGMq8l#v;aDS% z!!m1c&b4Zrtt*}QT>O9qkmQx1*#)J0j^Tk_Y#=v{S4swZFv2?Mbrt*xoINA zVxa&zhBM>E9L(gatiU~`*M`2D;$+xrP4XlU6~95;-h7=URa9BLQN<#vz~mOYfkntntSiY}KZ1rV?*j7u+oHW@+$1&HF#{&ZTZ}1vAG7=>Uvyh411k)ub15 zbFjs4QppS1dFgNz&n8`C?tg(gOHbn#VV*9%W;3m5@-Fg|fh0qn*(}UVBx~rWrAxq; z_g|{`cc;{y)9`sS8Zf>EgB(YWEw|@+1%wM2!J(B3#DNV+hotH~c`O zYIr>+!fKaE64o}&EIVKf%o^h<3>H@)%#+TL*>%`w%bf5bJFuV&ks5V?`Yam|6cig* zuBDtSV!TlbTN1rT0svH4cUngN@os^{K$VSaF$$2Q4R#H?k*#Q*(g@UyL-^qhW1TP9 zAScuVbReAe0Ue0QOgZnPdQUPN%FW#&+)gZ&DhZPzWzhXft)HFAK2EfV@%~vJzX1!L zaOo{ltB83Pxmsd-`QYv5^{Ly#laxwEZ>?1PhO@(cZIfJ1D4uxtfVFfTVrYoTX*f6q zFV;#$c(^!RnzYE_OU2}^bp99vgYYTKX&E-tP+k4>Drcoe0onA%K8VY#}5yeeq2 z!yFziVFb4!2+3P@(5K^VQHMyT=Nsx~AT#RdV zG*E!Zcngi%M2QF zKc?t|MEp!9pR!|X5f84)fAIbA@6(g{@2|gm{W{1m|7;+fm`F@eD5N>|gfnT~s0y1{ ztQ_28v?y$z7w<*2JG88IXOWr$tq&y2qErfT`T@(}-Ui7)2mmgrhwfC2xxE`M5>k zY?;HL?2B4m&)6S8S%n90sWL$?zT{KSWczU@Qe9`YityB;&r+$J@Ly!sD!N8+ix(2LqZrAN_mt=o+5e2W8b2Rd6WLY?5&MP&@X z|2pN0uXVLiy(wyc9&8)6tL0*rKve-C;&{snh>qfRrNwlihooRuTz$l7XuMC}!t(Sd zcR_kMC#8!JotV4>5?;L7EpNp|BJe`tD5dLJh|5eHc=L2#6sa8)H>Ysp!)oDDw~9ly z;c4rib!)o?wYqdKi&H|M3O8^}@a6PJX~IA z5Sni3byhoFRXh;AxPAFiL3zg0ERi%gvNONR04QnMe={6-}j66pYaNRt_9A(dX%!GHO!XNr;1s3%OCo`NykLR zC!NER_4c&wd$dX0*a)J_bXDElY~l!sgbnqJn@xvUDIb6BZlr@%x8$!xUAm2C&P%dt zPv39^>U4C;);lEr;o68K;WXQde(=RxI$Wu9`1AuNAG-9YGpkEtijwGgn?aMG1#{UFb#j9a#fD^e-#o8#rzM!(eiXo~PEF=)IGCKwI#u!2u$4baTwUdLhsB60 z8LK8bi(H9j8Qj}6p=nfTO3J1KPQD8W#Q;*aJ9HXG!pFA)xu-vTKP#2^x9qI{WSI%B zeoy;Kw`~Z>k7IWVXKOZUt9SKWz3~d0eZh_Dobp3Jg$KlGB|KIOptGQ;gJR87I2JzX z#?Tk9nV_GN6A4K{XA-6=BjrVv^%tYs-}IV`?g{6zNbg~CoeM|xp>SJ@SiN2T5^4;P6rhl zVNzs>pAn2sO3~7e_1$Oiv#zz!Kc~=7vxhen3$6dd)C|^nIAy*!>lU3VJ2ylg@^;4X z00T+0RK?EMKzrD=6J~v5!`$@I_ zgoZ;HsW_W#yWD-D_>$_FxRft0OTzM4mw_7N^fdm7c)EXj{W^UqVhTzyJT7&jD5mfJ zz!lxRGKOgWgi^g$VH+pur~-^6L+R2mKwPK}03!uLD4$fN3YR!#m^esQXc|m;uf@2^ zcVDfuduZk1X;jM@rag&2`$&3@JW_8bI%X>gYtDmFAWBZV;o+`7kfo}5VkM(N{lxyU z(DC0R+@x*-CH*~(Hu83#ca^Y2QFrh&$Fsu_t!vd4!s4x*@=h}%RE`-QCpX+e z`L#_4C31Znx#o8%u{ry`U%RUmx>5ph333cPe)O>i9-&+NI9zM&&==S75f7v_!|DC`TRXit`l@<^PBC3giBRA(eZ7g#FOIu;6o z2cvI^+?oAzu-~MiysFUIkR6RV&fX2Kob*_(VKs0vL6-Qv5LEMRBaDB9(gvZT$b7ax z?l2iNE~${99ZC7fFlR(lV~Q1+yQsX3 z&mqm42ujfqE|3Q@8NyA|^8$+y2A&D9i#VchIBE4KD`;QYPtGPDmf+*F@dVe1si5FI z?cGn`|1=oLmM({xjPV5i#R~J@EN_?n#3fD{ELDeI_6!!x^^2O+KjeaECah5A)qs`c zDyWeDz(x;2RJ}&32-B*Uq{$|UZ2ar72&0Ncu$QP3;J3z>8XcAnM(myU zp6TX^#sXDdhbR46dpe=)pmZ5gVRws{Aq&U&g9*+_79y$>T9fQZ!8@_c5d)<(>dq;S zD(!Z1n7Qx$i0iApn-7{h?gi5HTjfMtD-mAo=>Q;$&M=3tGSpO9aFiySc+TtL>y}#K zTo!w$>83cmGu-z5zr0Q;Kd^uPgFOeiXOajxQYduqWbo@5T-#j@ z?l3+ki?OPb7b2mVQ^-{)k1po`-5owX<+he3C(#x7+5L$m23AjcE5M+!+L-rQnjhXo zNyEQ;{qhXp+g^xng*N+D8A};gYscuJD%m#GqKTt@gFY5-7#4DqwvDAN5ER_L;XYAm zI5zSkG5zJ1bDdRALlG1`j!U>OR`#ZAJ@}aQ3J(Un9J`-X z=~XiJ=q@b~7=mAqlou@&WpBExBz*3}eNtV^sYd;tHN(O8+nj`BAqBX6fD}hB?AL^7 zwog!_!myhRE%vpd)L#;dCd(Ou!pZUb5lx2}L$;+mPk6BQ>K5mZnGyjcI5)%80kCW* za|bHiStYA;!L$M(1&cm*(!xQ)&3tTKSwYvLp9g|5AbOMYxT>j4hyivD5f*&P#SXja z=P3`xSm zi8{nW&|Sji&TU>dK)IBqoU*mfhC%VMhS95DDU&4>(n4J;1bHqln<}w>3elJ1T;EPr z)WMX6rco8OdkmE0Y1XGs5&knt1Duv~bSsu#mejQk1qFc;7;{On02SMEvT4q{{~{Hb zZ>%r>@%!OFrlX7T-o{%*ebZ%gLG-IOZ{Te1shfNEUm=|F52h%9mdb8-d$>1EExEDD zNPtSwRmALIUgo1<%x(8E?doGwwo6r4)zm$S(=AZzZh0)=`QeD}Xkos8^B! z?D4|(b>Z<=-Cnx@q28fX&JLqED~Umpgz{7<9-R+f$h*afE+s>#cC|doWMRWL&-y*B zW@qMQ$AUUVjYIDY;)Wl(W$viEk6FvYQumde2Q)vb` zybIBuQUyh-B@@V5g=?j}M?0o>h5yd#Txq#ot^GLSDGLyUMDL+!KsJ>!5~8SE@*>vm+F1E(p_U|Z3rrdNcz#|9ERnx9I(S^A4e2Zd`s*E))_f0&?bVSxZ!DJwMO z6C9x%Tz#sM7U`M6H#% z&f0jt$t%X#AbmxsfiX5i2X$aeEtN1IkF*sd27Qw1+R4t{K#ylA!`WBHOo-94vMw~Q z_scv|!$6JIgy*p0;j$G&1sj`=ZmaMZ;pn=byfX(m2w~f{6&gxRFG}6vcSp%fWZcyh zhNJiHsx%=9=m>A^i*ur=Z z^!X`EckY(AEA5{|^Hpk!1r?#o{;N7F5K&9#{b(Q*Yr{;`>QVqHqfYu8_;A9zjE4t+ z2i4E^9t}HxwhFCeFPr7dJ@6uX*QBeyhHlJVLmtwWgVKdgZZ`eyD?rCSy1e^TMK`-} zYts{3CiNP05G$Bw4&y-AMLtXO;O&813t>e41cRQPO2)NH{3<0zPNMD!B`Z*o0U)Z@ zuaQdA0QQ4o*h-G?ob97VsG7^GDdg?66bx4L(K(Yqy3z{a1{e}z@)9jA`?@0LIp5NK zW8`r^6~0!eCLYS{3e)w3EKoVZ_%kPWEG1Orrmc?+J71}l4yk-NYnMa-@ddenlG~!9 zbwG)}p$5e6C97BAM6KjUl$cw94#n}^=KSMy^(0o(`DdCpg=_QZ6ovC!G3@jiti?Pi zG$bSj^wPa?hVstVrY#an0)@lcH| zq`KmuYPCvpsk~jQvrWCsuK2p>o~t!A(PIqEuNYN4IrLZx8dI>WxI>W6&{~JiJxyWK zm^CXF2m9Y{#?^!CkNwdD-e7%{uprm)@JYp^vne5@p6 z@=fg4s+-jo)T{z9arW8AZM)k?ui+f*9sW+jeTyDI?XJ1&FTBQ-q?V^F>N0tLW_Ils zT|sc9bW-Bth(I2{?(V0@>VnXlT7396v^qbZ&v;cof7g8R-(ZfB*g`A^oENMD-`BMdBmm7Czfsmza9w-Du(6ZxRBx z3KGMG9`lU57V7pPbbYt*Mc|ZCRGnOl&%^58hgAEe(N%|1b0!k~zZB!!VYh3pqm2NLM1I0@iJKr^Xl3 zDEj5Gl%QgHf(r;*x8W_2@Tf;7N$<9X^$|BY}`giV)QBwpwtL}9kcG$ zolovVqsPOphH`3v09Jj>qkbfxA%ghWnfM(fJnYBsP9{uGh3=tH0y~j+eG-T!5l~Eo@^66Ijekt54?&r zwiy4sMAcs=<=`Huxwz25c2cEX%00kU8u7rwIibk%V7lz74PtEPxFF=fDK~gpzji{edhre zIeTzw6g0q1zz`0_0$&T{mr47s9WIG0_};dJj5_b~f@XSlLqc4YcJkF}tIU-2PF$#Q z0&-&yWD)e>i7G)SUM(OzV!UA!aqmh;4PWKrsBUI9h(XM}ck$s>IIK0(mHyy^55f<= zm%dnXrz2uYCpMV}T8GOhL z^IxR{QK2v+I0s|2F5bBdkYhPrRMywM2CIsJ8X4WRP*p(5(h~@Bpt~P(y7IJ1O@|J< zAz+Zve3cdO=XOqtF_%?q*DGh&36w=v(yhVKmT#PxA#Xs5KV<~@DOBc6nOAor_F3X4 z&2VxPseH1hUMB8N5n-{xdbN{tb_0@Bm+X91YcjlC(CRZMsA!_RPu3e_powhF zf@+?0*znuo>jw!hrg1nZ<(6YKP|)lQefs(du%<)8R9c3Um}035Shy>0#2$k3QIkv}GxA(2`rk81go{{dKdCE6NQHyGjYPZDA9xqk&6ghhvK0 z+iwGOf{nAMq#0LvPZ|{VNuU5}WB;lGz07wytJ%t>e?;E6j3*t`Ti#Vb)`)WIO8bJ0 zl;32G9_$JJ`1OC#l}C5|DK6JKb+C-r%jXEs`2V@nL&IN-~$cAX^S)MkA*d6=DUGFq7IU zg*=q=C~xKg>E>{~6tQvdWVqQI6Zkw&1r*-G2b97T+38{G3C@KXH4H;3Af|w*$=?!p zeHgM{5!|P3{)jj&kEnQr20_}e=JG9%VA8yHS>$)!tX3CH;c!RXUT%!O37nZVFce3b zPu?m;_`q1Ly*=7)Ih(_wOU+d?Sj+bCu@UiuxI~o^Mha+@t=t>k;3_=uz%Qec?b4&` zE0X_PqzcII64ASyLBuC&)?TqV*zj<#692uj-8OK_;1?k+IUE0zxF%oOT%c z?@k+NLF_j3(WVtNsHQR+ojs!0WVJoQRZ zbRdkbqROZf^m6Z3*Y?`r-au(|AZ%2(16*3XiJ??w1eES_crT55S~`yndc^f5nLxSX zRkkOzg8KLN>+in(&*6Xf2a=m3rpJp@rsW){yNI{NK*BZ+mpEZVm4Oy#n%e?oODvNy zQqrNz#^Aea+?Qf#gS=q5%ikF~^sr1bC1R8r)uVy0Res&(M|V}c#Am9I>4@^q4ef(5Of5|v^g@T2jY)N7VX1LQnDoh7>A@#m z_$l3FO5z>f{<|+xXNnB2z}%x*xKY8? zy}Bx*d{X1UtHzv)H5Mm3r8YRSGte-k@%V0B!~a8lmrG zzb`uoE?cfDfI3ZIewnhpmD(f|MEmlRv<~aZ$!kz_WfH-ke_kQBEPx3yo9VC)?WduLk5(~YGF!_RnKQiIxdo0;qv|m4(ASO z%0m6;X|DvCWi_UeR>wnqYe7IM8-;`_FSOJr$l(OAVscCjK>2WLaNmJ4dE`z^2(@|K^@oP!@&)#^!MVJ7QnuIM=(XBTKsuPXqfikW=H}p-^KMsH&BhzP8G) zn({VNmvS`^rRrVcQ`%+Lp3JBewjS-13{Llw;-_T?+)o|~ZrZPGo8^lKL=Alxx$4|dik4}$LSw`aXvq~#92CF#8bWIy1de=Y=xq_ZMzt(*9k>BcHVU&~jn*=-nVxrAK=5sZ?7H zV2X`dc1Ra7TDF}E&8oT($PbNFz86XV6ZH}j3^mXI7P4qgxoz+%30sRpIpsMOI6mvf zg@-5c8MCre(UI&gifiO<0Dumpdal5Q^HBI!=x!?KnW`EBO@`#4O8IQCa#H z__qzarf4Djs%SKl!>T2De_OP^(E+EhB8$ri~H4-Z|LQ zUS@G&Ipr4{N|1#{R^(T(uaqck#DBN>56)pjb$wPmt56S}}@tZ5p*U;(NtJeAcfJ=j;VpV#$mdtOUTDsOQ+(4ArtJY8H z2ZRXvq^7L>L|Z=HZPx_VF54CT+r&{(UP~%}0LFzD$av{>R@M7@vcc-T=tDcLv!@A= zJ=EAQp{rPqHdJkIy=Iog=uU}J7Cf>emei6DLw3jI-X#t)6C`lJhF6g%6)qtq+vR3L$F&pUK&77kE2nMieF8yDU1o~TRkATAzZ(sTpG z4eJ1!JlU5BIC8B1Q%0+w^4FB5cb(!_D4eZ@Y(weKI>b_kRrjoG2P*zbx!tyNIk`Gc zwN{vvGVp~6ZJNGK7fsdE0Wb;x(2p!4{6a5rAOJQZ>X(oQ2Pc6A*o>qzT;Qk4N-faq;whcN7vXT-;a< zdG>Ly!Sgf!Y`ynH9c-bo&1D{jav&NI^%vbgi|VB9 zL7vWM5_bu~Ox)_rzY8Cv;u|j16p=tVkEM6|gU?m$F_2vt3?X_3u|Wwy=}(MA8*a-U zPb*H)V(W`7o~1)OhS@z{@;np6J)iD@jalwpuJT?&x$I=`PFO6A)J`l&+C=nLs)n3Z z*TlY|qpdU&X>6nu_}UFzv{>GTOSMNo5Gk`dc;;wzp2nBrmi8$2zVJxx9et=j{vw^v zuz;w53Fpn8X;6X(cMF&ZRE2A7l|b+=^zUp{Ue$sX8eWdy*-U-vL)Fg~%qS%pJNxL}t5jg|?_Sv#^II(he;V^e?+;-QcDMe(vTB_|avT z*JZCx?OsvQ!Tr6295wahrP^HWZN@=4N%I7!H{GZ@?h228zU=OEG|LMZHvR1mwnKqgd55eGKqArNMKBY`P+GfCSkF- zbh2vL!DWhR@hQu^)e#irm{D6O6EX#PU()QJQ;KoR(i3IzMBg`1t8P@ay?45UoTW=J z1k1W`Nv#w9j5k$jW33v#QK9VKfFX@a)g^VbeOW9VyHX92?GG5W=$7!_(XR*P_|>AM zyhe}+1KqAVkhM33z^kIYt5}xqMDp%S47iU%Rjmytk22T(Gt61-cSr(Y-dx`OPsdd_8o6k&>MwlBTm)BnibcM<=;*$?W(}iRXZrPfW zI4|HMrjMR9JZJ=@>$HmE(*|LCDoz`m(>c41eca?GnaIW71>2eo78 zZZB4dQcj&&X)0Z+eu@gz!D0>6D3@Nn2V!~NefA!nx7x23giXD?0CK--!8#D{EzQ2J zdYOcXU7K}f>Q5KycUUr1iB1VTjMVe9J3}e$Cd-2W1kf^={Zc+j5)eOp=(etT@QXFD z`rOrK^0ul@OW~4UrS#u!^;E2TaQ{+W@>fa>D9^d#*Cs#bsbfu>>+8SU)j`7R?4`hBa8tAKNIXT^Cs-74*_OZnso=m* zlH7`*WQAUNYG=nZynqx8bG6zQ<4EIwG3l}IE2n;t+UrAv*~3vkF}2Xn83e7x>fK#| zGX~B{LzgFtfnw&ec0`)>>Md;3OXES$7Smf-qQ34b+_>=TNfty?Vg;%~`>5%>x&k(O zy`~^8E8OK|$`4-xfu9TWCZw$`nlgXV@)iu0wshrcpygxX#OmeBuwiaZ?GvBp=1D?X>jXy@us$$1d+R^c)c1>BScE z*X?$GVE-5Nf74Gh^%&?bj`jt_wQ=S;UdcX6XP9eU90t2qPdky6Nh6Ol9oX0T!2)x_ zmvc}C^&BGj^N zS`hHcPPcL}>Iv&Gq36uYZ0R2|5?s@a4ZHSC>?@LYy<`1XHn=Mll8!J=deG2^LShb> zck78eC{lWh=2P+;oEyGD6)fQ;J<}Ixvg!0s4%HnX#*KJh#A9>21DVL?Nn^5q;C(w$ zbyO;G%iCr6i~XJEc5h>o5S6!!bK!16a&r%P6r+3<^edhpK_-xEHrq2#4|tu>T+qvM zRJ=NoyUya-7Ilu6zw*@aTZG0(aA&JNy;blAflCNEHju4qxUd_cciu^`UgiEsQh+0b z_dm^kLY5RaY<8HtHW$rs*A3S=8N;$;A`uCKF9Ak3a1OfQ%T2c>t{Az*N{#Sr>4QA2 zVBYRj8caBGEYOpI8{Az2mBPx$YnD5Z95WE1zWQbRahVx@zXdFul4>2`Wi;Q&Q!ryqLvgVS-WmSp&W>(8&>T2$~~!VeeTB;5tbe|5XcfY)CW(g4pIV>yQx@FvipDVJ#t!R zj3pha*M+^}K=bO5vkl`FY-~Z$yg8slkx8XO{}Y!_9lTSHMQdE`4(aXH{7GFCqG_Jf z>rf71AL>ipH6v!KoOgt){iF%Wg?@o(ct~VM=yxjBE5no&uz`?kkzmtdVICFCO9ISl zH=VvzmnBk1Pz-{13pm2rj z-Ye!Pp8iHkj9=NMb}S0aH{_G2;{pw+%Xfo0kU^>TG}5v0Iri&P4})LDLy?S4*tCKl zaI=c&1>k?fXOi*>oSxJVTLJ4dp94U_s_uy6hsQ7Br_ir^P?S)6H4`!b%+~ly5~Mnp zR2vT|BWJ(>El{3--R4r}9(H`0?U3qe~g6zCv zFGCx%NkUR9m^|-K)UO%a9^)7*O?p^T>x^{A=rmU9S8aF(bWQz49=nuFCkJoXC=-MO z^8zf;&{<%P(PXtj+e!U`OR8U7kVz~=spE|m7C5{!HWL;b27XfO0Tx zgdajgT9#rc-L9o8OE*lEu<>LB==-^^k}`PiC&fMugLjnJebIUv2=2((4N3 z;)UT28tBGA?h{K?dO$G$Xv7BwS4UCE6_gb4M=_jia4#*b?R1j>X~-<-FjePMJESgV zfVwM2&cWdm$YDexB0}sl&viybOAa2+Iw2dc`xT+gBpeVTDehumYuAIEs8@SOEKQmw#=ka&qS*Kq%lk2T=gijgf@m zG*-}UE4k5}IB3iQ6i2>adKQmkgqWI%CCtjDVB7#U%F!8^L`EJ7W8H|1K(pOh}IboipC^?Njh zx~1Vd4SLziR!mN7pUO*Cxtwxh$AX)$|X&bJN_36KN0E2tq)Qvb?_6^#z zxCG>}<(wt&LHquy-Oq*!f+nimG=kU|U+n9V_GMt#3GNs=rGnBY#W>`M!q-bjTSTJ| zrB-xup$w@LogW1$#$lp)qk2>GsZXQly06{}2cS*ASzW!|c$j<1ThcDHg-VfKR-mD1 z(>J;#Hn>gQ+J?rIp;|0>QSpGl<O2K z+q5Ld0$nZKGpBs^dFK-*VvFlPfhu7|5$^V=q-~Y&EYvTzJp6gQHZx95=X;n#CmrX@QLciKcNzFEi zktCu1lAT)(Mt1sLX^CV0|Oi{+-BA zUZ^x|R2-gkIE9J<*=3b0zmOjaFgZ0>*GWxn?e>-`__Euky^RETK6^ zNW4jCJ(7s-azMs472DzUW911&mjICR#}p$5wF_MonQ`w3835sfXhi^BGBdt&;fL|e z=)>YSy#7-2AV&q+JhM;`v)tU1D%a2c{CE5{@HhHjUtpXw#JIRH82S-YU6v}TqmaNg zJj|xJOv(%_SFpxG%qX&vrz%kwm9KXu$1+1t*Y%kG30n>Q#l{~}Cfz&?d3b_rmsj^A zGMSM`d!lvGEjkzqATAyVorlZiO!oyd-0UN~IqVUTbXsB+;loSk3b<8osQbIRCM}9+ zj5shou+!Tx8Nm1jAWiVDpaX%B8K#m*B30>u+Ak(xTUHPTRnAHwz&e;q2J9#~3B)O? zDQV&SiKjh0?G&VPMT0)erlOkIQ6SOD^LYb4gFkxmvyMLS)2fNzF^m}Z{?`^t-M<}8@*tIEqtu9Lp$iKJy} zE1wW>zj^&Re+~RidVmuB7GE7yh@2TCqnsTIs4geA11s5B~9 zzcXpIYkz?1cPARHQpr&MUfLw150vb3>WQ@aGJ;V+CqfBikeQNxvX=Np7%ql}(*4)W zEkLbxmjlHX=M~V<>P+#MMD@M^AY^fv+E@T@uyh0cIKWh7LSIlyUe<^7Wn33X0+gb1 zbya%|DYNV->EjCXx_}ku=Gm!zJ*`yJ2V*?KP-ULMD6>_+3N#tEKiq7#s@pBBb^vH3 zPvnq5(qzIb$^*XXk#o0gr9KbJKDv{HZId%qViay$eC{5B-*OkM#7}y#N&e1)On+2Dpz zmI0-D+L7D82#?FV&)@$O61L*TPO!0SJ}67x`z`D4K8&ip&+T1F;nSj)3+WliimIuP#-)cz$SCw8CQO10T> zhNAQgtVAs2~pL?p5vLpSRFro~l;mXlDjlVmO#;pXiiLMoJZGw^;D) zZ!g%(IHy>vWK;-k=M2SWT4&ug?hYKDm3Pj;X!HUC{wRGR!9p7mOBWr<)So5YQJ9=v zF(+x7gcGiJ_B`s z7hXTb71-$8hN1-rX5ih!x@JrUC+p?f)wQDrV4r1g{*EYsy-VjbhAtc4V#Lk!+}BR4 zyFw=nT^V&M-lEf69;*%0yPfSfGRdF=UByG#w^;lpfk|VZoY=F0at&R_b@jD(L=&2A zR!Ndqphc0+)xJs0wk&q`o?5Xzj2BQ6@10!Er=rqTl6K8bS*nVyb};)mDXmm$%%-0* zcMPz$CAKOq;R-nlK(OjgVq;nQ<|w!uh*BFSKiipppm<}(S+y-n@A{+nAHQm=F2C3h z(JJtrTu5vh6cJnGl?RsE-A0At5r+x`HR)x19xj(Rx@ch;lXoGP=IT8{Ek9RgOTpZi zZ2I`u6$3H?ZDA?vW@pfqa0Gh z-PIOEKpb-yKCXVH@U9qZL0ok$Ku-B|b0rT<;Xz4J37%-blz6>4Uv;6+(lCu{l4kZ* ziTvFjUwgBwesGPuLY(2|Yde=rdpNmdmoH`0cC=TS95p#Kx=m6*L^9)&faXLplQdty z3a@|UXA9ec&_gTY=5U=6XWqkrv?pV&SdZ`&ofpN9q>KmlKWZQjrCi#50$?Wj0S11A zg29HXJ(Lod^z8gt!8K6Y>_W+37^yi@@OOB;8@?SI?1w9`jWBIqIKsb&1JtLY@2=C5 z$pKJ@7`t=lqh!vMsX7o(PH^J5OXvhmY13w@bQ100n^X>1k}r4k#iFu;(nnlk@~gw+MOY54R~1Vg zO0J5pS++rCp?bG=URP*X*?)Tg`F)4c7!8Z3?0G!5T@SW8=r_iEcw!kWFC30Sev+=N zR8~yPi$s?K0xlhQBterAa1$1}+AC$>7YM?U71Jm6Lm#EjQKiUS#Y8GGC5%e>4Jefo zBzq}o!So|KE$us?Ur^k@n6)Q?nbM+~_w0P0B0%=`;p7LtDlj-5v|%ZrR#r&S)7if% z%C2buDJsS_Jnh)dmR+PLlw8;V%hk>?Vqp1_2#eJP@Qmqfp#_&uW9tK05yB(R&0i&Q zvWs zr~@DetCspCefsf z%qR0HlOh}7&0_@+*4L>XuBU!dUq$;2sQgUbUpIsowMod~8#{0lmTiw2Tq*y%YnJ8E z4&RP9yq~>|MI4v^C;z`i)@R6UMd}?(R7_DN0eg|w8OaZM+xAQ%I1$t6OrzFa8OlA5 z$nl+1h<1p2&up70eW3$2Rq0i&;Vq%>Q(V=7q^@Y}Coa{^VH!%7Ztefi*qbd$b6jVF z_xTh~^^A>VP4WRoZOwTTna7fmkr^9e$*iccX_G|j%bAhxB%7Ni2rk%25Cj1d;9^#P z;l1X3$Io{>3X*IFG~%!NQI#1P;ePxq-xA5Yto!L`sf)(Dv$xd@Tz;=$Z)4hEU1BaI zL)PI2>0RYul8_~B3Kcdpn(c~8!ag!sghj)+gKuYN`0EESZchIW$8Rty*#p&SgLH!lO{t5eGos<~L}r-{HAQiKc@%$u%g z8308?==|DvNloia$jogscsvD~~Fi7phs!xt*jpZEEZzNyq}uFlK*lJPze~2+w=XIswO6 zvmqeYH&COZ#UXaF&fUE~6#F9>ktqW0JmEG_8y29oH~S$#=F^>lHOZ`u$@ezUUK#L4 zn9Q)X2)w5OjcN?=FQb(k4SHGiye3h7FEA9%c$)lNJlBd_%kn>xom3##P~c~9#`tEK zL2WG)4?9#k6v6Iy1fANv*;U|)T@r@0*)ac~GckR0p|I88q?4OW9E$uZPnEN7W(Wup ze1~ordI{(y5?cRg7tjN- z^U4TWv&zAt<8kxOn|A=-eT34=P#G5>@G~Y}<%!cJt6m|!7?5DohBmso=Q_&s1UR(K z(j()Uvuc$shWcRgwBUef8G_&6l{f%(yJ=k^rf7*5exwcvJXO4ccmQ&iz5-HTb?B{J zGtY|$Rn@Lh4SvuAT2~Juc!u87p5%zXV487|CzM<~3-S~PaciZ&e3V!)AK4uPrC}XU z5TLZ;DS;eS>F~3I@{h$4kO`YBl`& zr_&Qy!i6drNZF>MfzwI7ZHFA()2-=zleE2RP%-b-m1@WS8MCqco6tfkl9~oQE7T#o zN&|{>H`0nl@;vqbF70mi(7X3-T6Wm9JfN@(M&7wEgAK7;p5xFkmbb`!AO-Ku?3C}` z!7LmOia`bE-}0R0;cZq*f&G99n=1bUA;3#QLbZU*KSFyOu2E>MSPcUk(amZ1)hHjW zxolMWQfC3gqcn18X$aC3M9HW&NUOscd5e*Wy1MS#lULu}ZU;IJ{Xl0T8Wfq~L*2N7*CJJxJcWLMEe2V2Yn!tMxix!~ z)dw`cLI0Ftzi?H;*IT`Nf2S6{FA21`!l5G@&7kL-D*YsY&aDPFN7Xx6uO;>fD`Fy*g!-o4m`PoMj>ECRJOU`cjVB{6f|f#Mz7hScc`y z_nuX|j%%umz|x0i(0_k zxKQfqwCs>&DK<7@It=DIAJ>~LspY*=`1QNIs1}=`!w|~%?T=qSPX%{4ev_W2<6U$T zM!ko##2f3D4IsdeTzIk)pkbbV_gtpoD_SuUuc4e@m`H5aS(UTp#I1SeZn&tr3cLOP5=+y-PV_Jjv@_3hIl>5S{jsTXaG?PR5*$PYe~cWU~B}1{XH)sKP_4J+?rd`J-Ev$-*GVq@K$>I)Yoab;i-;sa^gM zSxZT0EO6MfIz7^4Rl>4cv~DM~f}YgeAM6Uh{*??ze0J@?YCV|Z>EN~F-0 zl+5!V42djo4xu?gScf`LsvA48d7iqxgB-qN~ zhQh7wU7}Rs+r;K)iK;inCQL??LD#`biKo=1mHk=D@&ogU3UZwzh8p#uHF(dDUp};a z2zD=QC`nLyvgJ@G&5cK!ElGBCM$(qFiP0CQXY4hjEMT*7r%_S@RF!A%({oTTf<;Pr zqa1|j$A?uadH4L;`mDdbY_8i41TOnSY6*utNCL8hi#o3XB!F;sJ2pBzRlZ#brfCv^ zlRx1L)bCWAsgs-;#SC!RGz=x@EnH7`pPE1b$?h;A3p z7Wzkv;}iSwd_EXURfZ^uc))~bnT@mofmXP`Akb-t9CD**R>*l+)R)}qggT^E@}n9z zr#Fvt0_=1yPd=xvF{4+rom3-9d6?KYWm0NzbImyHWXTRb=3kPkyrtDp*!WTwxGKm4 zpRxD_6c`b?x8nPp6A|db*3aT z0je%g+5oTbzFT%ZuV1@{&L7gp!V;0ARnE}eG(57`Wf{TD*S{nRI}t%HnJlrFpugkN&MPe zb{`ARm8dkJGmap$CA8-CE7)>MxV35@S&-@oLGUEd)uaohwe$cqjj0qLIah~kN~bRO zZjmxxtW@vRMY2m#@R?dPrJPufiw^9pUC!D(+txOU;+`Ylu54 zHI+x*4X{mh799D+Fw&NnDj0?kqi)RSrg0%#XB+w!NGPAa!JD{|vf;jVq9^E3Dn~fvlsE#-|itigy2?6X-C4&gmzJG?kBSle4K4 zG!Z%P?V8>bBncNpIV+UuXfb7Z%A{uW>BY9({OMtl`9>4?i}3Q3(>~bvX;SD)EkW?E z1kv>NOq^W9Rbp3&(+26+l6qeU$v~jeB*W?03v1-ew)-<|x^y|0lJk9mfnLYmh}8qB z?NfuHqaWWWfI=K?M0G|VgP!T&yZ|NC{3eq<-%6v%v=gI;e>} zLL*sz)T&TYkQ*zy_N};ZL({l&h8_q`+<}aW#AC8dPUTNz`_fISl>-CvL)M`U&!@#% zc1t*$Dqrf*xRW4eUT4n+H&+erYG;Jqp!`YRm(F-NIg-7!p`to1nu%Igs`zTUaHBT~ zTFDAZHfbhxSeRkA5GvCq8mmwYOe2*UtM)AK;g_q`=MJ^6*<0bWd}_0s7#H)ctR-|etQe;r;vJjj;H(ev)p*H109s|CA@AncNf$l9%mT_1vR=%8Ue9i~4^ zoswdrVVYS>9ir}9v+ccVHx1_~H(0wV5Jl%ler)eCof6Vt*psvogH z*r5;2z-J)2pbcu^QlG9E^S=$!vSHZ_G5~B!@P#LjsqD6JFhPNMMNKvMv+R6pe3gMU zk@5qR?T;i%x6>Uh|Kg;`D4sA~-Ke-TIS2|UwaA!C0Cj7y$KR*y-d23}WG=Ca;?`VX zyJ%ZfHI;w$midh%+~Ad9dd+g{RYKV3J8_ZRHCrq#7%qU)gm#9!x)k3P*xmu2wkNm? zWpEa#1%0A|F7fvQOLDQFPf3 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%0A|F7fvQOLDQFPf3 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